无需cv2.QRCodeDetector/pyzbar的OpenCV二维码检测方案求助
二维码检测实现方案(基于cv2.connectedComponentsWithStats)
核心问题说明
原有方案的核心缺陷是未利用二维码的固有特征做筛选:
- 基于连通域的方案仅过滤了连通域的宽高比和面积,没有匹配二维码三个定位角点的嵌套方块特征,容易把与二维码连通的周边区域误识别
- 基于轮廓的方案闭运算迭代次数过高,破坏了二维码的内部纹理结构,导致无法提取有效轮廓
实现思路
优先使用cv2.connectedComponentsWithStats实现,逻辑如下:
- 基础预处理:灰度化、高斯模糊去噪、OTSU二值化得到高对比度的二值图
- 遍历所有连通域,筛选符合二维码定位角点特征的连通域:定位角点为三层嵌套的黑白黑方块,宽高比接近1,面积在合理范围内
- 收集到≥3个符合要求的定位角点后,计算所有定位点的最小外接矩形,即为二维码的准确区域
- 自动适配不同尺寸的输入图,无需手动设置缩放比例
完整可运行代码
import cv2 import numpy as np def detect_qr_code(image_path): img = cv2.imread(image_path) if img is None: return None, None og = img.copy() # 预处理流程 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5,5), 0) thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] # 提取所有连通域 num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(thresh, 8, cv2.CV_32S) markers = [] for i in range(1, num_labels): x = stats[i, cv2.CC_STAT_LEFT] y = stats[i, cv2.CC_STAT_TOP] w = stats[i, cv2.CC_STAT_WIDTH] h = stats[i, cv2.CC_STAT_HEIGHT] area = stats[i, cv2.CC_STAT_AREA] ratio = w / float(h) # 初筛:接近正方形,面积在合理区间 if not (0.8 < ratio < 1.2 and 20 < area < 0.2 * img.shape[0] * img.shape[1]): continue # 验证定位角点的嵌套特征:二维码角点为7x7的黑-白-黑嵌套结构 roi = thresh[y:y+h, x:x+w] small_roi = cv2.resize(roi, (7,7), interpolation=cv2.INTER_NEAREST) expected_pattern = np.array([ [255,255,255,255,255,255,255], [255,0,0,0,0,0,255], [255,0,255,255,255,0,255], [255,0,255,255,255,0,255], [255,0,255,255,255,0,255], [255,0,0,0,0,0,255], [255,255,255,255,255,255,255] ], dtype=np.uint8) # 特征匹配度阈值设为0.7,兼容模糊、变形的二维码 match_score = np.sum(small_roi == expected_pattern) / 49 if match_score > 0.7: markers.append((x, y, w, h)) # 二维码至少有3个定位角点 if len(markers) >=3: # 提取所有定位点的四角坐标求最小外接矩形 points = [] for (x,y,w,h) in markers: points.append((x, y)) points.append((x+w, y)) points.append((x, y+h)) points.append((x+w, y+h)) points = np.array(points) x_min, y_min = np.min(points[:,0]), np.min(points[:,1]) x_max, y_max = np.max(points[:,0]), np.max(points[:,1]) # 加少量padding避免切到二维码边缘 pad = 5 x_min = max(0, x_min - pad) y_min = max(0, y_min - pad) x_max = min(img.shape[1], x_max + pad) y_max = min(img.shape[0], y_max + pad) # 绘制检测框、裁剪ROI cv2.rectangle(img, (x_min, y_min), (x_max, y_max), (0,255,0), 2) qr_roi = og[y_min:y_max, x_min:x_max] return img, qr_roi return img, None if __name__ == "__main__": # 替换为你本地的图片路径即可测试 res_img, qr_roi = detect_qr_code("你的测试图片路径.jpg") if qr_roi is not None: cv2.imshow("检测结果", res_img) cv2.imshow("二维码区域", qr_roi) cv2.imwrite("qr_roi.png", qr_roi) cv2.waitKey(0) cv2.destroyAllWindows()
内容的提问来源于stack exchange,提问作者Filipe Almeida
相关产品推荐
相关产品推荐

