二维码是一张明暗方格组成的图,里面存的是字节,不是链接。识别时要先在图片里找到它,纠正角度和缩放,按正确顺序读出模块,去掉掩码,校验 Reed–Solomon 纠错码,最后交还字节。本文列出的失败,大多落在其中某一步上。

本站的二维码扫描识别在页面里用 zxing-wasm(ZXing C++ 的 WebAssembly 版)跑完这条流程。下面说明图案由什么组成、每个纠错等级能装多少、字符集是怎么定的、Wi-Fi 与名片内容怎么读,以及哪些图片会失败。

图案里有什么:定位、时序、校正与静区

符号结构由 ISO/IEC 18004:2006 规定。除了数据,图上还有几块固定图案:

  • 定位图案。 左上、右上、左下三个 7×7 的方块。识别器靠它们找到符号和旋转角度;右下角没有定位图案,方向就是这样判断的。
  • 时序图案。 第 6 行与第 6 列交替排列的模块,用来数清两个定位图案之间有多少格。
  • 校正图案。 从版本 2 开始出现、分布在符号内部的 5×5 方块,版本 1 没有。照片有透视变形时,靠它们把网格拉正。
  • 格式信息。 纠错等级与掩码方式,写在定位图案附近,存两份,坏掉一份还有一份。
  • 数据与纠错码字。 其余区域,按之字形顺序填入,再与掩码图案异或。

四周还要留出静区:每条边 4 个模块宽的浅色边距(ISO/IEC 18004:2006 §5.3.7)。没有静区,识别器可能把旁边的深色内容当成码的一部分。二维码紧贴文字或照片边缘时读不出,留白之后就正常,原因就在这里。

尺寸按版本计算。版本 1 是 21×21 个模块,每升一版每边加 4 个模块,版本 40 是 177×177。

版本模块数本站生成器里的典型结果
121×21纠错 L 下的大写短网址
225×25纠错 M 下的 https://zerotool.dev
537×37同一张名片的 MECARD 形式
849×49同一张名片的 vCard 3.0 形式
40177×177纠错 L 下的最大容量

四种模式与每个字符的代价

二维码不直接存文字。编码器把内容拆成若干段,每段选一种模式:

模式字符范围代价
数字0–9每 3 个数字 10 位
字母数字0–9、A–Z、空格与 $ % * + - . / :每 2 个字符 11 位
字节任意字节每字节 8 位
汉字JIS X 0208 字符每字 13 位

同一个符号可以在段与段之间切换模式(ISO/IEC 18004:2006 §6.3.6)。所以一条域名大写、路径小写的网址,可能同时用字母数字段和字节段。生成器会选择总位数最少的切分方式,并在状态行里写出来。

二维码生成器的三个实际结果能看出差别:

  • https://zerotool.dev 在纠错 M 下是版本 2、25×25 模块、224 个数据位里用了 172 位(77%),按字节模式存 20 字节。
  • HTTPS://ZEROTOOL.DEV 在纠错 L 下是版本 1、21×21,因为大写字母可以走字母数字模式;小写形式需要版本 2。协议名和主机名不区分大小写(RFC 3986 §3.1、§3.2.2),所以这种替换在第一个 / 之前是安全的;路径和查询串可能区分大小写。
  • 2,400 个字符的纯文本在纠错 M 下需要 19,220 个数据位;版本 40 的 M 级只能放 18,672 位,多出 69 字节,同一段文字在 L 级放得下。生成器不会为了塞进去而截断内容。

中文、日文、韩文通常每个字在 UTF-8 里占 3 字节,基本平面之外的字符占 4 字节。字节模式的 2,953 是字节数,不是字数。

版本、纠错等级与容量

二维码有四个纠错等级,通常按「可以丢掉的码字比例」描述:

等级可恢复的码字,约适用场景
L7%干净的屏幕、短链接
M15%一般默认
Q25%会被摸、会被刮的表面
H30%小尺寸印刷、带 logo、工业标签

纠错码字用 GF(256) 上的 Reed–Solomon 编码算出,再分块交错排列,一道划痕不会毁掉整块。纠错只恢复损坏的码字,不是加密,也救不回超出等级上限的破损。

提高等级会增加纠错码字,同一段文字可能要换更大的版本。符号变密之后,打印或拍照时每个模块需要更多像素,收益可能被抵消。先按表面选等级,再核对实际印刷尺寸。

版本 40、纠错 L 的上限是:

模式容量
数字7,089 位
字母数字4,296 个字符
字节2,953 字节
汉字1,817 个字符

这是单一模式的上限。中英混排会按上面的分段代价计算,通常装得更少。

ECI 与字符集:乱码从哪里来

二维码存的是字节。规范(ISO/IEC 18004)允许用 ECI(Extended Channel Interpretation)头声明字符集。常见的分配号是 ECI 20 对应 Shift_JIS、ECI 26 对应 UTF-8;识别器还认识 Big5、GB2312、GBK、GB18030、EUC-KR 与 ISO-8859 系列。

