同一个链接,二维码为什么有的疏、有的密?
把同一个网址交给两个二维码生成器,得到的图片可能明显不同:一个格子粗大、图案疏朗,另一个格子细小、显得复杂。它们都能打开同一页面,为什么需要不同数量的格子?更密的那个是否保存了更多信息,或者一定更可靠?
要回答这个问题,需要把输入内容、编码参数和呈现尺寸分开比较。二维码保存的内容相同,并不要求黑白图案唯一;增加用于纠错的数据,也可能让格子数量变多。实际使用时,额外的纠错能力还要和每个格子的可辨认程度一起考虑。
本文针对常见的方形 QR Code Model 2。我用博客已有的真实链接,生成七张 PNG,再交给另一套软件解码。下面区分官方说明、实际文件实验和几何计算;没有进行手机摄像头、印刷或遮挡损坏测试。
先把“尺寸”拆成三个量
二维码里的一个黑色或白色小格叫模块。模块数量描述符号的网格;一个模块占几个像素,描述它如何绘制;最终图片有多宽,还包含四周的留白。把三者都叫“尺寸”,就很容易把改变绘图分辨率和改变编码容量混为一谈。
普通二维码有 Version 1–40。Version 1 是 21×21 模块,版本每增加一级,每边增加四个模块,Version 40 达到 177×177。因此,版本为 v 时,符号每边模块数可写成 17+4v。这里的 Version 是二维码网格规格,和生成库的软件版本无关。DENSO WAVE:二维码版本与容量
例如,Version 5 的符号是 37×37 模块。每个模块画成 6×6 像素,四周各留四个模块,那么完整图片宽度是 (37+4+4)×6=270 像素。只把每个模块改画成 12×12 像素,会得到更大的图片,网格仍是 37×37,也没有多装一个字节。
另外,视觉上的“黑块多”不能直接当作信息量。网格中有用于定位等用途的固定结构,数据区域还会应用掩码。比较密度时,先看版本和每边模块数,比凭黑色面积判断更有意义。编码过程中的这些不同部分,可见 Nayuki 对生成流程的说明。Nayuki:模块、编码与掩码
同一个 66 字节链接,只改变纠错等级
实验输入是这篇已经发布的流星雨文章的完整网址:
https://gumi.ink/posts/2026-10-08-how-to-read-meteor-shower-rates/
它的 UTF-8 编码长 66 字节。所有例子都强制使用一个 8 位字节模式的数据段,黑色模块、白色背景,每模块 6 像素。普通例子保留四模块留白,自动选择能容纳内容的版本和掩码;第一组只改变 L、M、Q、H 四种纠错等级。
这样固定数据模式,是为了减少对照中的变量。二维码也有数字、字母数字等编码模式,不同字符组合和分段方法会影响容量。本文没有把这些模式逐一测试,表里的结果只对应给定网址与单一字节段,不能推广成“所有 66 字节内容的版本表”。Nayuki:数据段与容量的关系
生成器使用 qrcode,解码器使用独立的 zxing-cpp 实现。程序先保存 PNG,再重新打开文件读取内容,而不是把生成器收到的输入当成“解码结果”。实测如下:
| 纠错等级 | 自动选出的版本 | 符号每边模块数 | 含留白的原始 PNG | 解码文本与输入完全相同 |
|---|---|---|---|---|
| L | 4 | 33 | 246×246 | 是 |
| M | 5 | 37 | 270×270 | 是 |
| Q | 6 | 41 | 294×294 | 是 |
| H | 8 | 49 | 342×342 | 是 |

L 级原图:246×246 像素,模块边长 6 像素,四边各留白 24 像素。

H 级原图:342×342 像素,模块边长和留白宽度与 L 级相同。网页可能按布局缩放图片,原始尺寸以下载文件为准。
这组结果直接回答了开头的一部分问题:相同链接,确实可以因为纠错等级不同而需要更大的网格。 四张图解出的内容没有增加,H 级也没有附带第二个网址。变多的是编码所需的冗余,而不是网页内容。
更强纠错需要空间,也要考虑最终呈现
纠错让部分污损后的数据仍有机会恢复。DENSO WAVE 说明二维码使用 Reed–Solomon 纠错,等级提高会增加数据量;L、M、Q、H 的常见近似恢复比例分别是 7%、15%、25%、30%。这里的比例按总码字数量说明,码字为 8 位。DENSO WAVE:纠错原理与比例口径、qrcode:四种等级的参数说明
不能据此推导“随便遮住图片面积的 30%,H 级都能读”。被遮住的位置可能涉及定位等结构;图像面积也不能直接等同于发生错误的码字比例。本次没有做损坏实验,更无法从完整 PNG 的成功解码,算出加多大 logo 仍然安全。
纠错增加时,如果原有版本容纳不下,生成器就要采用更大的版本;固定每模块像素数,图片随之变大。但设计稿经常给二维码预留一个固定宽度,这时图片总宽没变,更多格子只能分到更小的空间。
以包含留白的整体宽度固定为 200 像素作几何算例:L 级每边总计 41 个模块宽度,每模块约 200÷41=4.88 像素;H 级每边总计 57 个模块宽度,每模块约 200÷57=3.51 像素。这是尺寸关系的计算,不是任何手机的识读下限,也不是一次缩放后的扫码测试。
因此,选择 H 级同时又把图压进很小的位置,需要核对实际呈现是否还能清楚区分各模块。它提供了更多纠错冗余,也增加了网格密度。比较等级时,不能只比较一个“恢复百分比”,还应检查最终大小、留白和实际读取条件。
格子数量相同,图案也可以不同
第二组固定 M 级,只把掩码指定为 0 和 1。两张图都是 Version 5、37×37 模块、270×270 像素,程序比较实际模块矩阵,确认它们不同;zxing-cpp 则从两张保存后的 PNG 中读回了相同的完整链接。


