使用OpenCV实现图像盲文点检测的问题排查与优化
板材表面盲文点位检测优化方案
现存问题
当前实现了两套盲文点位检测方案,均存在明显漏检、误检,无法达到生产使用要求:
- 方案1:自适应阈值+连通域检测,反复调整参数仍无法过滤所有非盲文噪点,同时存在真点漏检
- 方案2:模板匹配+非极大值抑制,检测效果完全依赖匹配阈值,参数敏感度高,准确率不稳定
此前尝试的圆检测方案也存在相同的参数适配问题。
方案1实现代码
import matplotlib.pyplot as plt img1 = cv.imread(r"traitement\prod.png") plt.figure(figsize=(40,40)) plt.subplot(3,1,1) gray_img = cv.cvtColor(img1, cv.COLOR_BGR2GRAY) test = cv.adaptiveThreshold(gray_img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY_INV, 11, 6) _, _, boxes, _ = cv.connectedComponentsWithStats(test) boxes = boxes[1:] filtered_boxes = [] for x,y,w,h,pixels in boxes: if pixels < 1000 and h < 35 and w < 35 and h > 14 and w > 14 and x > 15 and y > 15: filtered_boxes.append((x,y,w,h)) for x,y,w,h in filtered_boxes: W = int(w)/2 H = int(h)/2 cv.circle(img1,(x+int(W),y+int(H)),2,(0,255,0),20) cv.imwrite("gray.png",gray_img) cv.imwrite("test.png",test) plt.imshow(test) plt.subplot(3,1,2) plt.imshow(img1)
方案2实现代码
import cv2 as cv import numpy as np from imutils.object_detection import non_max_suppression import matplotlib.pyplot as plt img = cv.imread('traitement/prod.png') temp_gray = cv.imread('dot.png',0) W, H = temp.shape[:2] thresh = 0.6 img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) match = cv.matchTemplate(image=img_gray, templ=temp_gray, method=cv.TM_CCOEFF_NORMED) (y_points, x_points) = np.where(match >= thresh) boxes = list() for (x, y) in zip(x_points, y_points): boxes.append((x, y, x + W, y + H)) boxes = non_max_suppression(np.array(boxes)) for (x1, y1, x2, y2) in boxes: cv.circle(img,(x1+int(W/2),y1+int(H/2)),2,(255,0,0),15) plt.figure(figsize=(40,40)) plt.subplot(3,1,1) plt.imshow(img)
当前检测效果
- 自适应阈值连通域方案效果:

- 模板匹配方案效果:

核心问题原因
两套方案效果差的根源不是参数没调好,是前置逻辑缺失、过滤维度太单一:
- 没有做光照和背景纹理预处理:自适应阈值对板材本身的凹凸纹理、局部光照变化非常敏感,二值化后会把大量背景噪点识别为前景,和盲文点混在一起
- 过滤规则只靠宽高、面积:很多背景噪点的尺寸和盲文点接近,单靠尺寸过滤无法区分
- 模板匹配用单模板+固定阈值:盲文点因为按压深度、拍摄角度存在轻微大小、灰度差异,单模板固定阈值很容易漏过变形的真点,同时误匹配纹理相似的噪点
可落地优化方案
优先选择优化后的连通域方案,泛化性比模板匹配更好,参数敏感度更低。
第一步:前置预处理(解决80%的干扰问题)
不要直接对原图做二值化或模板匹配,先做两步处理:
- 顶帽运算:用大小略大于盲文点(比如25*25)的椭圆核做形态学顶帽变换,过滤掉板材表面缓慢变化的光照差、大面积背景纹理,只保留尺寸和核接近的凸起点(也就是盲文点),处理后盲文点灰度会被统一增强,背景整体压暗
- 高斯模糊:用3*3核做高斯模糊,去掉顶帽处理后残留的细碎高频噪点,注意不要用均值模糊,会模糊盲文点边缘影响后续判定
第二步:优化二值化与连通域过滤规则
- 二值化改用自动全局阈值:顶帽处理后背景灰度已经非常均匀,不需要用自适应阈值,阈值取处理后图像的灰度均值+3倍标准差即可,不需要手动反复调整
- 连通域过滤新增两个形状维度判定,筛掉90%以上的误检:
- 宽高比过滤:真实盲文点接近正圆,宽高比范围控制在0.8~1.2,长条、扁形噪点直接剔除
- 圆度过滤:对每个连通域计算圆度(公式:
圆度 = 4π * 连通域面积 / 连通域周长²),圆度大于0.7的才保留,不规则形状的噪点会被全部过滤
- 可选网格校验(零误检):盲文点位本身是按固定行列间距规则排布的,检测完所有候选点后,通过霍夫直线检测找出点位排布的行列线,只有落在网格交叉点邻域内的点才判定为真点,偏离网格的孤立点直接剔除,可以完全消除零星误检
模板匹配方案备选优化点
如果要继续用模板匹配,做两处修改即可大幅提升准确率:
- 多尺度模板匹配:把原模板按0.9、1.0、1.1三个比例缩放生成多套模板,分别做匹配后合并所有候选框,再做非极大值抑制,解决盲文点大小偏差带来的漏检
- 替换固定阈值为局部最大值筛选:不再用全局阈值卡结果,只取每个局部邻域内匹配得分最高的点作为候选,再结合上面提到的形状规则、网格规则做二次校验,大幅降低阈值敏感度
优化后连通域方案核心代码参考
import cv2 as cv import numpy as np import matplotlib.pyplot as plt img1 = cv.imread(r"traitement\prod.png") gray_img = cv.cvtColor(img1, cv.COLOR_BGR2GRAY) # 顶帽运算增强盲文点 kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (25, 25)) tophat = cv.morphologyEx(gray_img, cv.MORPH_TOPHAT, kernel) # 高斯模糊去噪 blur = cv.GaussianBlur(tophat, (3, 3), 0) # 自动计算全局阈值 thresh_val = blur.mean() + 3 * blur.std() _, binary = cv.threshold(blur, thresh_val, 255, cv.THRESH_BINARY) # 连通域检测 num_labels, labels, stats, centroids = cv.connectedComponentsWithStats(binary, connectivity=8) filtered_points = [] for i in range(1, num_labels): x, y, w, h, area = stats[i] # 基础尺寸过滤 if not (14 < w < 35 and 14 < h < 35 and area < 1000 and x > 15 and y > 15): continue # 宽高比过滤 aspect_ratio = w / h if not (0.8 < aspect_ratio < 1.2): continue # 提取连通域真实轮廓计算圆度 mask = (labels == i).astype(np.uint8) contours, _ = cv.findContours(mask, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) if not contours: continue perimeter = cv.arcLength(contours[0], True) if perimeter == 0: continue circularity = 4 * np.pi * area / (perimeter ** 2) if circularity < 0.7: continue # 保留中心点 cx, cy = centroids[i] filtered_points.append((int(cx), int(cy))) # 绘制检测结果 for cx, cy in filtered_points: cv.circle(img1, (cx, cy), 2, (0, 255, 0), 20) # 可视化 plt.figure(figsize=(40, 40)) plt.subplot(3, 1, 1) plt.imshow(binary, cmap='gray') plt.subplot(3, 1, 2) plt.imshow(cv.cvtColor(img1, cv.COLOR_BGR2RGB)) plt.show()
代码里的核大小、圆度阈值、宽高比范围可以根据你实际采集的图像分辨率微调,调整幅度很小,不需要反复试参。如果需要做到100%无漏检误检,最后加一步盲文网格规则校验即可。
内容的提问来源于stack exchange,提问作者Maxime
相关产品推荐
相关产品推荐

