Wechat QRCode 二维码扫描
做OCR 识别时,发现物体标签上有二维码信息,用手机微信是可以扫描出来,但是用pyzbar 却无法识别。搜索后发现这篇文章微信AI设计了一种超分辨率技术,让扫二维码更方便,进一步搜索后发现opencv 中已经集成了该功能。真的是踏破铁鞋无觅处,得来全不费工夫。
理论知识不多赘述,核心的思路是:
- 通过超分辨率模型使得原本模糊的二维码变得清晰可识别
- 通过模型蒸馏,大幅缩小模型体积并提高效率
虽然大模型的准确率高,但是消耗资源多,在边缘计算设备中,蒸馏后的小模型反而更有优势。
安装使用
有时系统会有python-opencv 库,但是因为版本原因可能没有WechatQRCode 模块,这时候需要手动更新库:
bash
# 先卸载opencv 相关库
pip3 uninstall -y opencv-python opencv-contrib-python opencv-python-headless opencv-contrib-python-headless
# 重新安装最新的 opencv-contrib-python
pip3 install opencv-contrib-python安装后还需要下载模型和权重文件,是基于CAFFE框架的,可以通过一些工具转化为torch 或者tf 格式。不过对于一般使用来说不影响。
使用方法如下:
python
import cv2
detector = cv2.wechat_qrcode.WeChatQRCode(
"detect.prototxt",
"detect.caffemodel",
"sr.prototxt",
"sr.caffemodel")
img = cv2.imread("123.png")
res, points = detector.detectAndDecode(img)
print(res, points)
# 得到结果如下(假设包含两个二维码):
# (
# '二维码1 包含的文本',
# '二维码1 包含的文本'
# )
# (
# array([[232.75479 , 88.500145],
# [227.90897 , 19.418858],
# [296.88034 , 21.399696],
# [301.46844 , 89.96361 ]], dtype=float32),
# array([[318.8573 , 805.2406 ],
# [313.44568, 742.3865 ],
# [372.53503, 732.10156],
# [377.7074 , 794.7845 ]], dtype=float32)
# )经过测试,灰度处理后的图像能容易识别出来二维码信息:
python
import cv2
import numpy as np
img = cv2.imread("多个二维码.jpg", cv2.IMREAD_GRAYSCALE)
# 计算图像平均亮度
mean_brightness = np.mean(img)
# 如果平均亮度小于128(偏暗,说明是黑色背景),则进行反色
if mean_brightness < 128:
img = cv2.bitwise_not(img)
print("检测到黑色背景,已进行反色处理")
else:
print("检测到白色背景,保持原样")
# 保存结果图片
# cv2.imwrite("output.png", img)
res, points = detector.detectAndDecode(img)
print(res, points)特殊情况处理
并不是所有图片中的二维码都可以直接被识别,如下图中二维码区域相对较小,有时就无法识别到二维码信息。

这是需要先通过opencv 形态学运算找到可能存在二维码的区域。在OpenCV 的底层逻辑中,它会将(黑色)像素值为0 的区域视为背景,将所有像素值大于0(通常是二值化后的255)的区域视为前景目标。形态学操作的目标是前景色。
python
import cv2
import numpy as np
img = cv2.imread('qrcode-label.png') # 读取图片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转化为灰度图片
_, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY) # 图片二值化
kernels_to_try = [(2,2), (3,3)] # 卷积核,用于把白色区域扩大,黏合成一块
for k in kernels_to_try:
# 创建卷积核,这里用的MORPH_RECT 矩形
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, k)
# 形态学闭运算,先膨胀后腐蚀,可以将白色区域连接起来
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# 白色连通域提取
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(closed)
for i in range(1, num_labels):
# 对于每个连通区域进行判断
x, y, w, h, area = stats[i]
# 计算特征指标
aspect_ratio = float(w) / h
rect_area = w * h
extent = float(area) / rect_area # 填充率:连通域面积占外接矩形面积的比例
# 4. 筛选条件:
# - 面积不能太小 24x24 像素
# - 长宽比接近 1 (正方形)
# - 填充率高 (说明是个实心的矩形块)
if area > 600 and 0.8 < aspect_ratio < 1.2 and extent > 0.7:
# 标记可疑区域
cv2.rectangle(img, (x-5, y-5), (x + w+5, y + h+5), (0, 255, 0), 2)
print(f"检测到潜在区域: [x:{x}, y:{y}, w:{w}, h:{h}], 置信填充率: {extent:.2f}")
cv2.imwrite(f'prep-closed_{i}.png', closed)
cv2.imwrite('prep.png', img)下面是不同卷积核闭运算后得到的结果:
2x2 卷积核效果如下:
3x3 卷积核效果如下(不合格):
最终标记出所有可能的位置:
对于筛选出来的多个可能存在的区域,可以进行合并重叠区域之后,再进行识别。 
然后对于有些二维码是与标准二维码是反色的,还需要进一步进行反色处理。
