一、结论先给
二维码扫不出来,99% 是这五个原因之一:
| 原因 | 症状 | 解法 |
|---|---|---|
| 1. 分辨率不够 | 码在图上太小、放大就糊 | 每个模块至少 3-4 像素 |
| 2. 反光 / 过曝 | 屏幕拍屏有摩尔纹、金属面反光 | 侧角度拍、降低曝光 |
| 3. 对比度不足 | 灰底灰码、浅色码 | 提对比度、二值化 |
| 4. 码制不支持 | 不是 QR(Data Matrix / PDF417 / 一维码) | 换支持多码制的识别器 |
| 5. 损坏超过纠错上限 | 破损、污渍、logo 挡太多 | 换 H 级容错重新生成 |
先判断是哪种,再动手。盲目换工具往往没用。
二、二维码为什么能"部分遮挡也认得"
2.1 三个定位方块
二维码左下、左上、右上各有一个「回」字形方块,这是定位图案。识别器先找到这三个方块确定码的位置和方向,所以二维码斜着、倒着也能扫。
┌─────────────┐
│ ██┌─────┐██ │ ← 左上定位
│ │ ███ │ │
│ └─────┘ │
│ │
│ ██┌──┤ ← 右上定位
│ │ │
│ ██┌─────┐ │ ← 左下定位
└─────────────┘
这解释了两个常见现象:
- 挡住定位方块 → 完全识别不了(哪怕只挡一小角)
- 挡住中间数据区 → 可能还能认(靠纠错)
2.2 纠错等级
二维码有四个纠错等级,生成时可以选:
| 等级 | 可恢复比例 | 典型用途 |
|---|---|---|
| L | 约 7% | 干净环境、要塞很多数据 |
| M | 约 15% | 默认 |
| Q | 约 25% | 户外、易污损 |
| H | 约 30% | 中间要放 logo、印刷质量差 |
中间放 logo 的二维码必须用 H 级(或至少 Q 级)。用默认的 M 级放个大 logo,遮挡超过 15% 就扫不出来了——这是最常见的"自家二维码扫不出"事故。
2.3 版本与容量
QR 码有 40 个版本,版本 n 的尺寸是 (17 + 4n) × (17 + 4n):
| 版本 | 尺寸 | 数字容量(L 级) |
|---|---|---|
| 1 | 21 × 21 | 41 |
| 10 | 57 × 57 | 652 |
| 40 | 177 × 177 | 7089 |
内容越多,码越密,对分辨率的要求越高。一个 URL(30-60 字符)通常在版本 3-5。
三、命令行识别:zbarimg
# 装
sudo apt install zbar-tools # Debian/Ubuntu
brew install zbar # macOS
# 识别
zbarimg --raw -q photo.png
输出直接是解码内容:
https://it997.com/tool/qrcode-reader
3.1 常用参数
# 显示码制
zbarimg photo.png
# 输出:QR-Code:https://...
# 只输出内容(脚本用)
zbarimg --raw -q photo.png
# 关闭所有码制,只认 QR(减少误判)
zbarimg --disable --enable=QR-Code -q photo.png
# 支持 XML 输出(含坐标)
zbarimg --xml photo.png
3.2 zbar 支持的码制
| 类型 | 是否支持 |
|---|---|
| QR Code | ✅ |
| EAN-13 / EAN-8 / UPC-A | ✅ |
| Code 39 / Code 93 / Code 128 | ✅ |
| ISBN-10 / ISBN-13 | ✅ |
| Interleaved 2 of 5 | ✅ |
| Data Matrix | ❌(zbar 老版本不支持,新版部分支持) |
| PDF417 | ❌ |
| Aztec | ❌ |
PDF417(驾驶证、营业执照上的那种长条码)和 Data Matrix(电子元件上的小码)zbar 认不了,要用 zxing。
四、zxing:多码制的备选
# Java 命令行(需要 java)
wget https://repo1.maven.org/maven2/com/google/zxing/javase/3.5.3/javase-3.5.3.jar
wget https://repo1.maven.org/maven2/com/google/zxing/core/3.5.3/core-3.5.3.jar
java -cp "core-3.5.3.jar:javase-3.5.3.jar" com.google.zxing.client.j2se.CommandLineRunner photo.png
支持的码制更全:QR、Data Matrix、PDF417、Aztec、以及多种一维码。
Python 方案(推荐,装起来简单):
from pyzbar.pyzbar import decode
from PIL import Image
import cv2, numpy as np
def read_qr(path):
# 方式 1:pyzbar(快,QR 和一维码)
results = decode(Image.open(path))
for r in results:
print(r.type, r.data.decode('utf-8', errors='replace'))
# 方式 2:OpenCV(支持 detectAndDecode,且自带预处理)
img = cv2.imread(path)
data, pts, _ = cv2.QRCodeDetector().detectAndDecode(img)
if data:
print('OpenCV:', data)
# 方式 3:cv2.wechat_qrcode(微信同款模型,对模糊图效果更好)
detector = cv2.wechat_qrcode_WeChatQRCode(
'detect.prototxt', 'detect.caffemodel', 'sr.prototxt', 'sr.caffemodel')
res, _ = detector.detectAndDecode(img)
print('WeChat:', res)
wechat_qrcode 对模糊、低分辨率的图识别率明显更高(带超分模型),但需要下 4 个模型文件。批量处理模糊图时值得折腾一次。
五、预处理:把"认不出的图"救回来
识别不了时,先处理图再识别,成功率能翻倍。
5.1 放大(分辨率不足)
# 用最近邻放大,别用平滑插值(会把边缘糊掉)
magick photo.png -filter point -resize 400% big.png
zbarimg --raw -q big.png
-filter point(最近邻)是关键:默认的平滑插值会让黑白边界变灰,反而更难识别。
5.2 提对比度
# 自动拉伸对比度
magick photo.png -normalize out.png
# 更强:直方图均衡
magick photo.png -equalize out.png
# 灰度 + 二值化(最有效)
magick photo.png -colorspace Gray -threshold 50% bin.png
zbarimg --raw -q bin.png
5.3 去模糊
# 轻度锐化
magick photo.png -sharpen 0x1.5 out.png
# 反卷积去运动模糊(复杂,但效果明显)
magick photo.png -motion-blur 0x0-8 # 这是加模糊,去模糊用 -deconvolve
magick photo.png -deconvolve kernel.txt out.png
实操经验:二值化 + 最近邻放大这两步能解决大部分问题,去模糊是最后的手段。
5.4 反光 / 摩尔纹(拍屏常见)
# 轻度模糊消除摩尔纹,再锐化
magick photo.png -blur 0x0.8 -sharpen 0x1.5 out.png
或者物理上解决:侧 15-30 度角拍屏幕,正面拍必然反光。
5.5 只截码的部分
码在照片里太小,先裁出来再放大:
magick photo.jpg -crop 400x400+150+300 +repage crop.png
magick crop.png -filter point -resize 400% -colorspace Gray -threshold 50% final.png
zbarimg --raw -q final.png
👉 裁剪可以先用 图片裁剪 框出二维码区域,再识别,比整图识别准得多。
六、安全:扫码前必做的一步
二维码是"不透明的链接",扫之前你看不到它要你访问什么。
真实风险(每年都有案例):
| 风险 | 表现 |
|---|---|
| 钓鱼页 | 扫码跳转到仿冒的登录页/支付页 |
| 自动下载 | 触发 apk/ipa/木马下载 |
| 恶意配置 | 扫码连上恶意 WiFi、加恶意联系人 |
| 支付码替换 | 商家收款码被贴纸覆盖("码上骗") |
| 恶意填充 | 扫码自动填表提交个人信息 |
正确做法:
- 用能显示原文的识别器,先看清楚内容再决定要不要打开
- 内容是 URL 时,检查域名(
pay.example.com和pay-example.com天差地别) - 涉及支付、登录、下载的,手工输入网址,别直接跳转
- 商家静态收款码,付款前确认金额和商户名
- 共享单车、充电桩上的码,被贴纸覆盖的一律不用
👉 二维码识别工具 是先把内容解析出来给你看、再由你决定要不要访问的模式,图片在浏览器本地解码不上传——对不确定的码,这比直接用微信扫要安全。
七、如果你是生成方:让别人一次扫中
7.1 尺寸
| 场景 | 最小尺寸 |
|---|---|
| 屏幕显示 | 码区 ≥ 200 × 200 px |
| 打印(A4 传单) | ≥ 2 × 2 cm |
| 户外海报 | 每米距离 ≥ 1 cm(10 米外扫需 ≥ 10 cm) |
| 名片 | ≥ 1.5 × 1.5 cm |
7.2 静默区
二维码四周必须留至少 4 个模块宽的空白(静默区)。没有静默区,识别器找不到码的边界。
# 生成时带静默区(qrencode 默认 4 模块)
qrencode -o qr.png -s 8 -m 4 "https://it997.com"
# ↑ 静默区
最常见的生成错误:设计稿把二维码紧贴边框或压在背景图上。
7.3 颜色
- 深色的码 + 浅色的底(黑白色最好)
- 不要用反色(白码黑底):大量识别器不认
- 彩色码要保证亮度对比,红底绿码这种亮度接近的组合识别率低
- 渐变背景会让阈值判断失败,尽量用纯色底
7.4 容错等级
| 用途 | 等级 |
|---|---|
| 纯文本/URL,干净环境 | M(默认) |
| 中间要放 logo | H |
| 户外、易磨损 | Q 或 H |
| 内容特别长,塞不下 | L(但要保证环境干净) |
# qrencode 指定容错等级
qrencode -o qr.png -s 8 -l H "https://it997.com"
# ↑ L/M/Q/H
👉 生成用 二维码生成工具,可以直接选容错等级;生成完务必自己扫一遍再印刷。
八、几条纪律
- 先判断失败原因,别急着换工具。
- 预处理顺序:裁剪 → 最近邻放大 → 灰度 → 二值化 → 识别。
- 放大用
-filter point,平滑插值会毁掉边缘。 - 中间放 logo 必须用 H 级容错。
- 生成后一定要留 4 模块静默区,别贴边。
- 别做反色码,很多识别器认不了。
- 扫码先看原文,涉及钱和登录的手工输入网址。