最近,我們為 opencv_contrib 貢獻了一種條碼識別演算法。在這篇博文中,我們將介紹該演算法以及如何使用它。

關於作者
梁俊豪和王天奇是深圳南方科技大學的本科生。他們的主要研究興趣是計算機視覺。
介紹
條碼是現實生活中識別商品的主要技術。常見的條碼是平行線的圖案,由黑色條和白色條排列而成,它們的反光率和對比度差異很大。條碼識別是指在水平方向上掃描條碼,以獲取由不同寬度和顏色的條組成的一串二進位制程式碼。這些資料是條碼的程式碼資訊。透過與各種條碼編碼方法進行匹配,可以解碼條碼的內容。目前我們的程式碼支援 EAN-13、EAN-8 和 UPC-A 編碼方法。
EAN-13
EAN-13 條碼基於 UPC-A 標準,該標準最初由國際商品編碼協會 (International Item Coding Association) 在歐洲實施,後來逐漸推廣到全世界。生活中大多數普通商品都使用 EAN-13 條碼。有關更多詳細資訊,請參閱 EAN – 維基百科。為了方便起見,我們在下面將以 EAN-13 為例。

如圖 1 所示,一維條碼是一種圖形識別符號,它透過根據某些編碼規則排列不同寬度的多個黑條和空白區域來表達資訊。與其他影像相比,條碼區域具有以下兩個重要特徵:首先,條碼區域中的條和空格呈平行排列,方向趨於一致;其次,為了條碼的可讀性,條和空格之間存在較大的反光率差異,因此條碼區域的灰度對比度大,邊緣資訊豐富。
演算法細節

圖 2 顯示了我們的演算法,它分為三個不同的步驟。首先,我們定點陣圖像中的條碼,然後裁剪和二值化 ROI,最後解碼 ROI。在下面,我們將主要介紹定位和解碼演算法的原理。
條碼定位
條碼的方向一致性是複雜背景下條碼影像中最顯著的特徵。它可以用來檢測可能包含條碼的區域並消除大部分背景。然後,可以根據其他條碼特徵找到精確的條碼區域。我們將描述該演算法的細節。
預處理
首先,我們需要將影像轉換為灰度圖並將其縮放為預設大小。然後,我們使用 Scharr 運算元分別計算 x 和 y 方向的梯度。
方向一致性計算
我們將影像按預設比例劃分為多個塊,然後逐個計算每個塊的方向一致性。我們可以過濾掉梯度一致性低的那些塊,圖 3 中的白色框表示梯度一致性高的塊。

腐蝕
腐蝕操作定義如下。對於每個塊,我們將八個鄰域劃分為 4 組,如圖 4 所示。如果至少有一組包含至少一個鄰域,則保留該塊。

透過腐蝕,我們可以過濾掉孤立的塊。

塊連線
這是最重要的一步。我們需要根據它們的梯度方向連線相鄰的塊,即形成具有數值上類似平均梯度方向的塊區域。然後,我們使用旋轉矩形來擬合連線區域。

條碼解碼
我們參考了 ZXing 條碼解碼演算法,目前支援三種類型的條碼解碼:EAN-13、EAN-8 和 UPC-A。
定位條碼區域後,我們裁剪 ROI 並完成以下過程。
超解析度
為了提高低解析度條碼的解碼效果,我們使用了一種 超解析度模型,該模型用於微信的二維碼識別。不同大小的條碼影像使用不同的超解析度比例。較小的條碼影像將被放大更多。

二值化
在此步驟中,我們使用 Otsu 方法 對條碼影像進行二值化,並首先完成解碼過程。如果解碼失敗,將應用 ZXing 的混合二值化,並將再次嘗試解碼過程。使用這兩種二值化方法可以提高解碼成功率。

解碼
在此步驟中,我們迭代地選擇不同的解碼器 (EAN8、EAN13…) 多次掃描條碼以解碼每個數字並對結果進行投票。一旦某個解碼器返回一個置信度高的結果,此步驟將立即返回結果。

例如,在一次迭代中,解碼器為 EAN-13 格式。圖 9 顯示了掃描線,綠色線表示掃描成功解碼(格式正確但內容正確性無法保證),紅色線表示掃描失敗解碼。只有成功掃描才參與投票。假設在這些成功掃描中,結果 “6922255451427” 出現了 10 次,結果 “6922255412374” 出現了 3 次。EAN-13 解碼器以置信度 10/13 = 0.769 返回結果 “6922255451427”。由於其置信度高於 0.5,我們將立即返回它。否則,它將成為候選者,並進入下一個格式解碼器以獲取置信度最高的結果。
在 OpenCV 中使用條碼識別
C++
#include "opencv2/barcode.hpp"
#include "opencv2/imgproc.hpp"
using namespace cv;
Ptr<barcode::BarcodeDetector> bardet = makePtr<barcode::BarcodeDetector>();
Mat input = imread("your file path");
Mat corners; //the corners of detected barcodes,if N barcodes detected, then the shape is [N][4][2]
std::vector<std::string> decoded_info; //the decoded infos, if fail to decode, then the string is empty
std::vector<barcode::BarcodeType> decoded_format; //the decoded types, if fail to decode, then the type is BarcodeType::NONE
bool ok = bardet->detectAndDecode(input, decoded_info, decoded_format, corners);
Python
import cv2
bardet = cv2.barcode_BarcodeDetector()
img = cv2.imread("your file path")
ok, decoded_info, decoded_type, corners = bardet.detectAndDecode(img)
檢視 示例程式碼 中的更多詳細資訊。
效能
我們將我們的演算法的效能與 ZXing 的效能進行了比較。測試資料在 BarcodeTestDataset 中。我們的測試資料包含許多 小碼 和 傾斜碼,ZXing 無法處理。結果表明,我們的演算法比 ZXing 的準確率高得多,而且似乎我們的演算法更快。但是,ZXing 將大部分時間花費在灰度轉換上,解碼僅花費了 1 毫秒,這意味著 ZXing 快得多。這很容易理解,因為我們的演算法執行了條碼定位和超解析度。


(測試時間不包括初始化和影像讀取,我們的演算法基於 C++,ZXing 基於 Java)
相關資源
本文參考了幾個線上資源,本節列出了這些資源以方便起見,以及一些其他材料






