刻度识别项目
-
- 简介
- 实现效果
- 实现流程
- 实现原理
-
- 刻度尺的标定
-
- 指针位置标定
- 读数位置标定
- 刻度线标定
- 数字检测
- 数字分割
- 数字识别
- web部署
- 附录
简介
好久不见了,我看了一下我最近的博客到现在已经3个月时间没更新了。这是因为我最近都在忙毕业设计,所以一直没有时间更新。我这三个月的收获还是丰富的,最近会慢慢通过博客和大家分享。现在马上水上一篇吧。如果大家有看过我的EAST文本检测器应用这一篇博客的话,可能会好奇我最后的gif demo是怎么完成的。那个其实是我大三的一个srp项目。现在我就介绍一下它。
实现效果
需求不用啰嗦,直接看看效果即可,其实我也是做得比较简单,还有很多可以改进的地方。
1. demo1
2. demo2
实现流程
我完成上述的基本功能主要包含以下即可步骤。
- 刻度尺的标定
- 数字位置的检测和分割
- 数字识别
- 结果可视化
- 部署到web
其中,✔的是必须的,而其余的是个性化的可选项。
实现原理
刻度尺的标定
刻度尺的标定是非常重要的一环。考虑到刻度尺位置是相对固定的,因此通过简单的人工标定过程就可以知道指针、刻度和数字的相对位置。当然也可以用如今非常厉害的基于深度学习的目标检测技术,大家可以自行发挥。
下面是标定的示意图:
从demo中可以看到整个读数系统可以象形地作成上图。需要关注的是:1)指针的位置;2)刻度尺读数的位置,我们不妨假定我们可以框出数字,那么框框的中心坐标就是读数的位置;3)读数到指针所指位置的距离;首先数字的起始位置都是有一条粗黑实线,然后得到的实线到指针的像素距离需要转换成实际的刻度距离。
指针位置标定非常简单,因为其一般呈现等腰三角形,我们把它的边缘绘制出来,然后求其角平分线,就得到指针的指向。
我的标定程序需要从上往下按顺序指定三个红点,指定后自动连接绿线(指针边缘),然后自动生成其角平分线(蓝线),整个程序是交互式的,执行过程有提示信息,最终标定文件会自动保存,下次使用时加载标定文件即可。
假定我们得到离指针位置最近的数字的中心像素坐标,但我们还需要知道这个像素坐标离它低下那条粗黑实线有多远。在这里,我假定这个中心坐标的y坐标y c y_c yc和粗实线所在的y坐标y b y_b yb存在二次函数的关系,这其实并不严格,只是感觉。所以有:
y b = a y c 2 + b y c + c y_b = ay_c^2 + by_c + c yb=ayc2+byc+c
这样,我们对多张图片的多个位置标定出( y c , y b ) (y_c, y_b) (yc,yb),我们就可以拟合出系数。
程序需要人工框出所有数字,会自动返回红色中心点;然后人工点出黑色实线(蓝点),程序采集足够多组样本之后就可以拟合,生成拟合曲线:
这个拟合关系还是不错的。
我们现在可以推出起始的粗实线的坐标,我们还需要知道:1)指针指向和刻度尺的交点(结束坐标);2)像素距离到刻度尺距离的转换。首先,我们先标定刻度,如下所示:
按从下到上或相反的顺序点出刻度,而且尽量使点排成大致的直线,这是因为在采集多张图以后,程序会把这个直线拟合出来。拟合出的刻度直线,和最开始标定出来的指针的角平分线求交点,得到的y轴方向坐标就是结束坐标。
有开始坐标和结束坐标,就还剩下距离转换的问题了。在这里我也是在此假设,任一y轴坐标y 0 y_0 y0,它的上一个刻度和下一个刻度的像素增量δ y ↑ , δ y ↓ \delta y_{\uparrow}, \delta y_{\downarrow} δy↑,δy↓也存在二次函数的关系,即:
δ y ↑ = a ↑ y 0 2 + b ↑ y 0 + c ↑ δ y ↓ = a ↓ y 0 2 + b ↓ y 0 + c ↓ \delta y_{\uparrow} = a_{\uparrow}y_0^2 + b_{\uparrow}y_0 + c_{\uparrow} \\ \delta y_{\downarrow} = a_{\downarrow}y_0^2 + b_{\downarrow}y_0 + c_{\downarrow} \\ δy↑=a↑y02+b↑y0+c↑δy↓=a↓y02+b↓y0+c↓
和上面一样,采集足够的样本之后拟合,拟合效果如下:
可见效果还是可以的。拟合之后,假定我们起始坐标和结束坐标为y s , y e y_s,y_e ys,ye。那么我们可以求出y s y_s ys下一个刻度的像素坐标:
y s , 1 = y s + δ y s = a ↓ y s 2 + ( b ↓ + 1 ) y s + c ↓ y_{s,1} = y_s + \delta y_s = a_{\downarrow}y_s^2 + (b_{\downarrow}+1)y_s + c_{\downarrow} ys,1=ys+δys=a↓ys2+(b↓+1)ys+c↓
不断重复上述过程,我们可以求出毫米级读数y s , k ≤ y e ≤ y s , k + 1 y_{s,k} \le y_e \le y_{s,k+1} ys,k≤ye≤ys,k+1。如果需要取两位小数,那么在这个区间等比例取就可以了。
数字检测
数字检测使用我之前的博客提到的EAST模型,里面详细介绍了如何配置和使用,模型输入原图片,返回数字的中心坐标。
数字分割
本人数字分割使用的同样是比较传统的方法。首先,将图像转化为灰度图,然后使用OTSU方法对图片进行二值化。最后,对二值化的图片使用MSER算法,这个算法可以有效地抠出连通域。这样的话我们就得到单个分割的数字了。
mser = cv2.MSER_create(_min_area=min_area, _max_area=max_area)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转为灰度图
_, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 二值化
_, boxes = self.mser.detectRegions(img) # MSER分割
数字识别
容易发现,我们需要识别的数字都是印刷体的。所以,数据集只需要印刷体数字0~9即可,你可能会觉得找数据集是件麻烦事,但印刷体数据集是可以用opencv生成的。这一部分我参考了开源项目digitx,它非常详细地给出了生成数据集和数据增强的方案,并且训练了一个简单的CNN实现了高效的数字识别。
web部署
本人使用flask实现,但使用本地电脑作为服务器部署后只能供局域网访问,如果需要让国内的朋友都能访问的话,可以申请一个公网的服务器,比如阿里云等,然后把代码放到上面运行,别人就可以访问了。
附录
完整代码见:github
如有问题,可以加下面二维码咨询