目標檢測:二維碼檢測方案

MarsZuo發表於2022-03-26

一、序

移動網際網路時代,我們的身邊,無處不見的二維碼,在商店買東西可以用微信或支付寶的付款碼、在電影院可以使用二維碼在自助取票機上取票,朋友聚會時使用微信二維碼互相加好友。移動終端裝置的普及,出門只需要攜帶一步手機,極大方便了人們的日常生活。從技術的角度觀察,很多場景下,二維碼包含的都是一個 HTTP 連結,手機掃碼識別出二維碼的內容,然後跳轉到對應的網站。就好比 PC 終端上,訪問一個網站,一般的流程是,使用者在瀏覽器 “位址列” 輸入一個網址,然後回車確認,瀏覽器就會跳到對應的網站。而二維碼的流行,改變了這個互動流程,由手機 “掃一掃” 拍攝二維碼照片,然後手機內部解碼照片,提取二維碼包含的 HTTP 連結,最後跳轉對應的網站。簡言之,通過手機掃碼代替了使用者手動輸入網址的過程。

二、淺析二維碼原理

維基百科:二維碼也稱為二維條碼,是指在一維條碼的基礎上擴充套件出另一具有可讀性的條碼,使用黑白矩形圖案表示二進位制資料,被裝置掃描後可獲取其中所包含的資訊。

一個普通二維碼的基本結構:

目標檢測:二維碼檢測方案
  • Position Detection Pattern:位置探測圖形(這樣無論二維碼旋轉任意角度,都可以通過定點陣圖案識別出來);
  • Separators for Postion Detection Patterns:位置探測圖形分割符;
  • Timing Patterns:定點陣圖形,原因是二維碼有40種尺寸,尺寸過大了後需要有根標準線,不然掃描的時候可能會掃歪了;
  • Alignment Patterns:校正圖形,規格確定,校正圖形的數量和位置也就確定了;
  • Format Information:格式資訊,存在於所有的尺寸中,用於存放一些格式化資料的;
  • Version Information:即二維碼的規格,QR 碼符號共有40種規格的矩陣;
  • Data and Error Correction Codewords:實際儲存的二維碼資訊,和糾錯碼字(用於修正二維碼損壞帶來的錯誤)。

有了這個基本結構之後,畫二維碼圖,就是使用一定的編碼格式對資料進行編碼,然後填充到相應的網格區域,最後輸出二維碼圖片。詳細的二維碼生成細節和原理,可以看耗子叔這篇文章:https://coolshell.cn/articles/10590.html

三、二維碼識別

一般場景下,移動端 APP 掃碼的解析工作都在移動端裝置上完成,在 Android 中二維碼掃描的最常用庫是 zxingzbar

如果想在 Python 中編寫識別二維碼的程式,同樣也可以 zxing 庫:

# 安裝依賴庫
pip3 install zxing

下面的試驗,左圖是一張沒有背景干擾的圖片,右圖是一張有背景干擾的圖片,同樣使用 zxing 庫來檢測二維碼:
目標檢測:二維碼檢測方案

具體的程式碼實現:

import zxing

reader = zxing.BarCodeRead
barcode = reader.decode("/User/xxx/origin.jpg")
print(barcode.parsed) # output: 二維碼內容

其中,左圖識別輸出 https://cli.im/ ,右圖識別輸出:https://github.com

上面的識別的結果都是對的,但是速度很慢,在 “雙核四執行緒” 機器上,耗時都在 2 ~ 3s 之間。那麼,該如何提升二維碼探測的速度?

OpenCV

通過查資料,找到了 OpenCV 庫,在物件檢測模組中QRCodeDetector 有兩個相關 API 分別實現二維碼檢測與二維碼解析。

# 安裝依賴
pip3 install opencv-contrib-python

具體的 Python 程式碼實現:

import cv2
import sys
import time

img = cv2.imread(infile)
qr = cv2.QRCodeDetector()
result, points, code = qr.detectAndDecode(img)
print("The result: ", result) # result 是二維碼內容
print("The points: ", points) # points 是二維碼在圖片中的座標

同樣使用上面的兩張圖片進行測試:

目標檢測:二維碼檢測方案

在同樣的開發裝置上,可以看到二維碼的探測速度 OpenCV 比 zxing 快了 2 個數量級,但是,當解析有背景干擾的圖片時,無法探測出二維碼內容。

這裡引起了一個問題,如何在有複雜背景下的圖片中識別出二維碼?比如下面這樣的圖片:

目標檢測:二維碼檢測方案

3.目標檢測模型

目標檢測,object detection,就是在給定的圖片中精確找到物體所在位置,並標註出物體的類別。

目標檢測:二維碼檢測方案

圖片來自:https://github.com/aloyschen/tensorflow-yolo3

在這裡,我沒有深入研究 ”目標檢測模型“ ,只是簡單使用了 yolov3 影像識別框架跑了 demo。

yolov3 檢測分兩步:

  • 確定檢測物件位置
  • 對檢測物件分類(是什麼東西)

即在識別圖片是什麼的基礎上,還需定位識別物件的位置,並框出。