多数生成器不写 ECI 头。2006 年版标准把 ISO/IEC 8859-1 列为字节模式的默认字符集,而很多编码器直接写入 UTF-8 字节、不做声明。识别器只能按顺序判断:

  1. 有 ECI 头就按声明的字符集解码,并在卡片上标注,例如 UTF-8(ECI 26)。
  2. 没有 ECI 时,合法的 UTF-8 按 UTF-8 读。
  3. 否则先试页面语言对应的旧编码:中文页先试 GB18030,日文页先试 Shift_JIS,韩文页先试 EUC-KR。
  4. 再不行就按 ZXing 的推测规则:有 BOM 按 UTF-16,然后 Shift_JIS,最后 ISO-8859-1。

推测出来的结果会标「推测」。「文本编码」菜单可以把同一串字节按 UTF-8、Shift_JIS、GB18030/GBK、Big5、EUC-KR、ISO-8859-1、UTF-16BE 或 UTF-16LE 重新解一遍。哪种都不是文字的字节会标为「二进制」,直接展开十六进制。

本站实测的两个例子:

  • Shift_JIS 字节的 東京都千代田区丸の内 カフェ 能正确显示,标注「Shift_JIS(推测)」。
  • GBK 字节的 你好,世界。二维码测试 在英文页被推测成 ISO-8859-1(与 ZXing 的行为一致),在菜单里选 GB18030 后恢复正常。

中文码还有一个实际场景:老系统导出的 GBK 文本没有 ECI,扫码 App 按 UTF-8 读就会得到一串问号或方块。先在这里选对编码,确认原文,再决定要不要重新生成一个带 UTF-8 的码。

浏览器怎样读一张图片

页面用的是 zxing-wasm 3.1.4,它把 ZXing C++ 编译成 WebAssembly。脚本与模块从本站的 /vendor 目录加载,不走 CDN,首次使用时下载:gzip 后约 420 KB,未压缩合计约 967 KiB。实际传输量取决于压缩与缓存。

解码在页面里完成。文件、粘贴的截图或摄像头画面,图片字节不会上传到本站。这句话只覆盖解码这条路径:页面同时加载统计与广告,URL 模式还会按你填的地址去取图。哪些请求会离开页面,下面「隐私」一节逐项列出。

识别器对图片做这些事:

  • 一张图最多读 16 个码,按从上到下、从左到右编号,预览图上画的编号与结果卡片一致。
  • 读 QR 系列:QR、Micro QR 和 rMQR。
  • 尝试识别反色码、旋转的码,以及几乎没有白边的码。
  • 按原始分辨率解码,上限 16,777,216 像素;更大的图先缩小,并在状态行注明。浏览器自身的画布限制仍随设备和版本变化。
  • 图片留在本页;摄像头走 getUserMedia,优先后置摄像头,帧在本机解码。

2026-09-30 的实测给出了一个分辨率经验值:33 模块的码缩到 45×45 像素读不出,60×60 可以,约合每个模块 1.5 个图像像素。4000×3000 的手机照片里有一个小码,桌面版 Chromium 约 1.2 秒读出。

Wi-Fi、名片、邮件、短信与位置的解析

解出来的字节是文本,开头几个字符决定扫码 App 怎么处理。识别器按同样的前缀拆字段,不只给一整行字符串:

前缀解析为依据
http:// 或 https://网址,显示真实域名与警告—
WIFI:网络名、密码、加密方式、是否隐藏ZXing: Barcode Contents
BEGIN:VCARDvCard 名片RFC 2426(1998 年 9 月)
MECARD:NTT DOCOMO 的紧凑名片格式DOCOMO 字段表
mailto:收件人、抄送、主题、正文RFC 6068(2010 年 10 月)
tel:电话号码RFC 3966(2004 年 12 月)
sms: 或 SMSTO:号码与短信正文RFC 5724(2010 年 1 月);SMSTO: 见 ZXing
geo:纬度、经度、海拔与地图链接RFC 5870(2010 年 6 月)
其他 scheme:只显示文本,注明需要 App 打开—

Wi-Fi 内容是一行文本:WIFI:T:WPA;S:小王咖啡-访客;P:密码;;。ZXing 用反斜杠转义 \、;、,、" 和 :,Android 的解析代码再把反斜杠去掉。Wi-Fi 联盟后来把同一套 URI 写进 WPA3 规范 v3.5 第 7 节:所有带密码的网络都写 T:WPA,R:1 表示仅 WPA3-Personal。Wi-Fi 二维码生成器按这个格式生成,并在画码前检查字段长度。识别器只拆字段,不会替你连接网络。

名片码存的是一段文本。RFC 2426 规定转义方式,识别器把姓名、公司、电话、邮箱、地址、网址和备注分行列出,再决定要不要存进通讯录。MECARD 更短,没有公司和职位字段。

mailto:、tel:、sms:、geo: 各按自己的 RFC 拆字段;识别器不会打开邮件客户端、拨号、发短信或启动导航。只有 http 和 https 会变成可点的链接,而且必须由你点击。javascript:、data:、vbscript:、file: 一律按文本显示并加警告。

