问题描述
最近遇到一个仪表盘读数的问题,主要要识别三种仪表盘
参考了许多博客和论文,打算先用一种传统的方法试一下
解决方案
方案一:传统方法
- 模板匹配
- 直线拟合
- 表盘读数
方案二: 深度学习 (后续实现)
- YOLOX等目标检测方法识别表盘
- 目标检测方法识别数字、指针、指针旋转原点
- 欧式距离求相邻数字
- 字符识别模型识别数字
- 根据相邻数字求得指针所指数值
效果预览
1、原图
2、模板图
3、模板匹配结果
python cv2实现
1、模板匹配
选取合适的模板图,并根据模板图中关键点坐标求出各角度对应数值
import cv2
import numpy as np
from math import cos, pi, sin, acos
#模板匹配方法
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
method = cv2.TM_CCOEFF_NORMED
#centers表示所有模板图片的指针中心点坐标,(0,0)位于图片左上角
centers = [[47,50],[67,74],[102,96],[63,64],[66,67],[65,67],[107,105],[104,106],[94,89],[57,55],[66,71]]
#scales表示所有模板图片刻度线所在坐标
scales=[{0:(8,68),1000:(7,46),2000:(14,20),3000:(38,8),4000:(64,5),5000:(84,22),6000:(98,42),7000:(96,66)},
{0:(13,67),150:(17,50),200:(21,36),250:(30,27),300:(41,22),450:(65,18)},
{0:(24,97),1000:(27,73),1500:(30,50),2000:(47,40),2500:(56,28),3000:(68,25),4000:(83,16),5000:(93,16),6000:(99,17)},
{0:(14,63),50:(19,48),100:(26,29),150:(44,17),200:(65,13)},
{0:(14,67),200:(17,46),300:(24,32),400:(36,22),500:(50,15),600:(65,13)},
{0:(15,66),10:(14,55),20:(19,39),30:(32,36),40:(46,15),50:(63,13)},
{0:(30,105),1:(26,86),2:(36,71),3:(40,52),4:(58,42),5:(71,27),6:(90,27),7.2:(110,19)},
{0:(20,105),20:(21,80),40:(35,58),60:(54,39),80:(77,27),100:(101,26)},
{0:(25,90),0.5:(24,77),1.0:(32,57),1.5:(47,38),2.0:(66,24),2.5:(91,19)},
{0:(13,55),100:(12,45),200:(18,29),300:(29,19),400:(38,12),600:(57,10)},
{0:(18,71),200:(16,62),400:(21,43),600:(31,26),800:(43,18),1000:(64,14)}]
#angles表示所有模板图片对应刻度相对中心点的角度
angles=[]
#模板原图大小
original_template_image_size = [(128,124),(107,105),(164,166),(99,97),(104,107),(106,99),(163,160),(161,162),(140,140),(95,104),(112,114)]
#计算各个模板图刻度对应的角度
def calculate_angles(centers, scales):
template_number = len(centers)
for i in range(0, template_number):
angles.append({})
#print(f"模板{i+1}:")
for k, v in scales[i].items():
#第一个模板图片为圆形表盘,以中心点为轴,→为起始边向下旋转所成角度为r,r属于(0,360)
if i == 0:
r = acos((v[0] - centers[i][0])/((v[0] - centers[i][0]) ** 2 + (v[1] - centers[i][1]) ** 2) ** 0.5)
r = int(r * 180 / pi)
if 1000 < k < 7000:
r = 360 - r
else:
r = acos((centers[i][0] - v[0])/((v[0] - centers[i][0]) ** 2 + (v[1] - centers[i][1]) ** 2) ** 0.5)
r = int(r * 180 / pi)
angles[i][k]=r
#print(f"{k}刻度的角度为:",angles[i][k])
calculate_angles(centers,scales)
Tips:
- 模板匹配方法的选取可能对结果产生巨大影响
- 模板图片选取十分重要!!
2、直线拟合
对于一红一黑双指针问题,先识别红指针,再识别黑指针。具体问题具体分析,关键在于获取指针角度,而不是识别出指针
#获取指定图片的指针角度
def get_pointer_angle(img, template_type):
#shape = img.shape
center = centers[template_type]
center_x = center[0]
center_y = center[1]
freq_list = []
#圆形表盘
if template_type == 0:
for i in range(361):
x = 0.6 * center_x * cos(i * pi / 180) + center_x
y = 0.6 * center_x * sin(i * pi / 180) + center_y
x1 = 0.4 * center_x * cos(i * pi / 180) + center_x
y1 = 0.4 * center_x * sin(i * pi / 180) + center_y
temp = img.copy()
cv2.line(temp, (int(x1), int(y1)), (int(x), int(y)), 255, thickness=2)
freq_list.append((np.sum(temp), i))
#cv2.imshow('get_pointer_angle', temp)
#cv2.waitKey(10)
else:
for i in range(91):
x = center_x - 0.6 * center_x * cos(i * pi / 180)
y = center_y - 0.6 * center_x * sin(i * pi / 180)
temp = img.copy()
cv2.line(temp, (center_x, center_y), (int(x), int(y)), 255, thickness=2)
freq_list.append((np.sum(temp), i))
#cv2.imshow('get_pointer_angle', temp)
#cv2.waitKey(30)
#cv2.destroyAllWindows()
freq = max(freq_list, key = lambda x:x[0])
return freq[1]
#对于一红一黑双指针,先识别出红指针
def get_red_pointer_angle(img, template_type):
center = centers[template_type]
center_x = center[0]
center_y = center[1]
freq_list = []
for i in range(91):
x = center_x - 0.6 * center_x * cos(i * pi / 180)
y = center_y - 0.6 * center_y * sin(i * pi / 180)
temp = img.copy()
cv2.line(temp, (center_x, center_y), (int(x), int(y)), (0, 0, 255), thickness=2)
#cv2.imshow('red_pointer', temp)
#cv2.waitKey(30)
temp = np.sum(temp, axis=0)
temp = np.sum(temp, axis=0)
#获取图片中红色亮度的总和
temp = temp[2]
freq_list.append((np.sum(temp), i))
#cv2.destroyAllWindows()
freq = min(freq_list, key = lambda x:x[0])
red_pointer_angle = freq[1]
return red_pointer_angle
3、表盘读数
根据指针角度求数值
#根据角度和表盘类型,求得指针式仪表盘数值
def get_pointer_meter_value(angle, template_type):
#value是所要求得指针数值,scale_value_down是刚好小于指针数值的表盘刻度数值,scale_value_over是刚好大于指针数值的表盘刻度数值
value = 0
scale_value_down = -1
scale_value_up = 0
#表盘为圆形
if template_type == 0:
if angles[template_type][0] < angle < angles[template_type][1000]:
scale_value_down = 0
scale_value_up = 1000
elif angles[template_type][1000] < angle < angles[template_type][2000]:
scale_value_down = 1000
scale_value_up = 2000
elif angles[template_type][2000] < angle < angles[template_type][3000]:
scale_value_down = 2000
scale_value_up = 3000
elif angles[template_type][3000] < angle < angles[template_type][4000]:
scale_value_down = 3000
scale_value_up = 4000
elif angles[template_type][4000] < angle < angles[template_type][5000]:
scale_value_down = 4000
scale_value_up = 5000
elif angles[template_type][5000] < angle < angles[template_type][6000]:
scale_value_down = 5000
scale_value_up = 6000
elif angles[template_type][7000] < angle < angles[template_type][0]:
return 0
else:
angles_difference_angle = angles[template_type][7000] + 360 - angles[template_type][6000]
if angle > angles[template_type][6000]:
pointer_difference_angle = angle - angles[template_type][6000]
else:
pointer_difference_angle = angle + 360 - angles[template_type][6000]
value = 6000 + 1000 * pointer_difference_angle / angles_difference_angle
return value
#表盘为四分之一圆
else:
for k,v in angles[template_type].items():
if angle < v:
if k==0:
return 0
else:
scale_value_up = k
if scale_value_down != -1:
break;
else:
scale_value_down = k
angles_difference_angle = angles[template_type][scale_value_up] - angles[template_type][scale_value_down] #刻度线间角度差值
pointer_difference_angle = angle - angles[template_type][scale_value_down]#下游刻度线与指针角度的差值
value = scale_value_down + (scale_value_up-scale_value_down) * pointer_difference_angle / angles_difference_angle
return value
提升准确率的方法
- 选用更规范的模板图片
- 选用其他模板匹配方法
- 调整模板图尺寸
- 直线拟合时采用更高精度
- 直线拟合选取更优直线宽度
- 高斯滤波等去噪手段预处理
完整源码地址:https://github.com/frankstorming/meter_reading
参考资料
文章出处登录后可见!
已经登录?立即刷新