前段时间参加了一个表盘指针读数的比赛,今天来总结一下

数据集一共有一千张图片:

方法一:径向灰度求和

基本原理

将图像以表盘圆心转换成极坐标,然后通过矩阵按行求和找到二值图最大值即为指针尖端

导入需要用到的包

import cv2 as cv
import numpy as np
import math
from matplotlib import pyplot as plt
import os

图像预处理

去除背景:利用提取红色实现

def extract_red(image):"""通过红色过滤提取出指针"""red_lower1 = np.array([0, 43, 46])red_upper1 = np.array([10, 255, 255])red_lower2 = np.array([156, 43, 46])red_upper2 = np.array([180, 255, 255])dst = cv.cvtColor(image, cv.COLOR_BGR2HSV)mask1 = cv.inRange(dst, lowerb=red_lower1, upperb=red_upper1)mask2 = cv.inRange(dst, lowerb=red_lower2, upperb=red_upper2)mask = cv.add(mask1, mask2)return mask

获得钟表中心:轮廓查找,取出轮廓的外接矩形,根据矩形面积找出圆心

def get_center(image):"""获取钟表中心""" edg_output = cv.Canny(image, 100, 150, 2) # canny算子提取边缘cv.imshow('dsd', edg_output)# 获取图片轮廓contours, hireachy = cv.findContours(edg_output, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)center = []cut=[0, 0]for i, contour in enumerate(contours):x, y, w, h = cv.boundingRect(contour)  # 外接矩形area = w * h  # 面积if area < 100 or area > 4000:continuecv.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 1)cx = w / 2cy = h / 2cv.circle(image, (np.int(x + cx), np.int(y + cy)), 1, (255, 0, 0))  ## 在图上标出圆心center = [np.int(x + cx), np.int(y + cy)]breakreturn center[::-1]

由上面的图像可以看出,圆心定位还是非常准确的

图片裁剪

def ChangeImage(image):"""图像裁剪"""# 指针提取mask = extract_red(image)mask = cv.medianBlur(mask,ksize=5)#去噪# 获取中心center = get_center(mask)# 去除多余黑色边框[y, x] = centercut = mask[y-300:y+300, x-300:x+300]# 因为mask处理后已经是二值图像,故不用转化为灰度图像return cut

剪裁后的图像如下图所示:

极坐标转换

注意:需要将图片裁剪成正方形

def polar(image):"""转换成极坐标"""x, y = 300, 300maxRadius = 300*math.sqrt(2)linear_polar = cv.linearPolar(image, (y, x), maxRadius, cv.WARP_FILL_OUTLIERS + cv.INTER_LINEAR)mypolar = linear_polar.copy()#将图片调整为从0度开始mypolar[:150, :] = linear_polar[450:, :]mypolar[150:, :] = linear_polar[:450, :]cv.imshow("linear_polar", linear_polar)cv.imshow("mypolar", mypolar)return mypolar


由图像就可以很容易发现指针的顶点

计算角度

def Get_Reading(sumdata):"""读数并输出"""peak = []# s记录遍历时波是否在上升s = sumdata[0] < sumdata[1]for i in range(599):# 上升阶段if s==True and sumdata[i] > sumdata[i+1] and sumdata[i] > 70000:peak.append(sumdata[i])s=False# 下降阶段if s==False and sumdata[i] < sumdata[i+1]:s=Truepeak.sort()a = sumdata[0]b = sumdata[-1]if not peak or max(a,b) > peak[-1]:peak.append(max(a,b))longindex = (sumdata.index(peak[-1]))%599longnum = (longindex + 1)//25*50# 先初始化和长的同一刻度#shortindex = longindexshortnum = round(longindex / 6)try:shortindex = sumdata.index(peak[-2])shortnum = round(shortindex / 6)except IndexError:i=0while i<300:i += 1l = sumdata[(longindex-i)%600]r = sumdata[(longindex+i)%600]possibleshort = max(l,r)# 在短指针可能范围内寻找插值符合条件的值if possibleshort > 80000:continueelif possibleshort < 60000:breakelse:if abs(l-r) > 17800:shortindex = sumdata.index(possibleshort) - 1shortnum = round(shortindex / 6)breakreturn [longnum,shortnum%100]
def test():"""RGS法测试"""image = cv.imread("./BONC/1_{0:0>4d}".format(400) + ".jpg")newimg = ChangeImage(image)polarimg = polar(newimg)psum = polarimg.sum(axis=1, dtype = 'int32')result = Get_Reading(list(psum))print(result)
if __name__ == "__main__":test()k = cv.waitKey(0)if k == 27:cv.destroyAllWindows()elif k == ord('s'):cv.imwrite('new.jpg', src)cv.destroyAllWindows()
[1050, 44]

