背景
二維碼因其成本低,相容性好,儲存資訊更多等優勢成為了線上線下主要的連線工具,並且在移動裝置互動上提供了更快捷入口。對於大部分標準清晰的二維碼場景,使用開源的ZXing或ZBar庫就可以很好的完成二維碼的識別。但是由於二維碼是一種點陣式資訊編碼方式,任何視覺上的缺失,彎曲變形,光照,螢幕噪點等都會極大的干擾影響識別成功率和速度,影響了使用者體驗和轉化率。碰到一些特殊的場景,ZXing/ZBar因識別率不高,難以滿足業務的需求。
這些特殊場景主要包括以下幾方面:
1、二維碼是深色背景的,周邊帶有黑框,沒有明顯的分界線
2、二維碼是灰色的,對比度很低
3、二維碼拉伸變形,傾斜扭曲
4、由於光照干擾,二維碼影象光照不均,區域性區域過暗
原本愛奇藝APP直接整合的開源的ZXing庫實現掃碼功能,並沒有做過多的定製和優化,這些場景下識別體驗較差。因此開始了二維碼掃碼效能優化之路,針對各種場景進行逐一分析優化,經過2~3個版本的迭代,愛奇藝APP掃碼效能指標有了質的提升,內部樣本集效能測試結果顯示,識別率從最初的30%提升到了75%左右,平均耗時從2.1s下降到722ms。主要從三個方面進行了針對性優化: (1) 掃描速度優化 (2) 互動體驗優化 (3) 識別率優化。
由於二維碼樣本集的差異,測試效能資料僅供參考:
掃碼速度優化1. 去除不必要的格式轉換和旋轉操作
在分析程式碼時,發現原有的邏輯在相機幀`onPreviewFrame()`回撥裡處理幀畫素時,先把YUV資料格式轉換成了RGB格式,同時由於橫豎屏的關係,對畫素陣列進行了90°旋轉操作,然後把處理好的畫素陣列傳遞給`RGBLuminanceSource`,交給ZXing解碼。經過斷點分析,發現YUV轉RGB和旋轉這兩步耗費了大量時間,預覽陣列越大耗時越長。
ZXing本身提供了`YUVLuminanceSource`可以直接解碼YUV資料,在YUV格式中Y分量就是灰度分量,對比度相比RGB更高,直接把YUV格式的data資料交給ZXing解碼,轉換耗時就節省下來了。
相機預覽座標系是橫屏的,而移動APP一般是豎屏掃碼模式,而我們分析發現ZXing解碼二維碼與其是水平的還是豎直的影象關係不大,都能識別,因此可以不旋轉畫素陣列,只旋轉裁剪區域的座標系,只需要把矩形框的四個座標點做一下變換,設定給`YUVLuminanceSource`,就避免了對所有畫素進行旋轉的操作,這部分耗時也節省出來了。
2. 減少執行解碼格式
使用ZXing解碼時,一般用的是`MultiFormatReader`,其內部維護了一個Reader陣列,預設支援一維條碼(UPC-A,UPC-E,EAN-8,Code 39,Code 93等)和二維條碼(QR Code,Data Matrix,PDF 417,MaxiCode)等20多種格式,迴圈遍歷解碼失敗耗時會拉長,影響下一幀的解碼。結合愛奇藝APP掃碼業務特點,實際應用,中我們裁剪ZXing解碼格式只支援二維碼,大大縮短了解碼失敗耗時(識別不了的幀),間接優化了整體的解碼耗時。
3. 合理設定預覽大小和裁剪掃描框
ZXing 預設提供的預覽幀大小選擇演算法在大部分裝置上獲取到的是螢幕尺寸,螢幕解析度越高影象越清晰,相應的畫素點更多,解碼一幀的耗時相應增加。而對於二維碼掃描來說,定位二維碼的回字形探測影象和取樣解碼並不需要很高的解析度。因此修改預覽幀大小設定,調整匹配演算法,選擇最接近常規的預覽尺寸,然後裁剪二維碼掃描框的大小給ZXing解碼。為了增加掃描識別的魯棒性,我們間隔擷取一幀為螢幕寬度大小的正方形區域作為解碼區域,在提高識別魯棒性的同時,加快了解碼識別速度。
4. 序列改並行化處理
ZXing 解碼操作是採用一個HandlerThread訊息佇列處理的,是單執行緒模型,當一幀處理完成之後才會通過`setOneShotPreviewCallback()`請求相機準備下一幀,缺點就是如果處理一幀資料時間很長,會阻塞下一幀的處理,比如某一幀是模糊的或者沒有二維碼資料,等使用者聚焦對準了二維碼,還是需要等待一段時間才能識別。
當我們採用`setPreviewCallback()`產生連續預覽幀,將序列解碼修改為並行解碼,每當`onPreviewFrame()`資料到達時,提交一個AsynTask到執行緒池,執行緒池大小可以動態配置。合理設定執行緒池大小,可以大大加快並行化處理和識別速度。
互動體驗優化
1. 優化對焦模式和設定定點對焦
ZXing 預設採用的是`AUTO_FOCUS`對焦模式,間隔1.5s觸發一次自動對焦,時間很長,在部分裝置上體驗較差,可以把對焦時間縮短,但是在`autoFocus()`瞬間影象會變模糊。通過預設設定為`FOCUS_MODE_CONTINUOUS_PICTURE`(連續拍照)模式,在第一次對焦清晰之後,後續幀都會比較清晰。
在不設定對焦區域時,相機預設是全螢幕聚焦,當二維碼圖片較遠且周邊環境較暗時,自動對焦會非常慢。可以通過使用`setFocusAreas()`和`setMeteringAreas()`設定對焦區域和測光區域為掃描框區域,大大提高遠圖二維碼的聚焦速度和識別成功率。
2. 支援自動縮放
當二維碼區域很小很遠時,自動放大能大大提高使用者體驗和識別率。`QRCodeReader`解碼二維碼主要分為兩步:(1) 識別二維碼回字形探測位置座標,取樣得到矩陣 (2) 根據解碼規則進行RS解碼還原資料。在第一步解碼的`DetectorResult`裡包含定位點和對齊點的座標資訊。如果二維碼很小,在取樣過程中可能採集到不正確的畫素點,第二步解碼過程中還是可能會失敗。在這種場景下,我們可以根據定位點的資訊大致估算出二維碼的尺寸,然後跟掃描框的大小做比對,根據相機的焦距做自動放大。經過幾次放大操作之後,如果還是一直解碼失敗,而重置到預設的倍數,防止部分噪點定位錯誤估算二維碼尺寸偏差,導致一直處於放大狀態,體驗較差。
3. 支援單擊聚焦,雙擊縮放
部分相機對焦慢或者光照環境不均勻時,使用者單擊螢幕某點時,可以定點聚焦,提高相機預覽圖清晰度;使用者雙擊螢幕時,根據當前相機的放大倍數,選擇放大還是縮小。
4. 支援雙指縮放調整焦距
當自動放大到不合理的區域時或者二維碼太遠一直無法識別時,使用者可以手動使用雙指縮放相機,來達到輔助識別的目的。
掃碼識別率優化在整個二維碼掃描識別過程中,主要分為以下幾個步驟:1) 二值化 2) 定位位置探測圖形 3) 尋找對齊點 4) 透視變換校正 5) 畫素取樣解碼。在我們的二維碼測試集上,肉眼可見非常清晰的影象,ZXing卻怎麼都無法識別,為了進一步優化識別率,只能研究和改進ZXing的演算法了。大概有以下幾種場景和策略優化:
策略1:增加N:1:3:1:1的掃描模式
ZXing定位回字形位置圖案時,是針對白色背景的二維碼設計的,採用的狀態機模式只有掃描到1:1:3:1:1時才算定位到圖示。而實際對於一些黑框或者深色背景的二維碼,由於回字形圖案與背景融合在一起,沒有明顯的邊界,ZXing狀態機就無法定位了。
為了支援這種場景,需要修改ZXing定位點的相關邏輯,核心程式碼在`FinderPatternFinder`類,增加對邊界重合場景的考慮。對比邊界重合有兩種case:N:1:3:1:1或者1:1:3:1:N,當狀態機陣列是5時,校驗中央部分是否滿足1:3:1,然後判斷出左邊界還是右邊界重合,對此做一定的修正。原始碼中相應的對角線校驗邏輯也需要同步修改,同時增加反向對角線的校驗策略。
策略2:優化右下角點的估計演算法
版本號為1的二維碼,沒有校正點,在沒有平行正面90°的場景下,或者校正點存在汙染或者破損的場景下,透視變換退化為仿射變換,ZXing識別效果很差。
針對這種場景,我們利用左下角和右上角的回字形,通過定位下邊界線和右邊界線,然後利用直接相交找到正確的右下角點。在找到校正點的情況下,使用校正點做透視變換,沒有校正點的場景下,則利用重新估計的右下角點做透視變換,大大增強了對傾斜版本為1的二維碼識別。
策略3:調整定位點篩選閾值和排序規則
ZXing在找到潛在的定位點座標之後,會進行一輪的篩選和過濾,找到符合條件的三個點,然後進行排序確定左下,左上,右上點的順序。在篩選過程中,限定了定位點最大的moduleSize不能超過最小的moduleSize的40%,而對於有些二維碼(圖b)傾斜很嚴重時這個差值會大於40%,適當調整容忍閾值到60%以上可以增加容錯性。對於另外一些傾斜非常嚴重的二維碼(圖a),當相鄰的邊長大於對角線時,由於ZXing預設是把最長的邊當做對角線,這樣三個點的順序關係就會定位錯誤,導致錯誤的估計了第四個頂點的位置,從而導致識別失敗。
針對這種場景,我們做了如下策略的調整。在`ResultPoint#orderBestPatterns()`時,增加圖片最大寬高的限制,在第一輪以最長邊作為對角線的假設下,估計第四個點的位置上,如果第四個點明顯落在圖片的外面,則取次長的邊作為對角線再次排序,這樣對於傾斜非常嚴重的二維碼同樣可以正常校正識別。
策略4:採用不同的二值化演算法
二值化演算法的好壞決定了後面查詢定位點的準確性。所謂二值化處理,就是給定一個閾值,大於閾值的畫素設定為白色,小於閾值的畫素設定為黑色。對於一些灰色低對比度或者光照不均的圖片,使用ZXing內建的二值化演算法不能很好的還原影象,分離出回字形圖案。
我們分析研究了ZXing自帶二值化`HibridBinarizer`及和幾種常用的區域性二值化演算法效果。經過綜合評估,最終選用了局部均值跳幀執行的輔助策略,部分幀使用ZXing自帶二值化識別,部分幀使用區域性均值二值化識別,大大地提高了抗光照不均和低對比度二維碼場景的識別率。
圖4 二值化影象
策略5:整合opencv預處理
對於部分回字形中央模糊或者有椒鹽噪點的二維碼,二值化之後很容易干擾回字形的定位識別,需要對影象做一些濾波降噪的預處理,比如中值濾波/高斯濾波等,能明顯減少椒鹽噪聲的干擾。
對於部分旋轉傾斜角度的二維碼,由於ZXing採用水平線和豎直線掃描演算法定位回字形圖案,角度傾斜之後實際掃描到的是二維碼的對角線,儘管對角線也會遵循1:1:3:1:1的原則,但是隻會在中心點附近很小的區域內滿足該規則。而且ZXing正常情況下是跳行掃描的,hard模式才是逐行掃描,很容易跳過關鍵的點,從而導致定點陣圖案尋找失敗。而實際上只要把二維碼旋轉到水平方向,則可以立即識別。
針對這部分場景,我們集成了OpenCV計算機視覺庫,對相機採集到的二維碼灰度影象做預處理,濾波降噪之後,擷取二維碼中心區域,進行仿射校正(旋轉變化,拉伸變化)成固定畫素大小的二值化影象,然後交給ZXing做解碼,提高了部分場景下的識別率。
其他策略除了上面提到的通用的掃碼優化之後,我們還針對一些特殊場景進行了一定策略上的排程優化。
1、對於部分彎曲畸變的二維碼,當存在曲面彎曲時,採用正常的螢幕透視變換很難正確取樣。可以假設取樣座標系遵循一個更復雜的對映關係,比如二次函式,來擬合這種關係。當二維碼版本號大於7時,存在4個以上的輔助校正點,通過取樣更多的點,可以構造出這種矩陣關係,達到識別曲面場景的效果。
2、增加對反色二維碼的識別能力。ZXing預設只支援白底黑色的二維碼,我們在ZXing定位回字形位置探測圖案過程中,增加收集潛在反色樁點的機制,當正向識別不了的情況下,fallback到反向識別策略。
未來展望我們在掃碼優化實踐過程中,也查閱了一些公開的資料,從中參考了一些經驗總結,我們在ZXing原始碼上做了大量的針對性修改,增強魯棒性,策略上也做了調優,後續計劃把優化版的ZXing貢獻開源給github社群。
二維碼的尺寸大小,清晰度及複雜度也是影響識別率的一個因素。掃碼優化是一個長期的工作,我們還將進一步結合愛奇藝自身業務特點持續的進行優化,從二維碼生成投放到客戶端識別等各個環節進行探索優化,如二維碼標準規範化、探索使用tensorflow影象識別檢測二維碼區域、嘗試RenderScript利用GPU做二值化計算等。
參考資料
1. 史上最全的支付寶二維碼掃碼優化技術方案
https://www.infoq.cn/article/hZg1aBXkmoIsGHAYGoH8
2. Lark二維碼掃描優化
https://zhuanlan.zhihu.com/p/44845942
3. 智慧裝置上的二維碼解碼優化
https://cardinfolink.github.io/2017/06/28/%E6%99%BA%E8%83%BD%E8%AE%BE%E5%A4%87%E4%B8%8A%E7%9A%84%E4%BA%8C%E7%BB%B4%E7%A0%81%E8%A7%A3%E7%A0%81%E4%BC%98%E5%8C%96/