164 lines
5.7 KiB
Python
164 lines
5.7 KiB
Python
|
|
import cv2
|
||
|
|
import numpy as np
|
||
|
|
import matplotlib.pyplot as plt
|
||
|
|
import json
|
||
|
|
|
||
|
|
# 读取图片
|
||
|
|
img_path = r"C:\Users\yangy\.cursor\projects\h-soft-projects-luxsin-dashboard/assets/c__Users_yangy_AppData_Roaming_Cursor_User_workspaceStorage_b134a9df77916b35c1e5b1ece8dc14fe_images_Arcona-avg-0b78da44-4aaa-465b-9b2b-fba051a28742.png"
|
||
|
|
img = cv2.imread(img_path)
|
||
|
|
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||
|
|
img_copy = img.copy()
|
||
|
|
gray = cv2.cvtColor(img_copy, cv2.COLOR_RGB2GRAY)
|
||
|
|
|
||
|
|
height, width = gray.shape
|
||
|
|
print(f"图片尺寸:{height}x{width}")
|
||
|
|
|
||
|
|
# 定义图表区域
|
||
|
|
chart_top = int(height * 0.05)
|
||
|
|
chart_bottom = int(height * 0.92)
|
||
|
|
chart_left = int(width * 0.02)
|
||
|
|
chart_right = int(width * 0.98)
|
||
|
|
|
||
|
|
chart_height = chart_bottom - chart_top
|
||
|
|
chart_width = chart_right - chart_left
|
||
|
|
|
||
|
|
print(f"图表区域:top={chart_top}, bottom={chart_bottom}, left={chart_left}, right={chart_right}")
|
||
|
|
print(f"图表尺寸:{chart_width}x{chart_height}")
|
||
|
|
|
||
|
|
# 频率轴映射(对数刻度)
|
||
|
|
# 从图中可以看到:20Hz 在最左边,20kHz 在最右边
|
||
|
|
# 使用对数刻度映射
|
||
|
|
freq_min = 20 # Hz
|
||
|
|
freq_max = 20000 # Hz
|
||
|
|
|
||
|
|
# dB 轴映射(线性刻度)
|
||
|
|
# 从图中可以看到:顶部约 120dB,底部约 70dB
|
||
|
|
db_max = 120 # 图表顶部对应的 dB 值(y 坐标最小)
|
||
|
|
db_min = 70 # 图表底部对应的 dB 值(y 坐标最大)
|
||
|
|
|
||
|
|
# 提取白色线条
|
||
|
|
chart_roi = gray[chart_top:chart_bottom, chart_left:chart_right]
|
||
|
|
_, thresh = cv2.threshold(chart_roi, 200, 255, cv2.THRESH_BINARY)
|
||
|
|
|
||
|
|
kernel = np.ones((3,3), np.uint8)
|
||
|
|
dilated_thresh = cv2.dilate(thresh, kernel, iterations=2)
|
||
|
|
eroded_thresh = cv2.erode(dilated_thresh, kernel, iterations=1)
|
||
|
|
|
||
|
|
contours, _ = cv2.findContours(eroded_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||
|
|
|
||
|
|
largest_contour = None
|
||
|
|
max_area = 0
|
||
|
|
for contour in contours:
|
||
|
|
area = cv2.contourArea(contour)
|
||
|
|
if area > max_area:
|
||
|
|
max_area = area
|
||
|
|
largest_contour = contour
|
||
|
|
|
||
|
|
frequency_data = []
|
||
|
|
|
||
|
|
if largest_contour is not None:
|
||
|
|
x_coords = {}
|
||
|
|
|
||
|
|
for point in largest_contour:
|
||
|
|
x, y = point[0]
|
||
|
|
global_x = int(x) + chart_left
|
||
|
|
global_y = int(y) + chart_top
|
||
|
|
|
||
|
|
if global_x not in x_coords:
|
||
|
|
x_coords[global_x] = []
|
||
|
|
x_coords[global_x].append(global_y)
|
||
|
|
|
||
|
|
# 对每个 x,计算平均 y 值,并转换为频率和 dB 值
|
||
|
|
for x in sorted(x_coords.keys()):
|
||
|
|
y_values = x_coords[x]
|
||
|
|
avg_y = sum(y_values) // len(y_values)
|
||
|
|
|
||
|
|
# 计算在图表中的相对位置
|
||
|
|
relative_x = x - chart_left
|
||
|
|
relative_y = avg_y - chart_top
|
||
|
|
|
||
|
|
# 转换为频率(对数刻度)
|
||
|
|
# log10(f) = log10(f_min) + (relative_x / chart_width) * (log10(f_max) - log10(f_min))
|
||
|
|
log_freq = np.log10(freq_min) + (relative_x / chart_width) * (np.log10(freq_max) - np.log10(freq_min))
|
||
|
|
frequency_hz = 10 ** log_freq
|
||
|
|
|
||
|
|
# 转换为 dB 值(线性刻度,注意 y 轴是反向的)
|
||
|
|
db_value = db_max - (relative_y / chart_height) * (db_max - db_min)
|
||
|
|
|
||
|
|
frequency_data.append({
|
||
|
|
"pixel_x": int(x),
|
||
|
|
"pixel_y": int(avg_y),
|
||
|
|
"frequency_hz": round(frequency_hz, 2),
|
||
|
|
"db_value": round(db_value, 2)
|
||
|
|
})
|
||
|
|
|
||
|
|
print(f"\n提取的频响曲线点数:{len(frequency_data)}")
|
||
|
|
|
||
|
|
# 创建可视化结果
|
||
|
|
result = img_copy.copy()
|
||
|
|
for point in frequency_data:
|
||
|
|
cv2.circle(result, (point["pixel_x"], point["pixel_y"]), 1, (255, 0, 0), -1)
|
||
|
|
|
||
|
|
plt.figure(figsize=(20, 10))
|
||
|
|
plt.imshow(result)
|
||
|
|
plt.title(f'Extracted Frequency Response ({len(frequency_data)} points)')
|
||
|
|
plt.axis('off')
|
||
|
|
plt.tight_layout()
|
||
|
|
plt.show()
|
||
|
|
|
||
|
|
# 保存详细数据到 JSON
|
||
|
|
output_data = {
|
||
|
|
"metadata": {
|
||
|
|
"image_size": {"width": width, "height": height},
|
||
|
|
"chart_area": {
|
||
|
|
"top": chart_top,
|
||
|
|
"bottom": chart_bottom,
|
||
|
|
"left": chart_left,
|
||
|
|
"right": chart_right
|
||
|
|
},
|
||
|
|
"frequency_range": {"min": freq_min, "max": freq_max},
|
||
|
|
"db_range": {"min": db_min, "max": db_max}
|
||
|
|
},
|
||
|
|
"data_points": frequency_data
|
||
|
|
}
|
||
|
|
|
||
|
|
with open('frequency_response_detailed.json', 'w', encoding='utf-8') as f:
|
||
|
|
json.dump(output_data, f, ensure_ascii=False, indent=2)
|
||
|
|
|
||
|
|
print(f"\n详细数据已保存到 frequency_response_detailed.json")
|
||
|
|
|
||
|
|
# 显示前 50 个点
|
||
|
|
print(f"\n前 50 个频响点位(像素坐标 -> 实际值):")
|
||
|
|
print(f"{'序号':<6} {'X':<8} {'Y':<8} {'频率 (Hz)':<12} {'dB 值':<8}")
|
||
|
|
print("-" * 50)
|
||
|
|
for i, point in enumerate(frequency_data[:50]):
|
||
|
|
print(f"{i+1:<6} {point['pixel_x']:<8} {point['pixel_y']:<8} {point['frequency_hz']:<12.2f} {point['db_value']:<8.2f}")
|
||
|
|
|
||
|
|
# 保存为 CSV 格式
|
||
|
|
with open('frequency_response.csv', 'w', encoding='utf-8') as f:
|
||
|
|
f.write("index,pixel_x,pixel_y,frequency_hz,db_value\n")
|
||
|
|
for i, point in enumerate(frequency_data):
|
||
|
|
f.write(f"{i+1},{point['pixel_x']},{point['pixel_y']},{point['frequency_hz']},{point['db_value']}\n")
|
||
|
|
|
||
|
|
print(f"\nCSV 数据已保存到 frequency_response.csv")
|
||
|
|
|
||
|
|
# 绘制频率响应曲线图
|
||
|
|
frequencies = [p["frequency_hz"] for p in frequency_data]
|
||
|
|
db_values = [p["db_value"] for p in frequency_data]
|
||
|
|
|
||
|
|
plt.figure(figsize=(15, 8))
|
||
|
|
plt.semilogx(frequencies, db_values, linewidth=1)
|
||
|
|
plt.grid(True, which='both', linestyle='-', alpha=0.7)
|
||
|
|
plt.xlabel('Frequency (Hz)')
|
||
|
|
plt.ylabel('Amplitude (dB)')
|
||
|
|
plt.title('Extracted Frequency Response Curve')
|
||
|
|
plt.xlim(20, 20000)
|
||
|
|
plt.ylim(70, 120)
|
||
|
|
plt.tight_layout()
|
||
|
|
plt.savefig('frequency_response_curve.png', dpi=150)
|
||
|
|
plt.show()
|
||
|
|
|
||
|
|
print(f"\n频响曲线图已保存到 frequency_response_curve.png")
|
||
|
|
else:
|
||
|
|
print("未找到频响曲线")
|