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

  • 前言
    • 概述
  • 步骤概括
    • 1.仪表图像预处理
    • 2.刻度线提取
      • 2.1轮廓查找
      • 2.2面积筛选,长宽比,距离
      • 2.3刻度线轮廓拟合直线
    • 3.指针轮廓提取
    • 3.1 霍夫直线检测原理
    • 4.结果
    • 5.Pyqt5
      • 5.1功能
    • 测试
    • 总结
  • 检测程序
    • 使用方法:

前言

目前做了好几个版本,这篇博客是第一个版本,代码比较野生,后面做了重构,精简了代码
2.0精简代码版本地址,纯opencv无GUI

还有用了深度学习来做实例分割再检测的
2.0实例分割版本地址,Pytorch 有GUI 暂时没开源 有定制需要的可以滴滴

本文讲述了指针仪表示数读取的过程与方法,灵感来自实验室的长时间数据记录带来的枯燥,自学了python,Pyqt5,opencv的库,断断续续做了大半年,其实真正着手也没有那么困难,只不过在一些处理方法是来回选择,希望达到更好的效果,由于疫情的影响不能回校,采用深度学习的方法被迫泡汤.

概述

多年以来仪表识别的难点一直存在,摄像直读抄表,俗称“视觉抄表”,是一种通过手机或终端设备对水电气表拍照后利用图像识别算法将仪表照片自动识别为读数的智能抄表方案,具有使用范围广、安装简单、有图有真相、易于使用等特点。仪表表盘图像识别算法是视觉抄表中至关重要的一环,早在21世纪初,便有不少专家学者开始从事这一研究工作。然而,由于当时的算法识别率低、硬件成本高、通信基础设施不完善等诸多原因,视觉抄表一直停滞在研究阶段,并没有大规模普及开来。

随着NB-IoT网络、高性价比芯片等相关技术的发展为之提供了硬件基础,深度学习等图像识别技术的快速发展为之提供了软件基础,视觉抄表这一直观方法重新登上历史舞台,引起了业内人士的广泛关注。快速赋能离线表计,让表网数据更完整,有图有真相的特点彻底解决了买卖双方信用纠纷问题,让决策更可信。如今,在存量市场有绝对优势的视觉抄表方案,毋庸置疑成为了仪表智能化2.0时代不可或缺的产物。

受益于深度学习技术的出现,摄像直读抄表的识别精度相对于本世纪初得到了很大的提升,然而为了实现大规模商业化应用,视觉抄表方案存在大量工程化问题需要解决。例如,摄像终端硬件如何做到低功耗、低成本、高传输成功率、结构高适配,同时还能有效应对恶劣复杂的现场环境;算法识别结果的准确率如何做到保障,如何对异常数据进行快速稽查等等。

一般的,视觉抄表的流程可概括如下:
1、在仪表上外挂式安装拍照采集设备;
2、设置采集终端定期启动拍照;
3、图像通过无线网络上传至服务端;
4、通过图像识别算法,将照片读数转化成数值结果;
5、实现远程抄表、数据分析、收费管理等上层应用服务。
指针仪表1.0版本 -----传统机器视觉

主要环境依赖

开发语言:python 3.6
界面处理库:Pyqt5
图像处理库:opencv
都是用pip安装最新版即可

步骤概括

  1. 零刻度点标注
    1.提取表盘
    2.刻度线轮廓拟合直线,求出交点作为圆心
    3.根据指针轮廓找直线,提取多条直线
    4.多条直线的轮廓拟合成一条直线,即指针
    5.根据分度值求值

1.仪表图像预处理

裁剪出表盘,去除背景

输入图片:
百分表图片

均值滤波+灰度转换+概率霍夫圆检测

dst = cv2.pyrMeanShiftFiltering(input, 10, 100)cimage = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)circles = cv2.HoughCircles(cimage, cv2.HOUGH_GRADIENT, 1, 80, param1=100, param2=20, minRadius=80, maxRadius=0)

