用OpenCV识别一维条码

1,186 阅读6分钟

最近,我们为opencv_contrib贡献了一个条形码识别算法。在这篇博文中,我们将介绍该算法以及如何使用它。


关于作者。
梁俊豪和王天琦是深圳南方科技大学的本科生。他们的主要研究兴趣是计算机视觉


简介

条形码是现实生活中识别商品的一种主要技术。 常见的条形码是由反射率和对比度大不相同的黑条和白条排列的平行线图案。条形码识别是在水平方向上扫描条形码,得到一串由不同宽度和颜色的条组成的二进制代码的过程。这些数据就是条形码的代码信息。条码的内容可以通过与各种条码编码方法相匹配来解码。目前我们的代码支持EAN-13、EAN-8和UPC-A编码方法。

EAN-13

EAN-13条码是在UPC-A标准的基础上,由国际物品编码协会首先在欧洲实施,后来逐渐推广到全世界。生活中的大多数普通商品都使用EAN-13条码。更多详情请见 EAN - 维基百科.为方便起见,我们在下文中以EAN-13为例。

图1:EAN-13条码

如图1所示,一维条码是一种图形标识符,它通过按照一定的编码规则排列多个黑条和不同宽度的空白来表达信息。与其他图像相比,条码区有以下两个重要特点。第一,条码区的条和空是平行排列的,方向趋于一致;第二,为了条码的可读性,条和空之间存在较大的反射率差异,因此,条码区的灰色对比度大,边缘信息丰富。

算法的细节

图2:我们代码的结构

图2显示的是我们的算法,以离散的三个步骤组织。首先,我们在图像中定位条形码,然后裁剪和二进制化ROI,最后解码ROI。在下文中,我们主要介绍定位的原理和解码算法。


条形码的定位

条码的方向一致性是复杂背景下条码图像中最明显的特征。它可以用来检测可能包含条形码的区域并消除大部分背景。然后,根据其他条码特征可以找到精确的条码区域。我们将描述这个算法的细节。

预处理

首先,我们需要将图像转换为灰度,并将其缩放到一个预设的尺寸。然后,我们使用Scharr算子来分别计算xy方向的梯度。

方向性相干性计算

我们按照预设的比例将图像分成几个斑块,然后逐个计算方向一致性。我们可以过滤掉那些梯度相干性低的斑块,图3中的白框表示梯度相干性高的斑块。

图3:高梯度一致性的斑块

腐蚀

腐蚀操作的定义如下。对于每个补丁,我们将8个邻域分成4组,如图4所示。如果有一半以上的组至少有一个邻域,我们就保留这个补丁。

图4:侵蚀

通过侵蚀,我们可以过滤掉孤立的斑块。

图5:侵蚀后的斑块

斑块连接

这是最重要的一步。我们需要根据它们的梯度方向来连接相邻的斑块,也就是形成平均梯度方向在数值上相似的斑块区域。然后,我们用一个旋转的矩形来适应连接区域。

图6:条形码的边界框


条形码解码

我们指的是 ZXing条码解码算法,目前支持三种类型的条码解码。EAN-13, EAN-8, 和UPC-A。

定位条形码区域后,我们裁剪ROI并完成以下过程。

超级分辨率

为了提高低分辨率条码的解码能力,我们使用了一个 超分辨率模型这是在微信的二维码识别中使用的。对于不同大小的条码图像,采用不同的超分辨率比例。小的条码图像将被放大更多。

图7:超级分辨率

二进制化

在这一步中,我们使用大津的方法对条码图像进行二值化,首先完成解码过程。如果解码失败,将应用ZXing的混合二值化,再次尝试解码过程。同时使用这两种二值化方式可以提高解码的成功率。

图8:二值化

解码

在这一步,我们迭代地选择不同的解码器(EAN8、EAN13......)多次扫描条形码,对每个数字进行解码,并对结果进行投票。一旦其中一个解码器返回一个高置信度的结果,这个步骤将返回结果。

图9:扫描线

例如,在一次迭代中,解码器是EAN-13格式。图9显示了扫描线,绿色线代表成功解码的扫描(格式正确,但内容的正确性不能保证),红色线代表不成功解码的扫描。只有成功的扫描才会参与投票。假设在这些成功的扫描中,结果 "6922255451427 "出现10次,结果 "6922255412374 "出现3次。EAN-13解码器给出的结果是 "6922255451427",置信度为10/13 = 0.769。由于它的置信度高于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的算法的性能。测试数据是在 条码测试数据集.我们的测试数据包含很多小码倾斜码,而ZXing无法处理。结果显示,我们的算法比ZXing取得了更高的准确性,而且看起来我们的算法更快。然而,ZXing花了大部分时间做灰度转换,而解码只花了1ms,这意味着ZXing的速度更快。这还是很容易理解的,因为我们的算法是做条码定位和超分辨率的。

图10:准确度比较

图11:速度比较

(测试时间不包括初始化和图像读取,我们的算法是基于C++的,而ZXing是基于Java的)


相关资源

本文提到了一些在线资源,为方便起见,本节列出了这些资源,以及一些补充材料。

  1. GitHub上的条码模块
  2. 关于OpenCV条形码的文档
  3. 关于OpenCV条形码的教程
  4. 国际文章编号 -维基百科
  5. GitHub上的ZXing项目
  6. 用于超级分辨率的CNN模型
  7. 条码测试数据集

The postRecognizing one-dimensional barcode using OpenCVappeared first onOpenCV.