方法二:Hough直线检测

原理:利用Hough变换检测出指针的两条边,从而两条边的中线角度即为指针刻度

数据预处理与上面的方法类似

可以看到分别检测出了两个指针的左右两条边,然后可以由这四个角度算出两个指针中线的角度,具体计算过程写的有点复杂

class Apparatus:def __init__(self, name):self.name = nameself.angle = []self.src = cv.imread(name)def line_detect_possible_demo(self, image, center, tg):''':param image: 二值图:param center: 圆心:param tg: 直线检测maxLineGap'''res = {} # 存放线段的斜率和信息edges = cv.Canny(image, 50, 150, apertureSize=7)cv.imshow("abcdefg", edges)lines = cv.HoughLinesP(edges, 1, np.pi/360, 13, minLineLength=20, maxLineGap=tg)for line in lines:x_1, y_1, x_2, y_2 = line[0]# 将坐标原点移动到圆心x1 = x_1 - center[0]y1 = center[1] - y_1x2 = x_2 - center[0]y2 = center[1] - y_2# 计算斜率if x2 - x1 == 0:k = float('inf')else:k = (y2-y1)/(x2-x1)d1 = np.sqrt(max(abs(x2), abs(x1)) ** 2 + (max(abs(y2), abs(y1))) ** 2)  # 线段长度d2 = np.sqrt(min(abs(x2), abs(x1)) ** 2 + (min(abs(y2), abs(y1))) ** 2)# 将长指针与短指针做标记if d1 < 155 and d1 > 148 and d2 > 115:res[k] = [1]elif d1 < 110 and d1 > 100 and d2 > 75:res[k] = [2]else:continueres[k].append(1) if (x2 + x1) /2 > 0 else res[k].append(0)  # 将14象限与23象限分离cv.line(self.src, (x1 + center[0], center[1] - y1), (x2 + center[0],  center[1] - y2), (255, 0, 0), 1)cv.imshow("line_detect-posssible_demo", self.src)# 计算线段中点的梯度来判断是指针的左侧线段还是右侧线段middle_x = int((x_1 + x_2) / 2)middle_y = int((y_1 + y_2) / 2)grad_mat = image[middle_y-5:middle_y+6, middle_x-5:middle_x+6]cv.imshow("grad_mat", grad_mat)grad_x = cv.Sobel(grad_mat, cv.CV_32F, 1, 0)grad_y = cv.Sobel(grad_mat, cv.CV_32F, 0, 1)gradx = np.max(grad_x) if np.max(grad_x) != 0 else np.min(grad_x)grady = np.max(grad_y) if np.max(grad_y) != 0 else np.min(grad_y)if ((gradx >=0 and grady >= 0) or (gradx <= 0 and grady >= 0)) and res[k][1] == 1:res[k].append(1)  # 右测elif ((gradx <= 0 and grady <= 0) or (gradx >= 0 and grady <= 0)) and res[k][1] == 0:res[k].append(1)else:res[k].append(0) # 左侧# 计算角度angle1 = [i for i in res if res[i][0] == 1]angle2 = [i for i in res if res[i][0] == 2]# 长指针a = np.arctan(angle1[0])b = np.arctan(angle1[1])if a * b < 0 and max(abs(a), abs(b)) > np.pi / 4:if a + b < 0:self.angle.append(math.degrees(-(a + b) / 2)) if res[angle1[1]][1] == 1 else self.angle.append(math.degrees(-(a + b) / 2) + 180)else:self.angle.append(math.degrees(np.pi - (a + b) / 2)) if res[angle1[1]][1] == 1 else self.angle.append(math.degrees(np.pi - (a + b) / 2) + 180)else:self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2)) if res[angle1[1]][1] == 1 else self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2) + 180)print('长指针读数:%f' % self.angle[0])# 短指针a = np.arctan(angle2[0])b = np.arctan(angle2[1])if a * b < 0 and max(abs(a), abs(b)) > np.pi / 4:if a + b < 0:self.angle.append(math.degrees(-(a + b) / 2)) if res[angle2[1]][1] == 1 else self.angle.append(math.degrees(-(a + b) / 2) + 180)else:self.angle.append(math.degrees(np.pi - (a + b) / 2)) if res[angle2[1]][1] == 1 else self.angle.append(math.degrees(np.pi - (a + b) / 2) + 180)else:self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2)) if res[angle2[1]][1] == 1 else self.angle.append(math.degrees(np.pi / 2 - (a + b) / 2) + 180)print('短指针读数:%f' % self.angle[1])def get_center(self, mask):edg_output = cv.Canny(mask, 66, 150, 2)cv.imshow('edg', edg_output)# 外接矩形contours, hireachy = cv.findContours(edg_output, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)center = []for i, contour in enumerate(contours):x, y, w, h = cv.boundingRect(contour)  # 外接矩形area = w * h  # 面积if area > 1000 or area < 40:continue#print(area)# cv.circle(src, (np.int(cx), np.int(cy)), 3, (255), -1)cv.rectangle(self.src, (x, y), (x + w, y + h), (255, 0, 0), 1)cx = w / 2cy = h / 2cv.circle(self.src, (np.int(x + cx), np.int(y + cy)), 1, (255, 0, 0))center.extend([np.int(x + cx), np.int(y + cy)])breakcv.imshow('center', self.src)return centerdef extract(self, image):red_lower1 = np.array([0, 43, 46])red_lower2 = np.array([156, 43, 46])red_upper1 = np.array([10, 255, 255])red_upper2 = np.array([180, 255, 255])frame = cv.cvtColor(image, cv.COLOR_BGR2HSV)mask1 = cv.inRange(frame, lowerb=red_lower1, upperb=red_upper1)mask2 = cv.inRange(frame, lowerb=red_lower2, upperb=red_upper2)mask = cv.add(mask1, mask2)mask = cv.bitwise_not(mask)cv.imshow('mask', mask)return maskdef test(self):self.src = cv.resize(self.src, dsize=None, fx=0.5, fy=0.5)  # 此处可以修改插值方式interpolationmask = self.extract(self.src)mask = cv.medianBlur(mask, ksize=5)  # 去噪# 获取中心center = self.get_center(mask)# 去除多余黑色边框[y, x] = centermask = mask[x - 155:x + 155, y - 155:y + 155]cv.imshow('mask', mask)#self.find_short(center, mask)try:self.line_detect_possible_demo(mask, center, 20)except IndexError:try:self.src = cv.imread(self.name)self.src = cv.resize(self.src, dsize=None, fx=0.5, fy=0.5)  # 此处可以修改插值方式interpolationself.src = cv.convertScaleAbs(self.src, alpha=1.4, beta=0)blur = cv.pyrMeanShiftFiltering(self.src, 10, 17)mask = self.extract(blur)self.line_detect_possible_demo(mask, center, 20)except IndexError:self.src = cv.imread(self.name)self.src = cv.resize(self.src, dsize=None, fx=0.5, fy=0.5)  # 此处可以修改插值方式interpolationself.src = cv.normalize(self.src, dst=None, alpha=200, beta=10, norm_type=cv.NORM_MINMAX)blur = cv.pyrMeanShiftFiltering(self.src, 10, 17)mask = self.extract(blur)self.line_detect_possible_demo(mask, center, 20)if __name__ == '__main__':apparatus = Apparatus('./BONC/1_0555.jpg')# 读取图片apparatus.test()k = cv.waitKey(0)if k == 27:cv.destroyAllWindows()elif k == ord('s'):cv.imwrite('new.jpg', apparatus.src)cv.destroyAllWindows()
长指针读数:77.070291
短指针读数:218.896747

由结果可以看出精确度还是挺高的,但是这种方法有三个缺点:

  • 当两个指针重合时候不太好处理
  • 有时候hough直线检测只能检测出箭头的一条边,这时候就会报错,可以利用图像增强、角点检测和图像梯度来辅助解决,但是效果都不太好
  • 计算角度很复杂!!(也可能是我想复杂了,不过这段代码确实花了大量时间)

代码里可能还有很多问题,希望大家多多指出

OpenCV 表盘指针自动读数相关推荐

  1. python 仪表盘图片读数_OpenCV 表盘指针自动读数的示例代码

    前段时间参加了一个表盘指针读数的比赛,今天来总结一下 数据集一共有一千张图片: 方法一:径向灰度求和 基本原理: 将图像以表盘圆心转换成极坐标,然后通过矩阵按行求和找到二值图最大值即为指针尖端 导入需 ...

  2. Python基于OpenCV的指针式表盘检测系统(附带源码&技术文档)

    1.背景 指针式机械表盘具有安装维护方便.结构简单.防电磁干扰等诸多优点, 目前广泛应用于工矿企业.能源及计量等部门.随着仪表数量的增加及精密仪表技术的发展,人工判读已经不能满足实际应用需求.随着计算 ...

  3. PYTHON+YOLOV5+OPENCV,实现数字仪表自动读数,并将读数结果进行输出显示和保存

    最近完成了一个项目,利用python+yolov5实现数字仪表的自动读数,并将读数结果进行输出和保存,现在完成的7788了,写个文档记录一下,若需要数据集和源代码可以私信. 最后实现的结果如下: 项目 ...

  4. 指针式仪表的自动读数与识别

    指针式仪表的自动读数与识别 前言 概述 步骤概括 1.仪表图像预处理 2.刻度线提取 2.1轮廓查找 2.2面积筛选,长宽比,距离 2.3刻度线轮廓拟合直线 3.指针轮廓提取 3.1 霍夫直线检测原理 ...

  5. 指针式仪表自动读数与识别(一)

    前言的前言 因原个人博客废弃,不再维护,防止文章丢失,遂迁移至此. 鉴于大家对源码的需求较多,遂将源码上传.源码地址见文末. 前言 本系列文章是关于"指针式仪表的自动读数与识别", ...

  6. MATLAB指针式仪表自动读数系统设计

    一.课题介绍 随着模式识别技术.计算机技术等多种技术的不断完善和发展,机器视觉获得了巨大的进步与发展.目前在许多企业中,存在着大量的仪表,仪表的读数都要靠人来完成,工作量很大而且误差率相对来说比较高, ...

  7. 指针式仪表自动读数与识别(八):仪表自动读数系统设计与开发

    序 前面几篇文章都是偏理论的,这篇文章则是偏实践的,本文使用C#+EmguCV开发一个仪表自动读数系统,目前该系统能够识别圆形的温度表.气压表以及方形的电流.电压表,误差控制在0.1%左右. 系统概述 ...

  8. 指针式仪表自动读数与识别(五):刻度线定位与拟合

    刻度拟合 刻度在仪表自动读数中并不作为计算依据(起始和终止刻度除外),最终读数仅仅依赖指针.表盘位置以及量程,因此在求仪表刻度线时可以允许少量误差,这些误差不会对最终结果造成影响. 对于刻度线的拟合, ...

  9. RotateAnimation 实现表盘指针转动

    RotateAnimation 实现表盘指针转动 最近在做车助手这个项目,遇到这样的一个功能需求:获取车子启动的实时数据让指针转动: 我这里做了一个Demo:demo的原理在于使用onTouchEve ...

  10. 【机器视觉学习笔记】python安装OpenCV并设置自动补全及代码提示

    目录 安装 测试 设置自动补全及代码提示 平台:Windows 10 20H2 Python 3.8.12 (default, Oct 12 2021, 03:01:40) [MSC v.1916 6 ...

最新文章

  1. 四十一、文件的物理结构(上)
  2. 第三十一次发博不知道用什么标题好
  3. J-LINK不能烧写(错误:JLink Warning: RESET (pin 15) high, but should be low. Please check target)
  4. maven打包报错You have to use a classifier to attach supplemental artifacts to the project instead of rep
  5. linux 用脚本建分区,脚本创建磁盘分区
  6. NLP简报(Issue#7)
  7. robotframework 接口测试 +RSA 加密
  8. springboot基于javaweb的课堂考勤系统设计与实现毕业设计源码142335
  9. 【牛腩新闻发布系统】--初识牛腩
  10. Tableau 发布到tableau online错误
  11. Vue小说阅读器(仿追书神器)
  12. Oracle中表pagesize,Oracle使用pagesize命令
  13. 第四周作业-多线程编程
  14. 区块链学习——HyperLedger-Fabric v0.6环境搭建详细过程
  15. idea2020版本无法使用actiBPM插件问题
  16. mongodb关机重启
  17. UE4插件与编辑器Slate
  18. 一文了解常用效率记笔记软件
  19. Bucket、Hash Chain List
  20. MySql数据库中,表字段新增,修改,删除

热门文章

  1. 跟键盘在一起时间比女朋友还长? 程序员如何选好一块专属键盘?
  2. 2048版俄罗斯方块java_Java版俄罗斯方块
  3. python基本类型关键字_python基本类型关键字_python中的关键字---1(基础数据类)...
  4. 数据结构——绪论、时间复杂度
  5. 微云为您讲述陌陌是如何布局“智慧商城”
  6. .NET Standard中配置TargetFrameworks输出多版本类库
  7. C++ 常函数和常对象
  8. OpenJudge 百练 2787 算24
  9. 外星人入侵游戏(Python3)
  10. 学习日记12(类和对象)