对于下图中的黄色十字丝,如何定位黄色十字丝的中心位置坐标

期望得到以下结果:

对于摆得很正的十字有特殊的处理方法,即使用kernel形状为十字的形态学操作来预处理,可以直接得到十字的中心。
因为形态学操作的kernel中的非0像素,本质上是用于指定每个滑窗中需要求最大或最小值的像素的位置范围的,这些非0像素在kernel中展现的形状可以是矩形,可以是椭圆,当然,也可是十字。

图1:十字kernel形态学腐蚀
上图是用9*9的十字kernel来进行腐蚀操作得到的结果图,可以看到最大的十字的中心处留下了一个比较大的亮点。
下一步只要按照简单的斑点分析的套路就可以找到这个最大的亮点

图2:斑点分析找到的大亮点
回原图看就是下面这样

图3:原图上的位置到这里,可以把大十字的中心成功找到了。
不过有网友说,横跨整个屏幕的十字标尺上的所有小刻度的十字交叉点也要找到,那也没问题。
回去观察上面的图1,其实细心的你应该已经看出来了,那张图上已经把所有小刻度的十字交叉点的中心增强出来了,局部放大后看上面的图2就能看到很多离散点的点,我标记了一下,如下图

图4:蓝色为待定位小十字交叉点,红色为可能的干扰点
但是,图4中也可以看出,存在可能的干扰点,不过干扰区面积较大,使用斑点面积或长宽的取值范围即可过滤。最终可以得到下图

图5:最终结果
下面是基于OpenCV的样例代码:
import cv2
import numpy as np
# 1. 读取RGB彩色图像
img = cv2.imread('input.jpeg')
if img is None:
print("无法读取图像")
exit()
# 2. 应用3x3中值滤波
denoised_img = cv2.medianBlur(img, 3)
# 3. 提取绿色通道
g_img = denoised_img[:, :, 1]
# 4. 创建十字形9x9结构元素并进行腐蚀操作
kernel_cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (9, 9))
cross_eroded_img = cv2.erode(g_img, kernel_cross)
# 5. 处理mask0(阈值17)
mask0 = (cross_eroded_img > 17).astype(np.uint8) * 255
contours_mask0, _ = cv2.findContours(mask0, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours_mask0:
if len(cnt) < 3: # 至少需要3个点才能计算最小外接矩形
continue
# 计算最小外接矩形
rect = cv2.minAreaRect(cnt)
(center), (width, height), _ = rect
# 计算长轴和短轴
major_axis = max(width, height)
minor_axis = min(width, height)
# 筛选长轴和短轴在1~7之间的轮廓
if 1 <= major_axis <= 7 and 1 <= minor_axis <= 7:
x, y = map(int, center)
# 用红色十字标记中心点
cv2.drawMarker(img, (x, y), (0, 0, 255), cv2.MARKER_CROSS, 10, 2)
# 6. 处理mask1(阈值100)
mask1 = (cross_eroded_img > 100).astype(np.uint8) * 255
contours_mask1, _ = cv2.findContours(mask1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours_mask1:
area = cv2.contourArea(cnt)
if area > 10:
# 计算轮廓质心
M = cv2.moments(cnt)
if M['m00'] != 0:
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
# 用蓝色十字标记中心点
cv2.drawMarker(img, (cx, cy), (255, 0, 0), cv2.MARKER_CROSS, 10, 2)
# 保存并显示结果
cv2.imwrite('output.jpg', img)
cv2.imshow('Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
上面这个方法毕竟还是基于斑点分析的套路来做的,输出的坐标不是非常精确,无法保证定位结果一定是十字交叉线的中心。
如果期望更加精确的话,还需要在找到的每个位置附近做X和Y两个方向的投影找极值,或者基于边缘查找直线之类的思路,确定具有亚像素精度的十字中心。
在我们的灵闪机器视觉软件中,有成熟的十字定位算法。我们可以在上面所述的斑点分析方法得到每个十字的粗定位坐标之后,在局部用专用的十字定位算法来实现精定位,这个方案我已经配置好了,Task可以点击下载
这个Task中我配置了3种不同的方法,代表3种不同的处理思路:
斑点分析的思路和OpenCV的实现本质上是一样的。正常运行之后会得到和图5一样的结果。
在方法1的基础之上,使用循环工具遍历斑点定位得到的所有坐标,并在每个坐标附近根据预设的ROI大小来使用十字定位算法,即可得到亚像素精度的十字定位结果,如下图

图6:对比斑点定位结果(颜色较淡)和亚像素十字定位结果(颜色较深)的精度
从图6的对比可以看出,显然十字定位的结果会更加精准,斑点定位的结果还是有比较显著的像素级别的偏移的。
形态学 Hit-Miss 的方法在这个例子中,只是为了多提一种方法供参考。
一般的项目中很少使用到形态学 Hit-Miss 算法,但它作为一个非常经典的模板匹配算法,虽然限制也比较多,例如必须输入二值图和分开设定两个二值图模板,但是,在一些简单例子中,还是有用的,特别是它的耗时稳定且很低。

图7:形态学Hit-Miss的两个核
本质上它试图通过Hit核和Miss核的拆分,实现有一定容忍度的二值图形模板匹配。从应用角度来理解,它的Hit核定义的是你认为必须能够匹配上的“最小”形状(二值图中的白色像素),通常要设定得比你要匹配的形状更细,它的Miss核定义的是你认为必定不能匹配上的“最小”形状(二值图中的黑色像素),反过来也可以说Miss核定义的是必须匹配上的黑色像素定义的“最小”形状。
由于背后的实现其实还是腐蚀膨胀操作的组合,所以,使用得当的情况下,它的执行效率非常高,在这个图上,我的测试结果是0.2ms(i7 12700)。
以上Task在灵闪3.8.77中测试通过,但由于只使用了非常基础的算法,在3.8的其他版本中应该也不会有太大问题。