目的是统计出上图贴纸中的小圆。
相关参数:
摄像头:OV2640
开发平台:ESP32-CAM
处理像素:320*240
1、采集原始图像
配置ESP32输出灰度图,像素太高无法存储及处理。320*240= 75K。
2、图像梯度计算
参考代码:C语言绘制图像梯度图
原理介绍:图像的梯度
计算图像梯度前先简单做个滤波处理,测试过3*3均值滤波和3*3中值滤波,中值滤波比较耗时,在ESP32平台上处理一张320*240的灰度图,需要耗费500ms左右,果断放弃。取舍选择了均值滤波,效果会有些许差异,没做深入比较。
参考代码里面是RGB三色做图像梯度,我们灰度只有一个灰度值。
//图像梯度计算
for (i = 1; i < HEIGHT - 1; i++)
{
for (j = 1; j < WIDTH - 1; j++)
{
int a1 = p2[i - 1][j] - p2[i][j];
int a2 = p2[i][j - 1] - p2[i][j];
p[i][j] = SqrtByCarmack(a1 * a1 + a2 * a2);
}
}
SqrtByCarmack 为一个快速开根函数,因为我们精度没有必要太高。
处理后的图像还需要进一步二值化处理,提供进一步的轮廓提取。如下图。图像梯度真的很神奇,把模糊变化的像素点直接抹去。
我作为二值化化的值并不大,根据实际的测试结果手动选取的,比较理想。二值化值为6
3、内外轮廓跟踪
这部分也是重中之重,潜心研究了许久。
参考博客:[Suzuki85]轮廓跟踪算法论文翻译
初次接触图像处理,必先知道openCV这个好东西。因为在嵌入式单片机上是无法移植openCV这么庞大的开源库的。便想着去实现它。轮廓查找及大体思路参考上述博客,里面提及的算法正是openCV的实现原理。不过我并未全部沿用,也并非全部搞懂。层包含的关系我没有深入研究。只关心外轮廓和内轮廓的关系。
大体思路我记录一下:
- 查找并追踪外轮廓,外轮廓的依据就是nbd由0变为1的点,作为外轮廓的起始点。追逐方式采用8邻域逆时针查找,细节参考论文。逐一查找到轮廓的起始点或者孤立点,轮廓查找结束。查找到的轮廓点需要一个新标记 nbd置为2。
- 查找内边界。内边界的依据就是nbd由1到0的变化,极为内边界。跟踪方式为8领域顺时针查找,找到的轮廓用nbd=3标记。找到起始点或者孤立点轮廓查找结束。
- 如下图,红色为外轮廓 蓝色为内轮廓。
做梦都会有这样的一副画面,自己变成图上的一个小人,在每个像素点上跳跃,走迷宫一样的追逐着图上的轮廓,直到回到轮廓的起点。想象都很有趣。
4、内轮廓提取
直接沿用上图的点。
5、类圆轮廓提取(红点为拟合圆心)
最终得到很多轮廓。如何确定一个轮廓是圆呢?这又是我纠结了许久的问题。采用圆拟合还是Hough变换?对单片机而言都是比较奢侈的,就算实现了也得算很久。于是我只能自求门路。
- 圆的面积可以求出直径,圆的周长也可以求出直径,那两个直径约越相近是不是说明圆的相似度越高呢
- 几何图形中,相同的周长,圆的面积是最大的
- 横坐标平均值和纵坐标的平均值就是圆心坐标。
以上3点,即可判断轮廓是不是一个圆,且得轮廓的 面积、周长、直径、圆心,以及拟合圆的拟合度。
果断搞起来。
- 那么,首先得到一个轮廓的面积,那么积分这东西就有用了,扫描的每一行的累加和,就是封闭轮廓的面积。
- 其次,如何得到轮廓的长度呢,点与点的距离的累加和不就是轮廓的长度嘛。
- 其中,轮廓的点折叠效果会增大轮廓的长度,所以,尽可能一行只取最靠边界的两个像素点,减小长度累计误差。都是小修补,可视情况而定。
- 由面积通过圆公式求出直径d1,轮廓长度通过圆周长公式计算直径d2. d1和d2的偏差就是圆的判断依据。
手绘圆实景处理
>circle:0 d:49.98 (64,30) e:0.03
>circle:1 d:6.38 (60,4) e:0.15
>circle:2 d:35.59 (91,256) e:0.06
>circle:3 d:43.42 (110,104) e:0.03
>circle:4 d:53.95 (128,172) e:0.04
>circle:5 d:39.51 (179,239) e:0.03
>circle:6 d:14.14 (185,290) e:0.01
>circle:7 d:27.34 (215,161) e:0.02
>circle end 8