创建mask提取圆形区域

circle = np.ones(input.shape, dtype="uint8")
circle = circle * 255
cv2.circle(circle, (c_x, c_y), int(r_1), 0, -1)
bitwiseOr = cv2.bitwise_or(input, circle)

裁剪后的表盘

  1. 难点:概率霍夫圆检测参数要多改变数值去试

–标准霍夫圆检测

霍夫圆变换的基本思路是认为图像上每一个非零像素点都有可能是一个潜在的圆上的一点,跟霍夫线变换一样,也是通过投票,生成累积坐标平面,设置一个累积权重来定位圆。
在笛卡尔坐标系中圆的方程为:

(x−a)2+(y−b)2=r2\left ( x-a \right )^{2}+\left ( y-b \right )^{2}=r^{2} (x−a)2+(y−b)2=r2
其中(a,b)是圆心,r是半径,也可以表述为:
x=a+rcosθy=b+rsinθx=a+rcos\theta \quad y=b+rsin\theta x=a+rcosθy=b+rsinθ

a=x−rcosθb=y−rsinθa=x-rcos\theta \quad b=y-rsin\theta a=x−rcosθb=y−rsinθ
所以在abr组成的三维坐标系中,一个点可以唯一确定一个圆。
而在笛卡尔的xy坐标系中经过某一点的所有圆映射到abr坐标系中就是一条三维的曲线:经过xy坐标系中所有的非零像素点的所有圆就构成了abr坐标系中很多条三维的曲线。
在xy坐标系中同一个圆上的所有点的圆方程是一样的,它们映射到abr坐标系中的是同一个点,所以在abr坐标系中该点就应该有圆的总像素N0个曲线相交。通过判断abr中每一点的相交(累积)数量,大于一定阈值的点就认为是圆。
–Opencv霍夫圆变换
Opencv霍夫圆变换对标准霍夫圆变换做了运算上的优化。它采用的是“霍夫梯度法”。它的检测思路是去遍历累加所有非零点对应的圆心,对圆心进行考量。圆心一定是在圆上的每个点的模向量上,即在垂直于该点并且经过该点的切线的垂直线上,这些圆上的模向量的交点就是圆心。

霍夫梯度法就是要去查找这些圆心,根据该“圆心”上模向量相交数量的多少,根据阈值进行最终的判断。

 HoughCircles(image, method, dp, minDist, circles=None, param1=None, param2=None, minRadius=None, maxRadius=None)
1.image:输入图像 (灰度图)2.method:指定检测方法. 现在OpenCV中只有霍夫梯度法,加快速度3.dp:累加器图像的反比分辨=1,默认即可4.minDist =80 检测到圆心之间的最小距离,这是一个经验值。这个大了,那么多个圆就是被认为一个圆。5.param_1 = 100: Canny边缘函数的高阈值6.param_2 = 20: 圆心检测阈值.根据你的图像中的圆大小设置,当这张图片中的圆越小,那么此值就设置应该被设置越小。当设置的越小,那么检测出的圆越多,在检测较大的圆时则会产生很多噪声。所以要根据检测圆的大小变化。7.min_radius = 80: 能检测到的最小圆半径, 默认为0.8.max_radius = 0: 能检测到的最大圆半径, 默认为0

2.刻度线提取

通过轮廓查找,可以将所有黑色部分(刻度线,指针,干扰点)区域找出,根据刻度线的特点从以下几个方面讨论:
距离:刻度线中心点在半径r范围附近
长宽比:刻度线是细长区域,长宽比例为矩形,达到1:4以上
面积:通过上述筛选,对选出轮廓进行面积统计,刻度线面积占大多数,取统计中值附近进行二次筛选

2.1轮廓查找

img = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# cv2.imshow('dds', img)
# ret, binary = cv2.threshold(~gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
binary = cv2.adaptiveThreshold(~gray, 255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, -10)  #二值化aa, contours, hier = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)#轮廓查找

