May 17 2012

将自己的图片插入QRcode中

本文为翻译文章,英文原文请看这里[link],文中的QRcode中插入图片方法并非简单的利用容错,可以在保证识别正确率的同时加入非常大块的图像,文字等。正文如下————

QRcode是一种用来对字节符号进行编码的二维码。它的最常用方法之一便是在手机上替代网址的手动输入,而采用扫描带有网址信息的QRcode来打开相应的URL地址(常见的比如广告海报,不常见的比如飞机后面的条幅[link],GEEK的比如谷歌地图所看到的屋顶[link],2B的比如在自己衣服上裤子上[link][link]等等)

QRcode采用里德-所罗门码来进行编码,里德-所罗门码是一种带有容错机制的编码方法,采用这种机制扫描的时候并不需要读取所有的比特位,因此也使得简单的在QRcode中少量的更改信息,比如加入小型的图片等等,成为了可能。举个例子,2008年的时候Duncan Robertson为BBC电视台制作了一幅QRcode图片,就是用到了其强大的容错能力。

这是一种很简单但又非常实用的技巧,但是,站在技术的角度来看会感到很乏味。尽管上面的BBC三个大字看起来是很像一个QRcode里的字节符,但它对于QRcode的解码所带来的,仅仅是错误信息的冗余而已。我们可以把BBC三个字符处换成任何字符甚至图片,他们之间的唯一区别仅仅是带来的不同噪声而已。

在这个BBC QR Logo出来之后,网上出现了相当多的模仿者,大部分和上面一样,只是把中间的字符换成自己所喜爱的噪声信号。比如这幅迪斯尼海报就是代表[link]。

其实要说的是,对于我们技术工作者来说,有其他更好的方法来制作包含自己图像的QRcode,而不是仅仅添加一些无聊的噪声,和依靠QRcode的容错率来得到一个相对稳定的二维码。就比如下面这些:

这篇文章所要解释的就是制作这种二维码背后所包含的数学原理,也包括其简单的制作方法。文中所用到的源码发布在code.google.com/p/rsc上,并且还制作了一个在线的转换网站[link]。

 

背景知识

QRcode使用里德-所罗门码来进行错误修正。对于我们来说,里德-所罗门编码有两个非常重要的特性。第一,它是一种显式系统码,也就是说,你可以在最终的编码中直接看到原有的信息。就好比我们对”hello world”进行编码,最终看到的是”hello world”以及其后面跟随的几个容错码。第二点,里德-所罗门编码是可以被”异或”的,将两个不同里德-所罗门编码得到的结果异或运算后会得到一个新的里德-所罗门码,并且这个新码的原码即是原来两个原码的异或。如果你想知道为什么这两个特性会成立,请看我更早的一片文章Finite Field Arithmetic and Reed-Solomon Coding.

QRcode

一副QRcode图像会定义一些独特的描述符来帮助人们或者电脑识别出自己是一张QRcode。这种描述符随着QRcode的大小不同而略有区别——越大的QRcode图像拥有越多的描述符。但是对于人的识别来说,特征最明显的还是图片的四个角的符号是固定的,看到这样的四个角人类就本能的反应:这是一个QRcode。下面就是一些示例描述符。

上图中彩色部分就是编好的里德-所罗门码。对于每一副图像来说,可能含有一块或者多块里德-所罗门编码块,这个是由图像大小以及设定的纠错能力来决定的。对于下图来说,不同的编码块对应不同的颜色,L编码方式对应最小的错误冗余,为20%,其余的三种则分别增加冗余码,对应的百分比为38%,55%,和65%。

(实际上,我们可以通过读取图像最左上角的两个象素点来判断编码的冗余程度。定义黑色为0,白色为1,那么如果看到00则是L级别的冗余,01是M,10是Q,11则是最高的H级别冗余。现在,我们可以通过这种方法判断这幅印在T恤上的QR码[link]知道它所用的是最高级别的冗余,而另外的一件[link]比较2B的则用的是最少的冗余,以至于难以甚至无法读取)

正如我上面所提到的一样,原码信息是被显式的包含在被编码的图像中的,这样,原码信息中的每一个比特就与QR图像中的每一个像素所对应。这些像素在上面的图像中对应非灰色的色彩部分,灰色部分则是用于纠错的冗余信息码。编码好的比特是按照Z字形的结构连续排列在图像中的每一个像素上,从左下角开始并在右下角结束,如下图:

有了上面的这些工作,我们可以非常容易的知道原码信息在图像中的位置。然后通过改变自己的原码信息,就可以改变图像中的像素以至于可以在里面作图了。虽说如此,下面的一些情形可以让事情变得更有趣。

QR Masks

第一个难点就是编码完成的数据会按照下面的几个模板来异或运算进行混淆,混淆过后才是最终得到的图像。这样的模板有8个

对于一个QR编码器来说,它会根据输入的数据情况来选择最合适的模板来进行混淆,使得原本的数据隐藏起来。但在我们的这个编码器中,我们可以先选择好模板,然后再决定设计输入的数据。虽然说与原本的设计模式相违背,但依然可以产生合法的代码。

QR Code编码