下面是 darknet + yolov3 的環境安裝流程:

  • 安裝 darknet
git clone https://github.com/pjreddie/darknet.git
cd darknet
make
  • 使用預先訓練模型測試
# 下載預訓練權重
wget https://pjreddie.com/media/files/yolov3.weights

# 執行命令檢測,沒有使用gpu所以時間有點長
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

yolov3 預訓練模型,僅支援識別影像中一部分物品,不能識別出影像中的 ”二維碼“。如果想要識別出二維碼,需要製作訓練資料集然後訓練模型。

另一方面,如果採用 yolov3 進行圖片中二維碼探測,將需要大量的資料集,人工手動的二維碼標註,還需要具備 GPU 的機器來跑訓練集,這將是一個工作量巨大且耗時漫長的過程。該如何解決訓練模型的問題?

微信二維碼引擎 OpenCV 開源

開源世界總會給人驚喜,微信二維碼引擎居然也開源,文件地址:https://mp.weixin.qq.com/s/pphBiEX099ZkDV0hWwnbhw

目標檢測:二維碼檢測方案

按照文章的程式碼實現:

import cv2

# 探測二維碼
detect_obj = cv2.wechat_qrcode_WeChatQRCode('detect.prototxt', 'detect.caffemodel', 'sr.prototxt', 'sr.caffemodel')
img = cv2.imread(infile)
res, points = detect_obj.detectAndDecode(img)

# 繪製框線
for pos in points:
    color = (0, 0, 255)
    thick = 3
    for p in [(0, 1), (1, 2), (2, 3), (3, 0)]:
        start = int(pos[p[0]][0]), int(pos[p[0]][1])
        end = int(pos[p[1]][0]), int(pos[p[1]][1])
        cv2.line(img, start, end, color, thick)
cv2.imshow('img', img)
cv2.imwrite('wechat-qrcode-detect.jpg', img)

二維碼探測,輸出結果:

目標檢測:二維碼檢測方案

上述圖片的二維碼探測,結果是準確的,耗時卻是驚人的(注意:這是沒有繪製框線的耗時)

目標檢測:二維碼檢測方案

基於 CNN 的二維碼檢測

“一圖多碼” 是掃碼支付經常遇到的線下場景。早在2016年,微信掃碼引擎在業內率先支援遠距離二維碼檢測、自動調焦定位、多碼檢測識別。然而,傳統方法需要犧牲40%以上的效能來支援多碼的檢測與識別。伴隨著深度學習技術的成熟和移動端計算能力的提升,微信掃碼引擎引入基於 CNN 的二維碼檢測器解決上述問題。

目標檢測:二維碼檢測方案

以 SSD 框架為基礎,構造了短小精幹的二維碼檢測器,採用殘差連線(Residual Concat)、深度卷積(Depthwise Convolution)、空洞卷積(Dilated Convolution)、卷積投影(Convolution Projection)等技術進行了針對性的優化。整個模型大小僅943KB,iPhone7(A10)單 CPU 的推理時間僅需20ms,很好地滿足“低延時、小體積、高召回”的業務需求。

四、識別一圖多碼

上面提到,開源的微信二維碼引擎 OpenCV 很好的解決了複雜背景圖的二維碼識別問題,而且同時具備超高的效能,極短的耗時。

針對文章提到的一圖多碼的場景,我們進行驗證,發現在驗證過程中,一圖多碼的識別效果並沒有預期的那麼理想。

目標檢測:二維碼檢測方案

使用 微信二維碼引擎 OpenCV 探測一張包含多個二維碼的圖片時,會出現二維碼識別不全的問題。通過試驗,將圖片切割成多個小圖片,每張切片包含若干個二維碼時,驗證後發現識別效果最好的是一張圖片包含一個二維碼。因此產生下列的 ”滑動視窗單圖多二維碼” 探測方案。

滑動視窗一圖多碼方案

目標檢測:二維碼檢測方案

視窗滑動原理:

  • 第一次滑動:檢測不到二維碼,左邊界不動右邊界右移動,擴大視窗
    目標檢測:二維碼檢測方案

  • 第二次滑動:檢測不到二維碼,左邊界不動右邊界右移動,擴大視窗
    目標檢測:二維碼檢測方案

  • 第三次滑動:檢測到二維碼,左邊界移動到右邊界重合,視窗大小歸 0
    目標檢測:二維碼檢測方案

  • 第四次滑動:檢測不到二維碼,左邊界不動右邊界右移動,擴大視窗
    目標檢測:二維碼檢測方案

  • 第五次滑動:檢測到二維碼,左邊界移動到右邊界重合,視窗大小歸 0
    目標檢測:二維碼檢測方案
    後續的步驟同理,直到將右邊界移動到了圖片的最右端,代表探測結束。

最後,採用 ”滑動視窗一圖多碼探測” 方案的效果圖,準確度還是非常高的:

目標檢測:二維碼檢測方案

當然,這裡只是簡單的演示,裡面還有很多問題,比如:這種方案僅適合二維碼橫向排列的場景,如果同一個列有多張二維碼,就可以被裁剪掉。

have fun!

五、參考文獻

相關文章