
二维码的图像的典型特征有:
1.二维码是用特定的几何图形按一定规律在平面上分布的黑白相间的图形,利用黑色像素代表二进制 “1”,白色像素代表 “0”,通过黑白像素点的排列来记录数据信息。
2.定位信息:常见的 QR 码有三个形状相同的位置探测图形,分别位于二维码符号的左上角、右上角和左下角,每个位置探测图形由 3 个重叠的同心正方形组成,其模块宽度比为 1:1:3:1:1,用于帮助扫描设备快速定位和识别二维码。

也就是说,二维码的定位块呈现出“回字形”特征,由外而内为黑-白-黑的分布,均为正方形,切宽度比为7 : 5: 3

3.以 QR 码为例,其符号规格从版本 1(21×21 模块)到版本 40(177×177 模块),每提高一个版本,每边增加 4 个模块,可根据实际需求选择不同的规格来存储不同量的数据。
4.同样的内容,反复生成的二维码图案可能不一样,这是由于其特定的几何图形是按照一定规律随机分布在平面上的,而且调整纠错率时,二维码的图案分布也会重新调整。
所以我们可以考虑利用二维码左上、左下、右上的定位点的几何特点,判断某区域内是否存在二维码:
不过印刷质量不佳时,二维码的内部方块的边缘会出现明显的锯齿,会对这种方法的结果产生较大的干扰。所以想通过轮廓的矩形检测,判断内侧与外侧的正方形的颜色,宽度比来判断是否存在二维码,可能还需要更进一步的研究。

一种参考:
import cv2
import numpy as np
import math
def preprocess_image(img):
"""预处理:灰度化、二值化、去噪"""
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)
# 形态学操作去除小噪点
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
return thresh
def is_square(contour, eps=0.05):
"""判断轮廓是否为正方形(边长比接近1,且为凸多边形)"""
perimeter = cv2.arcLength(contour, True)
if perimeter == 0:
return False
approx = cv2.approxPolyDP(contour, eps * perimeter, True)
# 正方形需4个顶点,且凸性良好,宽高比接近1
return (len(approx) == 4 and
cv2.isContourConvex(approx) and
0.9 <= (cv2.boundingRect(approx)[2] / cv2.boundingRect(approx)[3]) <= 1.1)
def check_position_pattern(roi):
"""验证区域是否为二维码定位点(回字形结构)"""
h, w = roi.shape[:2]
if h != w: # 必须为正方形
return False
# 缩放为固定尺寸(便于比例计算)
size = 20 # 缩小到20x20像素
roi_resized = cv2.resize(roi, (size, size))
_, roi_bin = cv2.threshold(roi_resized, 127, 255, cv2.THRESH_BINARY) # 确保二值化
# 定位点结构:外层黑框(占比~70%)、中层白框(占比~50%)、中心黑方块(占比~30%)
outer_ratio = np.sum(roi_bin == 0) / (size * size) # 黑色像素占比(外层)
if not (0.6 <= outer_ratio <= 0.8): # 外层黑框占比约60%-80%
return False
# 裁剪中间区域(去掉外层10%边框)
inner_size = int(size * 0.8)
inner_roi = roi_bin[int(size*0.1):int(size*0.9), int(size*0.1):int(size*0.9)]
inner_white_ratio = np.sum(inner_roi == 255) / (inner_size * inner_size) # 中层白框占比
if not (0.4 <= inner_white_ratio <= 0.6): # 中层白框占比约40%-60%
return False
# 裁剪中心区域(去掉中层后剩余30%)
center_size = int(size * 0.3)
center_roi = roi_bin[int(size*0.35):int(size*0.65), int(size*0.35):int(size*0.65)]
center_black_ratio = np.sum(center_roi == 0) / (center_size * center_size) # 中心黑方块占比
return 0.7 <= center_black_ratio <= 1.0 # 中心几乎全黑
def check_geometry(points):
"""验证三个定位点的几何分布(近似直角三角形)"""
if len(points) != 3:
return False
# 计算三点之间的距离
d1 = math.hypot(points[0][0] - points[1][0], points[0][1] - points[1][1])
d2 = math.hypot(points[1][0] - points[2][0], points[1][1] - points[2][1])
d3 = math.hypot(points[0][0] - points[2][0], points[0][1] - points[2][1])
dists = sorted([d1, d2, d3])
# 验证勾股定理(直角三角形):a² + b² ≈ c²(误差允许15%)
a, b, c = dists
if not (0.85 * c**2 <= a**2 + b**2 <= 1.15 * c**2):
return False
# 验证最长边与最短边比例(二维码定位点距离比例通常在1.5-3之间)
if not (1.5 <= c / a <= 3.0):
return False
return True
def is_qrcode_region(img):
"""判断输入图块是否为二维码区域"""
if img is None or img.size == 0:
return False, []
thresh = preprocess_image(img)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
position_points = []
# 筛选候选定位点
for cnt in contours:
if is_square(cnt):
x, y, w, h = cv2.boundingRect(cnt)
roi = thresh[y:y+h, x:x+w] # 裁剪候选区域
if check_position_pattern(roi):
# 记录定位点中心坐标
center = (x + w//2, y + h//2)
position_points.append(center)
# 绘制候选定位点(调试用)
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
# 必须检测到3个定位点,且几何分布符合要求
if len(position_points) == 3 and check_geometry(position_points):
return True, position_points
else:
return False, position_points
# 测试代码
if __name__ == "__main__":
img = cv2.imread("qrcode_test.jpg") # 输入待检测的图块
is_qr, points = is_qrcode_region(img)
if is_qr:
print("该图块是二维码区域")
# 绘制定位点中心
for (x, y) in points:
cv2.circle(img, (x, y), 5, (0, 0, 255), -1)
else:
print("该图块不是二维码区域")
cv2.imshow("Result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。