第二个难点就是我们想要制作的是含有容易理解的信息的QRcode。简单来说你可以随意的按照自己设计的图片来生成QRcode,但这样解码出来的数据就完全是杂乱无章的乱码,作为一副QR图像来说就毫无意义了。因此我们需要约束自己的图案,以求产生包含能理解的内容的QRcode。幸运的是,QRcode允许原码信息以一些符号来表示,其中一种是一个8比特的数据,它需要引入一些垃圾数据来生成一副图像。另一种则是数值数据,这种格式中每10个比特表示3个十进制字符。到这里,制作特定图像中的限制已经很明显了,我们不能生成值超过999的10比特数据(注:10比特二进制最大值为1023)。尽管不能完全按照我们想要的来,但其灵活性已经很高了,能使用的比特串达到所有比特串的99.6%。因此,在生成自己想要的图片后,如果发现解码错误,我们随机选5个最具代表性的值为1的比特位——只有1才能产生错误的比特码——然后直接改写成0,重复扫描和以上的步骤。

但是仅仅有数字数据并不是一件有趣的事,我们扫描QR码只能得到一个毫无意义的巨大的十进制数值。又一次幸运的是,QRcode允许在一条信息中采用多种不同的编码类型。因此我就制作了这样的一条信息,前面一部分是自己的网址信息,然后再在一个#符号后面插入用来作图的数值数据。

http://swtch.com/pjw/#123456789…

数据前面的URL最先被编码,它在QR图像中占据最左边的部分,然后整个数据的冗余被加到编码数据的尾部,它占据了QR图像最右边的部分。中间的部分就是我们自己预先设定好的图像比特数据。

当使用手机扫描这幅QR图像的时候,解码器识别出URL并在浏览器中打开这个地址,然后根据其后#处的数值跳到页面相应的游标处。当然,这样的游标是不存在的,即便这样浏览器也不会发出任何抱怨。

使用这样的方法产生的图像如下:

后面的那张图将我们所不能控制的象素点用灰色表现了出来:左边的URL数据信息和右边的冗余码信息。我很欣赏这种人物的一部分融入噪声状冗余信号所产生出来的终结者(斯瓦辛格电影中的未来机器人)效果,但如果进一步扩大目前的视野会更好。

高斯-约旦消元

制作过程的第三个难点是,如何才能将想要的图像均匀的平铺在QRcode中,而不是前面的那种仅仅显示在中间的一小块。再次幸运的是,这一点我们仍然可以做到。

我之前提到过里德-所罗门码是一种可以被异或的编码方法:如果两个不同的里德-所罗门码块b1和b2,它们的原码信息分别为m1和m2,那么b1⊕b2依然是里德-所罗门码,并且这个合成码的原码会变为m1⊕m2(原因在我前面的这篇文章[link]中有解释)。这个特性让我们可以从一个合法的QRcode中产生另一个合法的QRcode。特别的,我们构建以下比特序列b0,b1,b2…,其中的bi就是指一个除开第i位之外其它位置都是0的比特块,然后尾部跟上冗余码以构成一个合法的里德-所罗门编码。这一组序列就是整个里德-所罗门码空间的一组基向量。下图所示矩阵就是2数据位2冗余位的基向量序列

没有填写的部分都代表比特位为0。灰色部分就是我们可以自己完全控制的比特部分,白色部分则是尾随后面的纠错冗余码。由上面的异或特性我们知道,我们可以将编完码的结果和上面的这组基进行异或,而不改变其他的控制位,并且保持冗余码的更新。

可能你又要抱怨了,这种做法其实无所事事,我们的图像位置该在哪儿还是在哪儿,没起到作用。

请等我慢慢说完,在有了上面知识的基础上,我们可以通过把多个基组合起来,以达到将自己的数据和容错码中的数据交换的目的。虽然说在根本上我们不能增加可以自由控制的点的数目,但却可以移动可以自由控制的点的位置——也就是说达到将不可控点空间分散化的效果,将这些不合作的控制点对图像整体的影响降低。这其实就是小标题中高斯-约旦消元法的基本思想,从原矩阵中产生一个简化的行列式。

上面的这个矩阵给了我们一个重要提示,即是没法做到完全一般化。尽管这些比特串是完全相互独立的,但是由于同时还需要照顾不守规矩的错误冗余码,我们没法做到QR图像中的每一个像素都是我们想要的值。在这里例子中,比特串最后的四个比特是不可控的,我们所做的操作就是通过上述基来对比特位置重构,分散不可控的点,使最终画面更协调。

在实际的程序里,采用这种思想的一个做法便是对里德-所罗门码块中的每一个比特,根据其在图像中的重要性进行打分与排序(图像中高对比区域像素的得分小于低对比区域像素),然后遍历图像中每一个像素,如果我们的基允许对其的改变并且改变后的得分更高的话,那么用相应的基对他进行异或,否则继续处理下一个像素。

使用这种方法,我们可以得到更宽但噪声更多的QR图像

图片里所示人物的前额以及右半部分的脸就为得到更宽的脑袋而牺牲了。

我们还可以随机决定可控点的位置,然后产生一种雾化的图片效果。

旋转

好了,以上方法介绍完毕之后大家都可以制作差不多凑合的QR图像了。我这里还剩下处理的最后一招,就是QR图像的旋转。在上面的图像里所有没法控制的冗余点部分都存在于存在于QR图像的右侧,但由于QRcode对于图像的旋转没有任何要求,因此其实可以把这部分移动到别的什么地方。

其他

关于这篇文章所使用的代码,可以在 code.google.com/p/rsc/source/browse/qr上找到。如果你喜欢这篇文章,我猜你也可能同时喜欢我的另一篇Zip Files All The Way Down

鸣谢

Alex Healy指出了里德-所罗门码对于异或运算是封闭的,这个是将冗余码分散到整幅图像中的关键。Peter Weinberger什么也没做,但很慷慨的为我们提供了演示所用的图像。在这里感谢他们二位。

No Comments

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment