Project-LocatingQRcode

概述

  本周的数字图像处理课上,老师简单地提了一下二维码定位的整体思路,其实之前稍微了解过一点二维码的东西,但是对于其细节没有很具体的学习,所以借此机会自己写一遍二维码定位和提取的实现,加深下对其的印象。

相关知识

Qrcode

  关于二维码的知识学习自网上的博客,与QRcode二维码具体知识点可以从CallMeWhy’s Blog学习,此处仅列举QRcode的各部分名称,方便思路介绍的时候进行说明。
  以一种size的QRcode进行举例说明,各部分名称如下:
              QRcode_img_1
  定位QRcode可以用到的部分包括Position_Detection_Pattern,Separators,Timing_Pattern。其中利用Position_Detection_Pattern可以快速检测二维码位置,Timing_Pattern由于其黑白相间的特性,用于修正坐标系,也可以用来排除图片中可以出现的另外的QRcode的Position_Detection_Pattern部分,保证QRcode定位的正确性。

contours

  轮廓(contours)相关的知识涉及到opencv中的具体实现,原文见官方文档

思路整理

目的

  本程序的目的是识别出图像中的QRcode,并且将QRcode从原图像是裁剪出来。假设的情况是图片中仅有一个二维码,旋转角度不超过90度,此二维码完整未损坏,并且图像中不包含其他二维码的Position_Detection_Pattern部分。

基本步骤

  基本步骤是先利用Position_Detection_Pattern的特征将其提取出来,然后再利用Position_Detection_Pattern将完整的QRcode区域识别处理;由于照片中的二维码可能出现旋转的情况,因此在定位二维码之后应该计算出二维码偏转的角度,并纠正二维码位置为水平,之后将二维码裁剪出来。

具体步骤

Position_Detection_Pattern 定位

  Position_Detection_Pattern的定位方法首先参考的是CallMeWhy’s Blog中使用的方法,并在其实现基础上进行改进。
  第一步是提取图片中的轮廓,步骤是先将图片变为灰度图,之后进行高斯模糊,然后利用Canny算子进行边界检测,然后利用opencv中的findContours函数进行轮廓的查找,照此思路实现如下:

1
2
3
4
5
6
7
8
# change img into gray
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Gaussian
gb_img = cv2.GaussianBlur(gray_img,(5,5),0)
# Canny
edge_img = cv2.Canny(threshold_img,100,200)
# ! find Position Detection Pattern
img_fc, contours, hierarchy = cv2.findContours(edge_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

  第二步是在第一步轮廓提取的基础上,利用Position_Detection_Pattern的性质,既有6层的轮廓,在图片全局范围内进行搜索,找到有6层轮廓的区域的最外层,其为Position_Detection_Pattern,此处对于每个轮廓进行深度判断即可,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ! find Position Detection Pattern
hierarchy = hierarchy[0]
found = []
for i in range(len(contours)):
k = i
c = 0
while hierarchy[k][2] != -1:
k = hierarchy[k][2]
c = c + 1
if c >= 5:
found.append(i)
PDP_img = img.copy()
PDP = []
for i in found:
PDP.append(contours[i])

  其中PDP即为所搜索的Position_Detection_Pattern最外围轮廓。
  这个思路一开始看是没有问题的,并且test2图片的测试样例通过了,但是在测试test1图片中出现,即只找到Position_Detection_Pattern中的两个,没有找到第三个,识别后图像及边缘灰度图如下:
QRcode_error_1
  观察未识别的Position_Detection_Pattern,发现其未被识别的原因是一个Position_Detection_Pattern中颜色差异过大,不同色块差异明显,因此在边缘提取的时候在一个Position_Detection_Pattern内识别出了其他的边缘曲线,因此改变了其原有的轮廓嵌套关系,导致其识别失败。因此考虑在识别Position_Detection_Pattern的第一步中添加阈值化处理。一开始选择的阈值化方法是单一的THRESH_BINARY阈值化,但是这样做的效果反而更糟了,错误的改变了其他区域的轮廓嵌套关系,使其识别出其他不规则图形,错误识别的结果如下:
QRcode_error_2
  考虑原因是THRESH_BINARY方法下的阈值参数设置的有问题,但是不同的图像阈值设置显然不能完全一样,所以考虑使用自适应阈值方法,使用阈值化的处理的位置应该在边缘检测之前,实现如下:

1
(T, threshold_img) = cv2.threshold(gb_img, 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY)

  在添加自适应阈值化之后,Position_Detection_Pattern识别正确,效果如下:
            Qrcode_success_PDP

QRcode 定位

  在实现Position_Detection_Pattern的定位之后,可以利用Timing_Pattern进行Position_Detection_Pattern识别的检测,但是考虑到本程序识别的对象不包括很特殊的情况,因此不在利用Timing_Pattern进行检测,而是直接利用Position_Detection_Pattern进行QRcode轮廓的定位。
  若不考虑Position_Detection_Pattern的检测,其实找到QRcode外轮廓的方法还是比较简单的,直接找到3个Position_Detection_Pattern的最小包围盒即可实现QRcode的定位,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
QRcode_location_img = img.copy()
all_PDP = []
for i in range(3):
temp_PDP = PDP[i]
for sublist in temp_PDP:
for point in sublist:
all_PDP.append(point)

all_PDP = np.array(all_PDP)
rect = cv2.minAreaRect(all_PDP)
box_QR = cv2.boxPoints(rect)
box_QR = np.array(box_QR)

cv2.polylines(QRcode_location_img,np.int32([box_QR]),True,(0,255,0),10)

  实现效果如下:
            QRcode_succcess_1

旋转和裁剪

  实现的思路是先找到偏转的角度进行旋转,然后裁剪即可。
  需要旋转的角度即为QRcode边缘矩形的下边缘与水平线之间的夹角。我采用的了下边缘的两个端点进行角度的计算,公式如下:

  计算如下:

1
2
3
4
5
6
7
# count the angle which img should rotate
left_down_point = location_QR[0]
right_down_point = location_QR[3]
angle_tan = (right_down_point[1] - left_down_point[1]) / (right_down_point[0] - left_down_point[0])
temp_angle = math.atan(angle_tan) * 180 / math.pi
if (abs(temp_angle - angle) > 0.01):
angle = temp_angle

  利用计算得到的角度进行旋转即可。需要注意的是,为了裁剪时候坐标计算的方法,旋转中心选择为QRcode矩形的中心,这样在旋转过后,直接利用位置不变的旋转中心和二维码轮廓的边长进行图像的裁剪即可,裁剪实现如下:

1
2
after_cut_img = after_rotation_img[int(center_y - QRcode_weight/2) : int(center_y + QRcode_weight/2),
int(center_x - QRcode_weight/2) : int(center_x + QRcode_weight/2)]

  至此QRcode的定位及提取就已经完成了,贴上两个测试用例的结果:
  无旋转测试用例:
QRcode_success_finish_1
  有旋转测试用例:
QRcode_success_finish_2

代码实现

  完整的代码实现可以从我的github中找到:SuperYanyann/locatingQRcode

小结

  这个小project中没有涉及复杂图像的情况,没有用到其他的检测的方法,并且这个项目中利用到的数学方法也比较简单,所以整体难度不是很大。不过由于这个项目只是实现了QRcode各个流程中的第一步,因此在以后的学习中可以继续对于此项目进行拓展和完善。

参考

[1] CallMeWhy’s Blog
[2] opencv官方文档
[3] 我i智能的博客
[4] kellen_f的博客