最近,我们为 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)
相关资源
本文参考了几个在线资源,本节列出了这些资源以方便起见,以及一些其他材料