aa, contours, hier =cv2.findContours()在新版本的opencv中可能只返回两个参数,可以去掉aa,改为contours, hier =cv2.findContours()

2.2面积筛选,长宽比,距离

 for xx in contours:rect = cv2.minAreaRect(xx)# print(rect)a, b, c = rectw, h = bw = int(w)h = int(h)''' 满足条件:“长宽比例”,“面积”'''if h == 0 or w == 0:passelse:dis = mential.ds_ofpoint(self=0, a=ca, b=a)if (incircle[0] < dis and incircle[1] > dis):#距离localtion.append(dis)if h / w > 4 or w / h > 4: #长宽比例cntset.append(xx)#刻度线轮廓cntareas.append(w * h)else:if w > r_1 / 2 or h > r_1 / 2:needlecnt.append(xx)#指针轮廓needleareas.append(w * h)
    cntareas = np.array(cntareas)nss = remove_diff(cntareas)  # 中位数,上限区new_cntset = []for i, xx in enumerate(cntset): #面积筛选if (cntareas[i] <= nss * 1.5 and cntareas[i] >= nss * 0.8):new_cntset.append(xx)

2.3刻度线轮廓拟合直线

刻度线拟合

    for xx in new_cntset:rect = cv2.minAreaRect(xx)box = cv2.boxPoints(rect)box = np.int0(box)cv2.polylines(img, [box], True, (0, 255, 0), 1)  # picoutput = cv2.fitLine(xx, 2, 0, 0.001, 0.001)k = output[1] / output[0]k = round(k[0], 2)b = output[3] - k * output[2]b = round(b[0], 2)x1 = 1x2 = gray.shape[0]y1 = int(k * x1 + b)y2 = int(k * x2 + b)cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 1)kb.append([k, b])  # 求中心点的点集[k,b]


再由以下函数求圆心

    point_list = findpoint(kb,path) #kb是线集cx, cy = countpoint(point_list,path)

将线集随机抽取一半分成两个部分,求两部分线集的交点,储存到point_list

def findpoint(kb,path):img = cv2.imread(path)w, h, c = img.shapepoint_list = []if len(kb) > 2:random.shuffle(kb)lkb = int(len(kb) / 2)kb1 = kb[0:lkb]kb2 = kb[lkb:(2 * lkb)]# print('len', len(kb1), len(kb2))kb1sample = sample(kb1, int(len(kb1) / 2))kb2sample = sample(kb2, int(len(kb2) / 2))else:kb1sample = kb[0]kb2sample = kb[1]for i, wx in enumerate(kb1sample):# for wy in kb2:for wy in kb2sample:k1, b1 = wxk2, b2 = wy# print('kkkbbbb',k1[0],b1[0],k2[0],b2[0])# k1-->[123]try:if (b2 - b1) == 0:b2 = b2 - 0.1if (k1 - k2) == 0:k1 = k1 - 0.1x = (b2 - b1) / (k1 - k2)y = k1 * x + b1x = int(round(x))y = int(round(y))except:x = (b2 - b1 - 0.01) / (k1 - k2 + 0.01)y = k1 * x + b1x = int(round(x))y = int(round(y))# x,y=solve_point(k1, b1, k2, b2)if x < 0 or y < 0 or x > w or y > h:breakpoint_list.append([x, y])cv2.circle(img, (x, y), 2, (122, 22, 0), 2)# print('point_list',point_list)if len(kb) > 2:# cv2.imshow(pname+'_pointset',img)cv2.imwrite(pname + '_pointset' + ptype, img)return point_list

输入点集,创建一个图像大小的二维0数组,二维数组中点的位置加1,最后找值最大的点,即为圆心

