最近在B站上看到了 PaperClip 关于二维码的视频,主要讲的是二维码中的纠错码。但是在读取二维码内容前需要先定位并矫正二维码位置,视频中有一个片段展示了二维码定位的流程:RGB转灰度,局部阈值二值化,形态学滤波,Harris角点检测,凸包,提取轮廓,计算顶点角点,透视变换。
说到检测二维码,我的第一反应是“跑一个Faster RCNN,返回bbox同时计算旋转角度”。但这想法立马就被自己否定了,二维码这么明显的结构特征,还需要用神经网络吗?hard code 都可以啊,效率还高得多。于是埋头自己构思了一个检测的方法。(这里我就不对二维码的构成细节进行累述了,直接进入正题。)

基于轮廓嵌套的识别方法

这是我自己设计的一套算法流程,因为只是空闲时间瞎搞搞的东西,想尽量用现成的 API 来做,所以可能有点绕弯子。代码放 Github 了,欢迎斧正。

https://github.com/New2World/LittleProjects/tree/master/QRCodeDetect

因为检测二维码最关键的就是检测三个定位框,而这些定位框最明显的特征有两个:嵌套关系方形。针对这两个特征做文章,先提取轮廓,然后检测三个三层嵌套的方形或平行四边形轮廓。

首先,对图像进行预处理:缩放,灰度,二值化,形态学开操作(先腐蚀后膨胀)。然后通过 OpenCV 的 findContour 函数获取所有轮廓,如下图。
【写着玩】二维码检测及定位
可以看到有很多轮廓嵌套,而其中最特别的就是三个定位框。正如上面所说,它们是方形且嵌套的,那么我们通过返回的 hierarchy 可以计数每个轮廓的嵌套层数。由于我们不能确定每个定位框内部会不会因为噪声或一些其他因素影响而产生额外的轮廓,因此只要轮廓嵌套数大于 3 都可以。

嵌套关系算是考虑到了,然后要做的就是判断是否是近似的矩形。之前做过图像中找圆,基本原理是检查轮廓周长和面积的关系比例是否近似 π\pi。依葫芦画瓢,通过检查周长的平方和面积的比例是否接近 16 来判断是否近似矩形。你可能会问为什么不检查边长相等?当然可以,但是 API 返回的轮廓并不规则。同一条边上可能不止两个端点,而是很多点构成了一条边,这样的话还得判断哪些点在一条边上,麻烦。由于我们嵌套条件是松弛过的,因此在判断矩形时就得把松弛了的条件拉回来,即嵌套的轮廓中必须有三层矩形。通过这两步筛选下来,剩下的轮廓基本就只有三个定位框了。如果剩下的轮廓个数不是三个那就判检测失败。

接下来需要矫正二维码方向。有很多方法可以做到,比如先通过三个定位框构建一个直角三角形,将直角顶点旋转到左上;或者计算三个定位框间的距离,距离相等或相近的边的公共框旋转到左上……但我记得 OpenCV 有个 minAreaRect 函数能找到外接矩形同时返回这个矩形的旋转角度。因为三个定位框基本位于同一平面,所以大致上可以认为它们的旋转角度一样且就是二维码的旋转角度。这里的旋转角度是指将二维码转正的角度,而不是旋转到正确方向的角度,这是接下来要做的。
【写着玩】二维码检测及定位
因为已经把二维码转正了,那么整个二维码可以分为四个象限。假设以左上为原点,垂直的轴为 x 轴,水平为 y 轴,那四个定位框可能的位置可以表示为 [0,0],[0,1],[1,0],[1,1]{[0,0],[0,1],[1,0],[1,1]}。为了方便表示,我们将坐标叠起来写成矩阵:

  • 旋转 0°:{[00][01][10]}    [1,1]\begin{Bmatrix} [0 & 0] \\ [0 & 1] \\ [1 & 0] \end{Bmatrix} \implies [1,1]
  • 旋转 90°:{[00][01][11]}    [1,2]\begin{Bmatrix} [0 & 0] \\ [0 & 1] \\ [1 & 1] \end{Bmatrix} \implies [1,2]
  • 旋转 180°:{[01][10][11]}    [2,2]\begin{Bmatrix} [0 & 1] \\ [1 & 0] \\ [1 & 1] \end{Bmatrix} \implies [2,2]
  • 旋转 270°:{[00][10][11]}    [2,1]\begin{Bmatrix} [0 & 0] \\ [1 & 0] \\ [1 & 1] \end{Bmatrix} \implies [2,1]

通过上面沿着 axis=0 求和的结果进行映射到旋转角度就能将二维码旋转到正确的方向。最后就只需要剪切图片只留下二维码区域即可。这里其实应该做仿射变换来调整二维码可能出现的一些形变,但我省略了……

OpenCV 中的 QRCodeDetector

OpenCV 也提供了一个检测二维码的类 QRCodeDetector。它的算法应该是基于[1]这篇论文的,主要是通过定位框每个黑白部分的长度比进行检测。
【写着玩】二维码检测及定位
在 OpenCV 源码的检测部分有两个函数 searchHorizontalLines 和 extractVerticalLines,先使用水平扫描找出黑白段长度比为 1:1:3:1:1 的部分作为候选,然后用垂直扫描筛选水平扫描得到的结果并返回两个方向上都满足长度比例的部分。接下来就用 KMeans 对所有候选进行聚类,使得最后合并到三个点,即三个定位框的中心点。如果聚类得到的有效的点不足三个,则判检测失败。在之后二维码转正的过程中,OpenCV 用的是检测出的三个定位点的夹角,夹角最大的是左上顶点,然后以此来对图像进行仿射变换。
想要具体了解实现细节还是得看看源码,不过 OpenCV 的识别并不是很完美,目前效果最好的应该是微信的二维码扫描引擎 QBar


[1]: Belussi, Luiz , and N. Hirata . Fast QR Code Detection in Arbitrarily Acquired Images. Sibgrapi Conference on Graphics IEEE, 2012.

相关文章: