今天我们将重构代码去检测视频中的条形码。

举个例子,下图是我实验的一个截图,我手持Modern Warfare 3,我们的程序能够顺利检测到其背面的条形码。

图1:使用Python和OpenCV检测视频流中的条形码

注意:非常感谢Jason的建议,他在上次的博客中留言道:要是能检测视频中的条形码将是很酷的事情。你说的很对,这确实很酷。

举个例子,假设我们12月26日这天正在电玩店工作。店外面排起了足有十个街区长的队伍,这些孩子正等着退货或是换货(显而易见,他们的父母或亲戚买错东西了)。

为了加快换货速度,我们就到队伍中间去一个一个去扫描他们的条形码。但是问题来了,激光扫描枪的线是连接到柜台上的电脑上的,队伍可有10个街区长,怎么都够不到。

既然这样,我有个计划:那我们可以用智能手机代替。

使用我们的iPhone(或者Android),带上摄像头,切换到录像模式,向着漫漫长队开工吧。

只要手机摄像头扫描到商品上的条形码,我们的APP就会检测到它的信息并及时传回到柜台。

听起来是不是太美好了,简直难以置信。

也许吧。毕竟,你也可以用激光扫描枪和无线连接来完成这项工作。况且稍后也会看到我们方法的局限性。

但是,我还是觉得这是一个关于使用OpenCV和Python检测条形码的好例子,该例展示了利用OpenCV函数来构建一个真实的应用。

不管怎样,继续往下看,看我们是怎么使用OpenCV和Python来检测视频中的条形码的。

使用Python和OpenCV在视频中实时监测条形码

这就是我们的方案,我们的视频条形码检测系统分成两个部分:

部件1:该模块检测图像中的条形码(本例中,既是视频中的帧图像)。上篇博客中我们已经实现了该功能,现在我们只需要简单修改一下代码即可

部件2:该模块的功能是读取视频源,然后调用条形码检测模块。

我们先讲解第一部分,单帧图像的条形码检测。

部件1:视频帧中的条形码检测

由于上篇博客中已经讲到图像中条形码的检测,这里我就不再熬述。

尽管这样,基于完整性考虑,我快速过一下这部分内容。打开一个新文件,取名simple_barcode_detection.py,开始编码:

Python