支付二维码也是这个道理。微信支付 Native 下单返回的 code_url(微信支付文档)形如 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00,本页显示为「App 链接」,只给文本;支付宝当面付返回的 qr_code(支付宝文档)形如 https://qr.alipay.com/bavh4wjlxf12tper3a,显示为网址,域名一行写 qr.alipay.com。页只解码文字,不检查订单状态,也不发起付款。

打开链接前值得看两处,识别器都会标出来:

  • https://citypark.example.gov@pay-meter.example.net/zone/4471 看起来像政府域名。@ 前面只是用户名,域名一行写的是 pay-meter.example.net。
  • https://exаmple.com/login 里的 а 是西里尔字母,页面按 punycode 显示 xn--exmple-4nf.com,并提醒形近字母。

解不出来的时候

没有哪个识别器能读所有图片。失败时,原因通常能在原图里看出来:

  • 每个模块的像素太少。 33 模块的码在 45×45 像素失败、60×60 成功,约合每个模块 1.5 像素。从大照片里裁小码,只有裁完仍保留足够分辨率才有用。
  • 模糊与眩光。 手抖、镜头上的指纹、覆盖在码上的反光都会抹掉明暗差。换一个角度再拍一张,常常就好了。
  • 对比度低。 灰底灰码有时能读,褪色的印刷品不一定。生成器会在亮度差低于 40% 时警告,这个值取自 ISO/IEC 15415 符号对比度 C 级的下限。
  • 反色。 黑底白码是标准允许的(反射反转,§5.2),zxing-cpp 会尝试读,但有些手机 App 不试反色图。同一张码手机不认、本页能读时,反色是一种可能。
  • 一帧里有多个码。 本页最多处理 16 个,但码与码重叠、被图片边缘切掉、或者一个码印在另一个码上,可能一个都读不出。
  • 破损超过纠错上限。 H 级大约能丢 30% 的码字,超过就没了。带大 logo 或缺角的码,可能还能读,也可能不行。
  • 字节不是文字。 这是上面说的乱码,不是解码失败:字节读出来了,字符集猜错了。
  • 浏览器显示不了的格式。 PDF 要先截图;HEIC 只有能显示它的浏览器(通常是 Safari)才能读。
  • URL 模式被 CORS 拦住。 填图片地址时,图片所在服务器必须允许跨域请求。不允许就先把图存下来,再拖进页面。

在这里读不出,先换更清晰的源图、裁得更紧,或者换一个识别器,再判断这个码是不是无效。存在谁都读不出的码;也存在只是某一个识别器读不出的码。

隐私:哪些请求会离开页面

本地文件、粘贴的截图或摄像头画面,图片在页面里解码。图片与解出的文本不会上传到本站,识别器也不会把它们写进浏览器存储。解码脚本与 WebAssembly 模块首次使用时从本站 /vendor 目录加载,所以这一次请求需要网络。「清空」或 Ctrl/⌘+L 会移除结果并停止摄像头。

另外几类请求与解码分开计算:

  • URL 模式按你填的地址去取图,不带 Cookie,也不带来源页。图片所在服务器能看到这次请求,它自己的日志策略适用。
  • 打开解出的链接、地图、邮件地址、电话号码或短信,会联系对应目标,或打开你配置的 App。
  • 页面加载 Google Analytics 与 AdSense。 统计会收集常规的页面与设备信息,并记录工具事件:请求摄像头、图片来源、结果类型、切换编码、打开链接。事件里只有工具名与动作,没有图片或解出的文字;复制事件只在当前结果复制成功后才记录。

所以解出的内容在标签页存续期间留在页面里,但这个页面不是纯离线阅读器:它要加载解码器、统计与广告。Network 面板能看到实际发出的请求,URL 模式与链接警告则说明了哪些请求会离开浏览器。

自己生成、打印与复检

读和写用的是同一套标准,从生成端看限制更清楚。二维码生成器生成静态码:内容就在图案里,不经过本站跳转,也没有过期时间。它提供:

  • 文本 / 网址、联系人、邮件、电话、短信、位置六类模板;Wi-Fi 标签页会指向 Wi-Fi 二维码生成器。
  • 纠错 L、M、Q、H 四档,PNG 256–4096 px 与 SVG。PNG 的每个模块都是整数像素:版本 2 加静区是 33 个模块宽,选 1024 px 会得到 1023 px,每模块 31 px。
  • 默认 4 模块静区;可以调小,但会给出警告。
  • 容量不足时说明需要多少位、能放多少位、降一档纠错是否放得下。
  • 汉字模式(JIS X 0208),默认关闭,因为不是每个读取端都测过。

同一张名片用 vCard 3.0 在纠错 M 下需要版本 8(49×49),用 MECARD 只要版本 5(37×37)。生成器不提供 logo 叠加、点阵样式、渐变和边框,因为这些都会盖住模块,只能靠纠错硬撑。

印之前,用你希望读者使用的手机扫一次导出的文件,再用二维码扫描识别读同一个文件。这样能发现模板选错、静区太小、印刷尺寸导致每模块像素不足。要条形码而不是二维码,用条形码生成器:它生成 EAN-13、EAN-8、UPC-A、Code 128、Code 39、ITF-14 和 Codabar,本页的二维码识别器读不了这些。