本文為翻譯文章,英文原文請看這里[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什么也沒做,但很慷慨的為我們提供了演示所用的圖像。在這里感謝他們二位。
————————————————————————————————————
此文轉載自我的個人博客,博客地址請解碼下面的這張QR圖像。由於本人水平有限,翻譯過程中難免出現錯誤,望廣大網友見諒,並留言指出。