# import the necessary packages
importnumpyasnp
importcv2
defdetect(image):
# convert the image to grayscale
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# compute the Scharr gradient magnitude representation of the images
# in both the x and y direction
gradX=cv2.Sobel(gray,ddepth=cv2.cv.CV_32F,dx=1,dy=0,ksize=–1)
gradY=cv2.Sobel(gray,ddepth=cv2.cv.CV_32F,dx=0,dy=1,ksize=–1)
# subtract the y-gradient from the x-gradient
gradient=cv2.subtract(gradX,gradY)
gradient=cv2.convertScaleAbs(gradient)
# blur and threshold the image
blurred=cv2.blur(gradient,(9,9))
(_,thresh)=cv2.threshold(blurred,225,255,cv2.THRESH_BINARY)
# construct a closing kernel and apply it to the thresholded image
kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(21,7))
closed=cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,kernel)
# perform a series of erosions and dilations
closed=cv2.erode(closed,None,iterations=4)
closed=cv2.dilate(closed,None,iterations=4)
# find the contours in the thresholded image
(cnts,_)=cv2.findContours(closed.copy(),cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
# if no contours were found, return None
iflen(cnts)==0:
returnNone
# otherwise, sort the contours by area and compute the rotated
# bounding box of the largest contour
c=sorted(cnts,key=cv2.contourArea,reverse=True)[0]
rect=cv2.minAreaRect(c)
box=np.int0(cv2.cv.BoxPoints(rect))
# return the bounding box of the barcode
returnbox

如果你看过之前的博客,那么这段关于在图像中检测条形码的代码将非常熟悉。

第一件事就是导入NumPy库来做数值计算,cv2来调用OpenCV。

第5行定义的函数带1个参数,就是需要检测条形码的图像。

第7行将图像转换为灰度图像,9-16行检测图像中具有水平梯度高和垂直梯度低的区域(想了解更多,请参看前面条形码检测的博客)。

19和20行分别模糊图像和二值化图像,方便后续23-28行的形态学处理。形态学操作用来检出条形码的矩形区域,忽略其他区域。

现在我们已经有了条形码的矩形区域,接下来在30-31行就是找出其轮廓。

如果没有找到轮廓,那么就认为没有条形码(35和36行)。

如果找到了轮廓,在40行我们就对轮廓进行排序(轮廓按面积大小降序排列)。这里,我们同样假设面积最大的轮廓就是条形码的位置。

最后,我们拿到这个轮廓,然后计算该轮廓的矩形包络(41和42行)。然后45行就返回该包络的x,y坐标,意即条形码的位置。

现在,我们这个简单的条形码检测就告完成,步入第二部分,图像获取,然后整合第一部分。

部件2:访问摄像头,检测视频中的条形码

我们开始构建一个驱动接口以便后面检测视频中的条形码。打开并新建一个文件,取名detect_barcode.py,开始构建第二个部件:

Python

# import the necessary packages
frompyimagesearchimportsimple_barcode_detection
importargparse
importcv2
# construct the argument parse and parse the arguments
ap=argparse.ArgumentParser()
ap.add_argument(“-v”,“–video”,help=“path to the (optional) video file”)
args=vars(ap.parse_args())
# if the video path was not supplied, grab the reference to the
# camera
ifnotargs.get(“video”,False):
camera=cv2.VideoCapture(0)
# otherwise, load the video
else:
camera=cv2.VideoCapture(args[“video”])

我们开始也是导入我们需要的库。出于方便管理的目的,我们将simple_barcode_detection函数放到pyimagesearch模块中,并在此导入。然后我们导入argparse来解析命令,导入cv2来调用OpenCV的函数。

6-9行解析我们的命令行参数。我们设置了一个可选项,–video,该选项指定需要检测条形码的视频文件路径。

注意:这个切换选项对于运行源代码提供的例子视频还是很有用的。忽略这个选项默认就是用笔记本或是台式机上的摄像头。

13-18行获取一个camera引用,如果没有–video选项,那么就获取一个webcam;反之就活去一个视频文件句柄(17-18行)。

设置完成后,我们就可以开始加载我们的条形码检测模块了。

Python

# keep looping over the frames
whileTrue:
# grab the current frame
(grabbed,frame)=camera.read()
# check to see if we have reached the end of the
# video
ifnotgrabbed:
break
# detect the barcode in the image
box=simple_barcode_detection.detect(frame)
# if a barcode was found, draw a bounding box on the frame
cv2.drawContours(frame,[box],–1,(0,255,0),2)
# show the frame and record if the user presses a key
cv2.imshow(“Frame”,frame)
key=cv2.waitKey(1)&0xFF
# if the ‘q’ key is pressed, stop the loop
ifkey==ord(“q”):
break
# cleanup the camera and close any open windows
camera.release()
cv2.destroyAllWindows()

第2行我们开始循环抓取视频中的图像帧,直到抓取完视频中的图像或者说我们按下q键然后退出循环。

第5行中,我们访问camera。返回值是2-tuple.其一是boolean值,表示是否抓取成功(从摄像头或者视频文件)。frame这是实际抓取到的图像数据。

如果抓取失败(例如到了视频文件尾部),11和12行就退出循环。

一旦抓取到图像帧,在16行我们就利用条形码检测模块检测图像中的条形码,返回外接矩形范围。

我们将最终的包络区域绘制在图像中(20行),最后显示到屏幕上(23和24行).

最后,29和30行则检测q键是否按下,按下就退出循环。32和33行就进行camera对象的清理操作。

如你所见,我们的脚本还是比较简洁的。开始运行我们的代码,看看结果如何。

成功检测视频中的条形码

我们试一些例子,打开终端,输入如下指令:

Python

$pythondetect_barcode.py—videovideo/video_games.mov

本博客的开始部分就展示了我们脚本的输出结果。下面则是三个成功的条形码检测视频截图。


图2:在Xbox视频游戏流中,成功检测条形码

我们看一个在衣服优惠券上检测的例子:

Python

$pythondetect_barcode.py—videovideo/coupon.mov

如下是一个视频流截图:


图3:使用Python和OpenCV成功检测条形码的示例

完整视频输出:

当然,如我之前所说,我们的方法只在很好的条件下才有效(稍后会详述该方法的缺点和限制)

这就是一个条形码检测失败的例子。


图4:一个失败的例子。条形码离摄像头太远了

该种情形下,条形码离摄像头太远,图像中也有太多的干扰和噪声,例如视频游戏盒子上有很多的大块的文本区。

这个也是个失败案例,我只是想开个玩笑而已。


图5:我的耳朵当然不是条形码

再一次说,我们这只是一个很简单的解决办法,并不适用于所有情况。虽说不是一个健壮的解决方案,但如果能满足下述条件的情况下,简单的图像处理还是能提供让人惊讶的够好的结果。

限制条件和缺点

就如博文中见到的,我们的方法在一定假设条件下还是能顺利检测到视频中的条形码。

第一个假设:我们的相机以90度的静态视角来观察这个条形码。以使条形码区域的图像梯度能够被条形码检测器检测到。

第二个假设:我们的视频或者摄像头是近距离查看条形码,也就是我们拿着智能手机直接对着条形码的上面,而不是远离摄像头。条形码离相机越远,成功检测的概率就越小。

那么怎样改进我们的条形码检测器呢?

好问题

Christoph Oberhofer 提供了一篇很好的综述,关于在QuaggaJS中如何实现条形码的鲁棒检测。 Tomasz Malisiewicz也写了一篇很好的文章,关于如何利用他的VMX中的机器学习算法来训练出一个条形码检测器。如果你想更近一步了解这些内容,请一定要看这些文章。

总结

基于之前在图像中检测条形码的程序,本文中我们将代码分为两部分:

一个部件就是检测视频帧的条形码

另一个则是访问视频源(摄像头或者视频文件)

然后使用条形码检测模块检测视频中的条形码。

我们的方法有如下假设:

第一个假设就是我们摄像头的向下看的静态视角为90度。

第二个假设是我们会拉近看图像中的条形码,且图像中没有其他干扰物或噪声。

事实上,能否满足这些假设全取决于你的实际应用场景。

至少,我想通过这篇博文给你说明的是一些基本的图像处理技术,以及如何采用Python和OpenCV,实现这些图像处理技术来构成一个简单的条形码检测器。