def countpoint(pointlist,path):# pointlist=[[1,2],[36,78],[36,77],[300,300],[300,300]]img = cv2.imread(path, 0)h, w = img.shapepic_list = np.zeros((h, w))for point in pointlist:# print('point',point)x, y = pointif x < w and y < h:pic_list[y][x] += 1# print(pic_list)cc = np.where(pic_list == np.max(pic_list))# print(cc,len(cc))y, x = cccc = (x[0], y[0])cv2.circle(img, cc, 2, (32, 3, 240), 3)# cv2.imshow(pname + '_center_point', img)cv2.imwrite(pname + '_center_point' + ptype, img)return cc


统计分布最多的点,为下图黑点

3.指针轮廓提取

去除掉刻度线和杂点后,剩余的轮廓只含有刻度线和圆盘
此时可以用直接使用霍夫直线检测,但是圆盘可能会存在一部分干扰,可用预处理中的mask方法去掉圆盘。
2.刻度线提取中已经将刻度线提取出来,剩下的包换指针区域

3.1 霍夫直线检测原理

Hough直线检测的基本原理在于利用点与线的对偶性,在我们的直线检测任务中,即图像空间中的直线与参数空间中的点是一一对应的,参数空间中的直线与图像空间中的点也是一一对应的。这意味着我们可以得出两个非常有用的结论:
1)图像空间中的每条直线在参数空间中都对应着单独一个点来表示;
2)图像空间中的直线上任何一部分线段在参数空间对应的是同一个点。
因此Hough直线检测算法就是把在图像空间中的直线检测问题转换到参数空间中对点的检测问题,通过在参数空间里寻找峰值来完成直线检测任务。

霍夫变换运用两个坐标空间之间的变换,将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题
霍夫变换直线检测(Line Detection)原理及示例

    circle = np.zeros(img.shape, dtype="uint8")cv2.circle(circle, (cx, cy), int(r), 255, -1)mask = cv2.bitwise_and(img, circle)# cv2.imshow('m', mask)kernel = np.ones((3, 3), np.uint8)mask = cv2.dilate(mask, kernel, iterations=1)# erosion = cv2.erode(mask, kernel, iterations=1)# cv2.imshow('1big', mask)lines = cv2.HoughLinesP(mask, 1, np.pi / 180, 100, minLineLength=int(r / 2), maxLineGap=2)nmask = np.zeros(img.shape, np.uint8)# lines = mential.findline(self=0, cp=[x, y], lines=lines)# print('lens', len(lines))for line in lines:x1, y1, x2, y2 = line[0]cv2.line(nmask, (x1, y1), (x2, y2), 100, 1, cv2.LINE_AA)

再查找直线轮廓,指针细化,找指针的骨架

    aa, cnts, hier = cv2.findContours(nmask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)areass = [cv2.contourArea(x) for x in cnts]# print(len(areass))i = areass.index(max(areass))# print('contours[i]',contours[i])# cv2.drawContours(img, contours[i], -1, (10,20,250), 1)# cv2.imshow('need_next', img)cnt = cnts[i]output = cv2.fitLine(cnt, 2, 0, 0.001, 0.001)k = output[1] / output[0]k = round(k[0], 2)b = output[3] - k * output[2]b = round(b[0], 2)x1 = cxx2 = axit[0]y1 = int(k * x1 + b)y2 = int(k * x2 + b)cv2.line(oimg, (x1, y1), (x2, y2), (0, 23, 255), 1, cv2.LINE_AA)cv2.line(oimg, (x1, y1), (x0,y0), (0, 23, 255), 1, cv2.LINE_AA)cv2.circle(oimg, (x1,y1), 2, (0, 123, 255), -1)# cv2.imshow('msss', oimg)

4.结果


角度:11.332886626198873
时间:0:00:00.506669

5.Pyqt5

界面设计就没什么好说了

5.1功能

1.从摄像头读取照片
2.设置记录的时间和间隔
3.生成csv表格
4.可视化折线图



初略效果图

测试



偏差在0.2左右,主要是中心点在变动

总结

自学的python,很多代码不扎实,不太会写,有很多冗余的步骤,由于window中的pyhton,opencv库也不是编译的,效率较低,还有很多优化的空间。

