OpenCV Python中如何区分圆形轮廓与字母/数字轮廓?
问题原因
你当前通过approxPolyDP拟合轮廓后判断边数的筛选逻辑鲁棒性极低:字母、数字中的类封闭曲线(如0、O、Q)、边缘带毛刺的轮廓,拟合后的边数很容易落在你设置的8-23区间内,自然会出现大量误检。
高鲁棒性圆形筛选方案
组合使用以下几何特征约束,基本可以完全区分目标圆形与字母/数字轮廓,不需要复杂的模型方案:
- 圆度校验(核心判定条件):正圆的圆度值为1,越接近1说明轮廓形状越接近正圆,计算公式为
圆度 = 4 * π * 轮廓面积 / (轮廓周长的平方)。你场景里的规整填涂类圆形,圆度普遍在0.85以上;而字母、数字哪怕是类圆形的0、O,受字体设计、笔画粗细变化影响,圆度基本都在0.7以下,设置0.8的阈值即可筛掉90%以上的误检。 - 宽高比校验:正圆的最小外接旋转矩形为正方形,宽高比为1。计算每个轮廓的最小外接旋转矩形,要求宽高比在0.9~1.1区间内,即可直接排除扁形、长条形的字符轮廓。
- 面积范围约束:你当前仅设置了面积大于30的阈值,过于宽松。提前统计图中目标圆形的实际像素面积,设置合理的上下限(比如目标圆面积集中在100-200像素区间,就仅保留该范围的轮廓),过大、过小的干扰轮廓会被快速排除,字符轮廓的面积普遍远小于目标填涂圆,这一步可筛掉大半干扰。
- 凸性校验:正圆是完全凸的形状,凸包面积与轮廓自身面积的比值接近1。而B、8、D这类带凹陷的字符,凸性值会明显低于0.9,增加该条件可进一步筛掉带内凹的非圆轮廓。
如果是固定机位拍摄的固定版式场景(比如答题卡识别),还可以提前定位到填涂区域的ROI后再做轮廓检测,直接避开题号、字符印刷区域,准确率会进一步提升,几乎不会出现误检。
改进后代码
import cv2 import math cnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) contour_list = [] # 以下阈值可根据你的实际图像调整 MIN_AREA = 80 MAX_AREA = 300 MIN_CIRCULARITY = 0.8 MIN_ASPECT_RATIO = 0.9 MAX_ASPECT_RATIO = 1.1 MIN_CONVEXITY = 0.9 for contour in cnts: area = cv2.contourArea(contour) # 优先筛面积,计算最快,提前排除小干扰 if area < MIN_AREA or area > MAX_AREA: continue perimeter = cv2.arcLength(contour, True) if perimeter == 0: continue # 圆度校验 circularity = 4 * math.pi * area / (perimeter ** 2) if circularity < MIN_CIRCULARITY: continue # 外接旋转矩形宽高比校验 rect = cv2.minAreaRect(contour) w, h = rect[1] if h == 0 or w == 0: continue aspect_ratio = w / h if w > h else h / w if aspect_ratio < MIN_ASPECT_RATIO or aspect_ratio > MAX_ASPECT_RATIO: continue # 凸性校验 hull = cv2.convexHull(contour) hull_area = cv2.contourArea(hull) if hull_area == 0: continue convexity = area / hull_area if convexity < MIN_CONVEXITY: continue contour_list.append(contour) mcq = cv2.drawContours(paper, contour_list, -1, (255, 0, 0), 1) cv2.imshow("thresh", thresh) cv2.imshow("mcq", mcq) cv2.waitKey(0)
内容的提问来源于stack exchange,提问作者Joshua Yang
相关产品推荐
相关产品推荐