掩码按规则改变数据区域的黑白排列。普通二维码有八种掩码,自动选择时会评估图案的惩罚分数,选择较低者;格式信息记录所用掩码,读取时可还原数据。因此,图案并不是把输入字节原样涂黑后的唯一画法。Nayuki:掩码选择与格式信息
这里没有比较两个掩码在摄像头下谁更容易读,也没有证明任意生成器一定给出同一图案。实验支持的结论是:网格规格和解码内容都相同,黑白矩阵仍然可以不同。 判断两个二维码是否指向同处,应读取完整文本比较;只比较图片外观,容易把合法的不同编码当成内容变更。
去掉留白,这次为什么还能解码?
二维码四周的空白叫静区,帮助读取器把符号和周围内容区分开。DENSO WAVE 要求四边各保留四模块宽的空白;它的单位是模块,不是固定四个像素。本文每模块 6 像素,所以正常静区每边为 24 像素。DENSO WAVE:静区与符号尺寸
第三组故意把 M 级的 border 改为 0。37×37 模块的符号没变,PNG 缩到 222×222 像素,也不再提供所要求的四模块静区。无留白的对照图片
实际结果是:本次 zxing-cpp 仍解出了正确链接。 这说明这一软件读取过程能够处理这个干净、黑白分明的文件;不能把未发生的失败写成实测事实。另一方面,读取成功也不能证明图片符合静区要求,或在贴近文字、图案的实际版面中同样可靠。
这正是文件实验和使用环境之间的区别。我们拿到的是没有拍摄噪声的数字文件,正式用途可能涉及周边内容、距离、光线和失焦。静区应该在最终版面中保留;一次宽容的解码结果,不能替代这个呈现条件。本文没有进一步模拟这些环境因素。
复现时,让图片和失败结果都留下来
本次运行环境为 macOS 27.0.1、arm64、Python 3.12.14,依赖为 qrcode 8.2、Pillow 12.3.0、zxing-cpp 3.1.1。生成库的参数说明在其官方仓库,独立解码绑定的版本资料见 zxing-cpp 3.1.1。
下载并解压完整实验包,在解压目录安装固定依赖后运行:
python3.12 -m venv .venv
.venv/bin/python -m pip install -r qr-density-requirements.txt
.venv/bin/python qr-density-lab.py qr-density-output
脚本的关键生成步骤如下,完整版本还负责保存、独立解码和记录结果:
qr = qrcode.QRCode(
version=None,
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=6, border=4,
)
qr.add_data(QRData(PAYLOAD.encode("utf-8"), mode=MODE_8BIT_BYTE))
qr.make(fit=True)
这里 version=None 配合 fit=True 自动寻找能放下内容的版本;box_size 控制像素绘制,border 控制静区。二者不会减少输入的数据量。qrcode:QRCode 参数与用法
脚本还强制同一网址采用 Version 1、M 级,并关闭自动扩大版本。实际得到 DataOverflowError,详情如下,没有生成该条件的图片:
Code length overflow. Data size (540) > size available (128)
核对本次安装的生成库源码可知,这两个长度都以位计,540 包含数据段头部的编码开销。这表明该配置容量不足,不能靠指定小版本把完整网址塞进去。实际处理应允许扩大版本,或者重新决定要编码的内容;如果修改输入,就应重新检查解出的完整文本。脚本没有截短网址来“通过”测试,也没有把容量错误当成空二维码输出。
运行会留下七张 PNG 和 results.json。结果包含环境、输入长度、每张图的版本与尺寸、解码文本、掩码矩阵对照和容量错误。另可分别下载脚本、依赖清单、本次原始结果及运行说明。脚本只编码、读取文件,不会访问其中的网址;成功读出文本与目标网页能否打开,也是两个需要分别检查的环节。
实际选择时,先明确要保持什么
如果用途是普通的页面链接,可以把常用的 M 级作为比较起点,再根据最终载体核对。DENSO WAVE 也强调纠错等级要结合环境和尺寸,M 是常用等级;这不是保证所有场景都用 M 最优。DENSO WAVE:按环境选择纠错等级
具体做法是先确定完整内容,生成后核对解码文本,再在正式页面、标签或印刷样张上检查最终大小与四模块静区,用预期的读取设备验证。若要比较两个生成器,先记下数据模式、版本、纠错等级、掩码和留白;否则只凭两张截图,很难知道究竟改变了哪一项。
同一个链接的二维码可以更疏、更密,也可以只是黑白排列不同。本文验证了两条具体原因:纠错等级会改变所需容量和网格大小,掩码会在相同规格下改变图案。 选择合适的二维码,要同时保持正确内容、足够的呈现空间和必要留白,再验证真实用途。格子越多不等于内容越多,纠错等级越高也不能单独证明整体越容易读取。