python cv2 指针仪表读数

cv2识别指针式仪表(持续更新)

    • 问题描述
    • 解决方案
    • 效果预览
    • python cv2实现
      • 1、模板匹配
      • 2、直线拟合
      • 3、表盘读数
    • 提升准确率的方法
    • 参考资料


问题描述

最近遇到一个仪表盘读数的问题,主要要识别三种仪表盘
python cv2 指针仪表读数_第1张图片
python cv2 指针仪表读数_第2张图片
python cv2 指针仪表读数_第3张图片
参考了许多博客和论文,打算先用一种传统的方法试一下


解决方案

方案一:传统方法

  1. 模板匹配
  2. 直线拟合
  3. 表盘读数

方案二: 深度学习 (后续实现)

  1. YOLOX等目标检测方法识别表盘
  2. 目标检测方法识别数字、指针、指针旋转原点
  3. 欧式距离求相邻数字
  4. 字符识别模型识别数字
  5. 根据相邻数字求得指针所指数值

效果预览

1、原图
python cv2 指针仪表读数_第4张图片
2、模板图
python cv2 指针仪表读数_第5张图片
3、模板匹配结果
python cv2 指针仪表读数_第6张图片
4、识别结果
python cv2 指针仪表读数_第7张图片
可以看出识别结果会有细微的偏差,但是通过调参可以缩小误差


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:

  1. 模板匹配方法的选取可能对结果产生巨大影响
  2. 模板图片选取十分重要!!

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


提升准确率的方法

  1. 选用更规范的模板图片
  2. 选用其他模板匹配方法
  3. 调整模板图尺寸
  4. 直线拟合时采用更高精度
  5. 直线拟合选取更优直线宽度
  6. 高斯滤波等去噪手段预处理

完整源码地址:https://github.com/frankstorming/meter_reading


参考资料

使用OpenCV进行仪表数值读取

基于深度学习的指针式仪表图像智能读数方法

你可能感兴趣的:(python,计算机视觉,人工智能)