检测程序

更新:由于GUI界面操作不够人性化,特意改了一个主程序版本,去掉了PYQT界面,只需要改动最后的图片路径即可
主程序代码见 github

使用方法:

  1. 运行程序

  2. 点击0刻度位置

  3. 图片关闭,等待结果

  4. 查看输出文件夹可以看到处理过程

if __name__=="__main__":# 输入文件夹,改变图片路径即可inputpath='input/2.jpg'# 输出文件夹outputpath='output'p0=markzero(inputpath)ang1 =decter(inputpath,outputpath,p0)print(ang1)

有GUI代码见 github

指针式仪表的自动读数与识别相关推荐

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

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

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

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

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

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

  4. 指针式仪表自动读数与识别(九):多仪表自动读数

    前面的几篇文章是针对单个仪表的读数,本片文章是针对于多个仪表的读数.考虑到由于有些仪表并不止一个表盘,所以在摄像头采集到的图像中,一张图像会 包含多个表盘,若是用多个摄像头去拍摄则得不偿失,所以我们使 ...

  5. 指针式仪表自动读数与识别(二):仪表图像预处理

    一.仪表图像预处理 1.预处理操作 在做任何图像处理相关操作之前都要先进行预处理.预处理操作包括: (1) 缩放和变换 缩放操作的主要目的是减小图像大小,减少计算量,缩放操作不是必须的,但是如果系统对 ...

  6. 指针式仪表自动读数与识别(四):非圆形表盘定位

    基于RSCD的非圆形表盘定位 非圆形表盘外观一般为方形,常见于电流表和电压表.这些仪表没有明显的圆形表盘,因此无法通过直接Hough圆检测来定位表盘圆.观察仪表特点,可以发现虽然表盘不是圆形,但是表盘 ...

  7. 指针式仪表自动读数与识别(三):圆形表盘定位

    Hough圆检测及其常用优化 针对圆形仪表来说,表盘定位常用的方法是Hough圆检测. Hough圆检测原理如下: 引用自百度百科: 通过在参数空间里进行简单的累加统计,然后在Hough参数空间寻找累 ...

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

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

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

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

最新文章

  1. h264中profile和level的含义
  2. java web service_怎样新建一个Java的Web Service
  3. java 正则 任意字符_Java正则表达式 去掉括号内任意字符
  4. sqlite3API函数
  5. BM16 删除有序链表中重复的元素-II
  6. 物理磁盘空间使用已满导致数据库hang起
  7. Angular 依赖注入里factory函数的调用时机
  8. java中的内部类总结
  9. 读者诉苦:Redis 宕机,数据丢了,老板要辞退我
  10. VMware虚拟机三种网络模式的区别
  11. mysql关联语句优化_MySql语句关联优化问题,为什么加了限制条件反而更慢?
  12. 发布Drools Workbench到Tomcat on Linux
  13. jQueryQQ音乐动态轮播图
  14. BT5新的征程!全民***计划!
  15. c语言经纬度和大地坐标转换,经纬度坐标与大地坐标转换表
  16. SELECT FOR ALL ENTRIES IN 通过配置优化速度
  17. html中table的样式设置
  18. vue脚手架的作用是什么?
  19. CSS : 七彩呼吸灯
  20. 离“失业”还有多远?机器人流程自动化是怎样改变人类生活的?

热门文章

  1. 香港十大黄金交易公司2019最新排名
  2. Unity傻瓜式 安卓打包 安卓编译 unity安卓编译
  3. Redis入门到实战
  4. openssl在java端的加解密和签名验证
  5. 算法第四版习题解答(1.2 Data Abstraction)
  6. 用CS3817自己做个功放(附原理图)
  7. 英文写作中的最常见“十大句式”
  8. 北航操作系统课程-20200402课堂小测-调度算法
  9. AD无法打开 报错ad please wait a moment:解决方法
  10. Linux 安装与配置服务器版jre7