现在生成一个二维码实在太容易了。输入一段网址,点一下“生成”,几乎瞬间就能得到一张由黑白小方块组成的图片。

正因为这个过程太快,很多人会下意识地认为:二维码生成器只是把网址“画”成了一张特殊图片。

实际上并不是。

二维码里的黑白方块并非随机出现,每一个区域都有自己的任务。有些负责告诉扫码设备“二维码在这里”,有些负责校正方向和位置,剩下的大量区域才真正承担数据保存工作。生成一个标准 QR Code,背后要经过编码、纠错、排列、掩膜等一整套过程。

二维码是怎么生成的?从数据编码到黑白方块的原理解析

二维码到底存了什么

先说一个很容易混淆的问题:扫描网页二维码时,网页真的被装进二维码了吗?

通常没有。

如果二维码对应的是一个网站,例如 https://example.com/,真正编码进去的主要就是这串字符。手机扫码软件把字符还原出来,发现它符合网址格式,再交给浏览器打开。

因此二维码本质上是一种数据载体。除了网址,它同样可以编码文字、数字以及其他符合规范的数据。

这也解释了为什么二维码离线状态下依然可以被“识别”。识别二维码本身不一定需要互联网,因为原始数据就在图案里;但如果二维码保存的是网址,那么打开这个网址自然还是需要网络。

黑白方块不是随机排列

我们看到的每一个小方格,在 QR Code 里通常称为模块(Module)。最终二维码就是由大量模块按照规则组成的矩阵。

可以把它粗略理解成一张方格纸:某个位置填黑,另一个位置留白。扫码程序并不是在“看图片表达了什么意思”,而是在确定每一个模块的状态,然后按照 QR Code 的规则还原数据。

标准 QR Code 一共划分为 40 个版本。Version 1 是 21×21 个模块,版本每提高一级,每条边增加 4 个模块;到 Version 40 时,可以达到 177×177 个模块。

所以平时看到有些二维码比较疏,有些密密麻麻,并不只是设计风格不同。编码内容多少、数据类型以及所选择的纠错等级,都会影响需要使用多大的二维码版本。

生成二维码要经历什么

假设现在准备把一段网址生成二维码,生成器并不是拿到字符串后直接开始画格子。

整个过程可以简化理解为:

  1. 分析准备保存的数据;

  2. 选择合适的数据编码方式;

  3. 把内容转换成对应的二进制数据;

  4. 根据数据量确定二维码版本;

  5. 加入纠错数据;

  6. 在二维码矩阵中放置功能区域;

  7. 按照规定顺序填入数据;

  8. 进行掩膜处理;

  9. 写入格式等必要信息;

  10. 最终绘制成可以看到的二维码。

换句话说,我们最后看到的黑白图案已经是处理完成的结果,而不是原始文字简单转换后的样子。

三个大方块有什么用

随便找一个普通二维码观察一下,会发现左上角、右上角和左下角通常各有一个很醒目的方形图案。

这三个区域叫作位置探测图形,可以理解成二维码给扫描设备留下的“路标”。

摄像头拍到二维码以后,首先需要解决的并不是里面写了什么,而是几个更基础的问题:二维码在哪里?有多大?转了多少度?应该从哪个方向读取?

三个位置探测图形就是为解决这些问题而存在的。

由于它们位于二维码的三个角,扫码程序可以据此判断二维码的位置和方向。也正因为如此,正常的 QR Code 并不要求一定正着扫描,手机拿歪一些通常仍然能够识别。

除了这三个最显眼的大方块,二维码中还可能存在用于校正位置的图形、帮助确定模块坐标的时序图形等结构。只是这些区域没有三个角上的方块那么容易被肉眼注意到。

纠错为何能救回数据

实际使用二维码时有一个挺有意思的现象:二维码被折了一角、沾上一点污渍,甚至中间放了一个小Logo,有时手机还是能扫出来。

原因就在于二维码并不只保存原始数据,还会额外生成用于恢复数据的纠错码。

QR Code 使用纠错机制来提高数据恢复能力。标准二维码提供 L、M、Q、H 四个纠错等级,官方给出的可恢复码字比例大约分别为 7%、15%、25% 和 30%。

纠错等级大致恢复能力特点
L约7%纠错占用较少,可用于环境较稳定的场景
M约15%在容量与恢复能力之间取得折中
Q约25%允许更大程度的数据损伤
H约30%恢复能力较高,同时会占用更多编码空间

