导读:计算机视觉专家Adrian Rosebrock近日分享了如何借助OpenCV和Zbar,编写出能够实时识别二维码和条形码的扫描程序,最后部署在树莓派上,成功制作一款实用的条形码&二维码扫描设备。
最近有朋友问我(作者Adrian Rosebrock——译者注)OpenCV里有没有什么模块能直接识别条形码和二维码,很遗憾,答案是没有。但是OpenCV能够加快读取条形码和二维码的过程,包括从硬盘加载图像,从视频流中抓取新的帧,并进行处理。 等我们获取图像或视频帧后,就可以将其传入Python中专用的条形码解码库,比如Zbar。 然后Zbar会对条形码或二维码进行解码。OpenCV可以接着执行进一步的图像处理工作以及展示结果。 听起来有些复杂,其实整个处理过程相当简单明了。程序库Zbar也衍生了很多变体,其中pyzbar是我的最爱。
在本文,我会教你怎样用OpenCV和Zbar读取条形码和二维码。而且,我还会展示怎样将我们制作的这个条形码&二维码扫描仪部署到树莓派上!!
使用OpenCV和ZBar打造一款条形码及二维码扫描仪
本文主要分为四部分。
安装Zbar(带Python绑定)用于解码条形码&二维码
前段时间Staya Mallick在LearnOpenCV博客上发表了一篇实用教程,讲解如何用Zbar扫描条形码。
本文关于Zbar安装部分基本上是根据这篇博文的指导,但是做了一点改进,主要是围绕安装Python Zbar绑定部分,目的是确保我们能:
使用Python3(官方Zbar Python绑定只支持Python 2.7) 准确地检测和定位图像中二维码及条形码
安装所需的软件,只需简单三步。
第一步:从apt或brew库中安装Zbar
在Ubuntu或树莓派上安装Zbar
$ sudo apt-get install libzbar0
在MacOS系统中安装Zbar
使用brew在macOS系统中安装Zbar也很容易(假定你已经安装了Homebrew):
$ brew install zbar
第二步:创建一个虚拟环境,安装OpenCV。
这里你有俩个选择: 使用现成的已经安装好了OpenCV的虚拟环境(跳过这一步,看第三步)。 或者创建一个新的独立的虚拟环境,安装OpenCV。
虚拟环境对于Python开发来说是非常实用的做法,我非常鼓励使用虚拟环境。
我选择创建一个新的独立的Python 3 虚拟环境,然后安装了OpenCV,并将环境命名为barcode:
$ mkvirtualenv barcode -p python3
注:如果你已经安装好了OpenCV,就可以跳过OpenCV编译过程,只需将你的cv2.so绑定符号链接(sym-link)入你的新Python虚拟环境中的site-pakages目录。
第三步:安装Pyzbar
现在我已经安装了Python 3 虚拟环境,命名为barcode,然后激活了barcode环境,安装pyzbar:
$ workon barcode
$ pip install pyzbar
如你不是用的Python 虚拟环境,只需:
$ pip install pyzbar
如果想将pyzbar安装到Python版系统中,确保你也使用sudo命令。
用OpenCV解码单张图像上的条形码和二维码
在我们实现能实时读取条形码和二维码之前,我们首先创建一个单张图像扫描仪练练手。
打开一个新文件,命名为barcode_scanner_image.py,插入如下代码:
# 导入所需工具包
from pyzbar import pyzbar
import argparse
import cv2
# 构建参数解析器并解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to input image")
args = vars(ap.parse_args())
在第2-4行代码,我们导入了所需的工具包。
需要按照上一部分的指令安装pyzbar和cv2(OpenCV)。不过,在Python安装中包含了argparse,负责解析命令行参数。
对于该脚本我们有一个必需的命令行参数(--image),在7-10行进行解析。
你会在这部分结尾处看到在传入包含输入图像路径的命令行参数时,如何运行这里的脚本。
现在,我们获取输入图像,运行pyzbar:
# 加载输入图像
image = cv2.imread(args["image"])
# 找到图像中的条形码并进行解码
barcodes = pyzbar.decode(image)
在第13行,我们通过图像的路径(包含在我们很方便的args目录中)加载图像。
从这里,我们调取pyzbar.decode来发现和解码图像中的条形码(第16行)。
我们还没完成——现在我们需要解析包含在barcode变量中的信息:
# 循环检测到的条形码
for barcode in barcodes:
# 提取条形码的边界框的位置
# 画出图像中条形码的边界框
(x, y, w, h) = barcode.rect
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
# 条形码数据为字节对象,所以如果我们想在输出图像上
# 画出来,就需要先将它转换成字符串
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
# 绘出图像上条形码的数据和条形码类型
text = "{} ({})".format(barcodeData, barcodeType)
cv2.putText(image, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 0, 255), 2)
# 向终端打印条形码数据和条形码类型
print("[INFO] Found {} barcode: {}".format(barcodeType, barcodeData))
# 展示输出图像
cv2.imshow("Image", image)
cv2.waitKey(0)
从19行开始,我们循环检测到的barcodes。 在这里的循环中,我们继续:
从barcode.rect对象(22行)提取边界框(x,y)坐标,这样能让我们定位和确定当前条形码在输入图像的位置。
围绕着检测到的barcode(第23行),在图像上画出边界框。
将barcode解码为“utf-8”字符串,提取barcode的类型(第27行和28行)。调取.decode(“utf-8”)函数将对象从字节数组转换为字符串,非常关键。你可以通过删除或添加注释,试验一下结果。
在图像上格式化和绘制barcodeData和barcodeType(第31-33行)。
最后,输出同样的数据,朝终端输入信息以进行调试(第36行)。
我们测试一下搭建的OpenCV条形码扫描仪。 从这里,打开你的终端,执行如下命令:
$ python barcode_scanner_image.py --image barcode_example.png
[INFO] Found QRCODE barcode: {"author": "Adrian", "site": "PyImageSearch"}
[INFO] Found QRCODE barcode: https://www.pyimagesearch.com/
[INFO] Found QRCODE barcode: PyImageSearch
[INFO] Found CODE128 barcode: AdrianRosebrock
可以在终端中看到,全部4个条形码均被正确的发现和解码!
{"author": "Adrian", "site": "PyImageSearch"} (二维码自动识别)
如图所示,识别出了图像中的条形码和二维码,以红框标出,并显示出了它们包含的信息。
用OpenCV实时读取条形码和二维码
在前面部分中,我们学习了如何为单张图像创建一个Python+OpenCV条形码扫描仪。
我们的条形码和二维码扫描仪效果很好——但是问题来了,我们能实时检测和解码条形码+二维码吗?
我们试试看。打开一个新文件,命名为barcode_scanner_video.py,插入如下代码:
# 导入所需工具包
from imutils.video import VideoStream
from pyzbar import pyzbar
import argparse
import datetime
import imutils
import time
import cv2
# 创建参数解析器,解析参数
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--output", type=str, default="barcodes.csv",
help="path to output CSV file containing barcodes")
args = vars(ap.parse_args())
在第2-8行,我们导入了所需的工具包。 这里回想一下上面的解释,你应该识别pyzbar,argparse和cv2. 我们会使用VideoStream以高效和单线程的方式处理获取的视频帧。如果你的系统中没有安装imutils,只需使用如下命令:
$ pip install imutils
我们接着解析一个可选的命令行参数--output,其包含了指向输出结果CSV文件的路径。该文件会包含从视频流中检测到和解析出的条形码的时间戳及载荷。如果该参数没有指定,那么CSV文件就会被我们当前名为“barcodes.csv”的工作目录所替换(第11-14行)。
在这里,我们初始化视频流,打开CSV文件:
# 初始化视频流,让摄像头热热身
print("[INFO] starting video stream...")
# vs = VideoStream(src=0).start()
vs = VideoStream(usePiCamera=True).start()
time.sleep(2.0)
# 打开输出CSV文件,用来写入和初始化迄今发现的所有条形码
csv = open(args["output"], "w")
found = set()
在第18-19行,我们初始化和启动了视频流,你可以: 使用自己的USB网络摄像头(无注释行第18行及注释行第19行) 或者如果你是使用树莓派的话(和我一样),可以用PiCamera(无注释行第19行及注释行第18行)。
我选择使用我的树莓派 PiCamera,下部分会说到。
然后我们等上几秒钟,让摄像头热热身(第20行)。
我们会将发现的所有条形码和二维码以CSV文件写入硬盘(确保不要写重复)。这里只是一种记录条形码的例子,当然你可以按照自己的喜好来,比如检测到条形码后,将它们读取为:
实际操作随意,我们只是用CSV文件作为示例。
我们在第24行代码打开CSV文件以写入硬盘。如果你修改了代码以添加到文件,你可以只需将第二个参数从“w”改为“a”(但是你后面只能换种方式搜索重复文件)。
我们也初始化一个set用于found条形码。这个set会包含独一无二的条形码,防止出现重复。
我们开始获取和处理视频帧:
# 循环来自视频流的帧
while True:
# 抓取来自单线程视频流的帧,
# 将大小重新调整为最大宽度400像素
frame = vs.read()
frame = imutils.resize(frame, width=400)
# 找到视频中的条形码,并解析所有条形码
barcodes = pyzbar.decode(frame)
在第28行,我们开始循环,继续抓取来自视频流中的frame,并调整大小(第31和32行)。
在这里,我们调取pyzbar.decode以检测和解码frame中的全部条形码和二维码。
我们接着循环检测到的barcodes:
# 循环检测到的条形码
for barcode in barcodes:
# 提取条形码的边界框位置
# 绘出围绕图像上条形码的边界框
(x, y, w, h) = barcode.rect
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
# 条形码数据为字节对象,所以如果我们想把它画出来
# 需要先把它转换成字符串
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
# 绘出图像上的条形码数据和类型
text = "{} ({})".format(barcodeData, barcodeType)
cv2.putText(frame, text, (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 如果条形码文本目前不在CSV文件中, write
# 就将时间戳+条形码 to disk and update the set
if barcodeData not in found:
csv.write("{},{}\n".format(datetime.datetime.now(),
barcodeData))
csv.flush()
found.add(barcodeData)
如果看看前面的循环部分,你会发现这里循环和之前的很像。
实际上,第38-52行和前面识别单张图像的脚本是一样的。这部分代码详情解释,参见单张图像条形码检测和扫描部分。
第56-60行代码比较新。在这些行代码中,我们是检查是否发现了独有的(此前没有发现)条形码(第56行)。
如果是这种情况,我们将时间戳和数据写成CSV文件(第57-59行)。此外还可以将barcodeData添加到found集,作为一种处理重复文件的简单方法。
在实时条形码扫描脚本的剩余代码行中,我们展示视频帧,检查是否按退出键,并进行清除:
# 展示输出帧
cv2.imshow("Barcode Scanner", frame)
key = cv2.waitKey(1) & 0xFF
# 如果按下”q”键就停止循环
if key == ord("q"):
break
# 关闭输出CSV文件进行清除
print("[INFO] cleaning up...")
csv.close()
cv2.destroyAllWindows()
vs.stop()
在第63行,我们展示输出帧。
然后在第64-68行,我们检查是否按了“q”,执行主循环。
最后,我们在第72-74行执行清除。
在树莓派上部署条形码和二维码扫描仪
我决定使用树莓派、触摸屏和一个充电宝打造一款自己的实时条形码扫描仪。
下图显示的是我的组装成果。如果你也想自己做一个,以下是需要的部件:
很容易就能组装好。
在这里,打开你树莓派上的终端,用如下命令启动应用(这一步需要一个键盘/鼠标,但是后面就用不着了):
$ python barcode_scanner_video.py
[INFO] starting video stream...
等一切准备就绪后,就可以将条形码展示给摄像头了,可以打开barcode.csv文件(或者如果你愿意,也可以在另一个终端上执行tail -f barcodes.csv,查看打开CSV文件时的数据)。
我首先向摄像头展示了一个黑色背景上的二维码,Zbar很轻松的检测到了它:
然后又在我家厨房里用这套装置发现了另一个二维码:
成功了!而且能多角度扫描识别二维码。
现在,我们试试一个包含了json-blob数据的二维码:
最后,我试了试传统的1维条形码:
1维的条形码对于我们这套系统来说略微难些,因为摄像头不支持自动对焦。但是最后还是成功的检测和解码了条形码。
如果你用有自动对焦功能的USB网络摄像头的话,效果要好得多。
结语
在本文,我们讨论了怎样用OpenCV和Python库Zbar打造一款条形码和二维码扫描仪。
将Zbar和OpenCV安装后,我们创建了两个Python脚本:
在这两种情况中,我们都使用了OpenCV来加快进程。
最终,我们将创建好的程序部署到树莓派上,并且能成功实时识别条形码和二维码。
可以自己试着去做一条这样的条形码&二维码扫描仪,项目源代码下载
参考资料:www.pyimagesearch.com