这里有个容易产生误解的地方:“30%纠错”并不意味着随便遮住二维码面积的30%都肯定能够识别。二维码损坏的位置、数据分布、定位区域是否完整、打印质量以及扫描环境都会影响最终结果。

所以给二维码加Logo时,不能简单理解为“只要Logo面积不到30%就安全”。如果恰好覆盖了关键功能区域,照样可能导致扫描失败。

掩膜让二维码更好识别

数据都准备好了,是不是直接按照0和1填成黑白方块就行?还差一步很重要的处理:掩膜。

如果直接按照原始数据排列,某些内容可能碰巧产生大片连续黑块、大片白块,或者形成不利于识别的图案。这样的二维码虽然理论上存着正确数据,但实际摄像头读取时可能更容易受到干扰。

QR Code 因此会对数据区域应用规定的掩膜模式,对部分模块状态进行规则化变换,然后比较不同结果,选择更适合读取的一种。

从外观上看,它只是让一部分黑白格子发生了变化;从目的上看,则是在避免二维码出现过于糟糕的图案分布。

所以同一段内容使用不同生成方式时,有时会看到黑白图案并不完全相同。二维码只要符合规范并能正确还原数据,并不意味着视觉图案必须永远一模一样。

扫一扫时发生了什么

生成二维码的过程复杂,扫描时其实是在反着走一遍。

手机摄像头先获得画面,识别其中的位置探测图形,再判断二维码边界、方向以及模块网格。确定每个模块的位置之后,程序开始读取格式信息、识别掩膜方式并提取数据。

如果部分数据因为污渍、模糊或者拍摄角度出现错误,还会利用纠错数据尝试恢复。数据最终被重新组合并解码,才变回我们熟悉的网址、文字或其他内容。

正常扫码往往不到一秒,但这短短一瞬间实际上完成了图像定位、几何校正、数据提取、纠错和解码等多个步骤。

为什么二维码越来越密

自己用二维码生成工具做过测试的人可能发现:输入几个字时二维码比较简单,粘贴一大段内容后,方格明显变得更密。

原因并不复杂——需要保存的数据变多了。

二维码每个版本能够容纳的数据有限。当现有版本装不下内容时,就需要使用更大的版本,于是模块数量增加,看起来也就越来越复杂。

纠错等级同样会影响这个结果。提高纠错等级意味着需要分配更多空间保存纠错信息,在原始内容不变的情况下,也可能需要更高版本。

因此二维码不是内容塞得越多越好。尤其是在名片、包装或者小尺寸标签上使用时,二维码过于密集会对打印精度和扫描条件提出更高要求。

静态码和动态码的区别

很多二维码平台会提供“静态二维码”和“动态二维码”,这两个名称很容易让人误以为它们采用了完全不同的二维码编码技术。

从实际实现思路来看,很多所谓动态二维码依然是普通二维码。区别往往在于里面保存的不是最终目标地址,而是服务商提供的一个中转网址。

例如二维码实际保存:

https://example.com/q/12345

用户扫描后先访问这个地址,服务器再把访问者跳转到真正的目标页面。以后即使修改目标地址,二维码中的中转网址并没有改变,所以原来的二维码图片还能继续使用。

这也是为什么动态二维码通常需要依赖平台服务。一旦中转域名、账户或者服务本身出现问题,二维码图片虽然还能被识别,却未必能够继续跳转到原来的内容。

而直接把最终网址编码进去的静态二维码,不需要依赖这层跳转服务,但生成之后想换网址,通常就只能重新生成二维码。

美化二维码要留出余地

现在不少二维码都会改颜色、换圆角、加入品牌Logo,看起来比传统黑白二维码精致很多。这些设计并不是完全不能做,但二维码首先还是一个机器识别符号。

三个位置探测图形、周围的空白区域以及整体明暗对比都对读取很重要。如果为了视觉效果把结构改得太激进,人的眼睛觉得很好看,摄像头却未必愿意配合。

比较稳妥的做法是把“能扫”放在“好看”之前。完成设计后不要只拿自己的手机试一次,最好换不同尺寸、不同屏幕和不同拍摄距离实际测试。准备印刷的话,还应该测试真实打印后的效果。

参考资料

  1. DENSO WAVE QR Code基础介绍

  2. QR Code版本与数据容量说明

  3. QR Code纠错机制说明

  4. QR Code标准与规格说明

  5. QR Code发展历史