相較於BarCode,QRCode有明顯的特徵區域,也就是左上角、右上角、左下角三個”回“字區域,得益於hierarchy中,父子關係的輪廓是連續的(下標),所以這個時候我們就可以透過cv2.findContours()返回的hierarchy來進行定位。
我們直接上程式碼
1 import cv2 2 import numpy 3 4 5 def qrcode(image): 6 # 有些二維碼和邊緣緊貼,無法識別出整個矩形,所以我們先對圖片大小進行擴充套件 7 expand_length = 10 8 edge = expand_length // 2 9 h, w = image.shape[:2] 10 image_extend = numpy.zeros((image.shape[0] + expand_length, image.shape[1] + expand_length, 3), numpy.uint8) 11 image_extend[:] = 255 12 image_extend[edge:edge + h, edge:edge + w] = image 13 14 # 轉灰度、二值化、找輪廓 15 gray = cv2.cvtColor(image_extend, cv2.COLOR_BGR2GRAY) 16 # blur = cv2.GaussianBlur(gray, (5, 5), 0) 17 _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) 18 contours, hir = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 19 20 ''' 21 2.4282182798755647 22 2.3203121154092337 23 2.3487607213520345 24 2.318010267306266 25 ''' 26 27 # 三個“回”字特徵輪廓儲存 28 parent_hierarchy_list = [] 29 parent_contours_list = [] 30 31 # 透過層級資訊去查詢三個“回”字特徵區域 32 for index, item in enumerate(hir[0][:-2]): # 查詢最外層(A)輪廓 33 if item[2] != -1: 34 parent_index = item[2] - 1 35 if hir[0][index+1][3] == parent_index: # 查詢次一層(B)輪廓 36 child_first = hir[0][index+1][2] - 1 37 if hir[0][index+2][3] == child_first: # 查詢最裡層(C)輪廓 38 # 計算A輪廓的周長和C輪廓周長的比值 39 error = cv2.arcLength(contours[parent_index], True) / cv2.arcLength(contours[parent_index + 2], True) 40 if 2 < error < 3: 41 parent_hierarchy_list.append(item) 42 parent_contours_list.append(contours[index]) 43 # 繪製出三個“回”字特徵區域的最外層輪廓 44 cv2.drawContours(image_extend, contours, index, (0, 255, 0), 3) 45 46 # 將整個二維碼區域繪製出來 47 points_list = [] 48 for index, box in enumerate(parent_contours_list): 49 x, y, w, h = cv2.boundingRect(box) 50 if index == 0: 51 points_list.append((x, y+h)) 52 if index == 1: 53 points_list.append((x+w, y)) 54 if index == 2: 55 points_list.append((x, y)) 56 points_list = numpy.array(points_list) 57 rect = cv2.minAreaRect(points_list) 58 box = cv2.boxPoints(rect) 59 box = numpy.int0(box) 60 cv2.drawContours(image_extend, [box], 0, (255, 0, 0), 2) 61 62 cv2.imshow('', image_extend) 63 64 65 if __name__ == '__main__': 66 img = cv2.imread('../images/QRCode_3.png') 67 qrcode(img) 68 cv2.waitKey() 69 cv2.destroyAllWindows()
通常我們所見的二維碼都是有留白邊緣區域的,但是在隨便找一些二維碼圖的過程中,有一些是沒有留白邊緣區域的:
上圖是在IDE中開啟的,原圖是沒有灰色邊緣的,這個時候我們如果直接讀取這張圖片,得到的輪廓資訊並不是我們期待的三個連續的父子關係的hierarchy,為了避免這種情況,這裡就手動向外擴充套件十個畫素,人為製造一個間隔。
通常來說,我們透過三層for迴圈來定位特徵區域已經是足夠的,但是如果二維碼的其他區域也出現了三層輪廓,那麼我們就需要進行篩選,所以程式碼透過計算最外層輪廓的長度和最記憶體輪廓長度的比值來進行篩選,每一個“回”的黑白框框的比例大概為1:1:3:1:1,也就是說他們的邊長比為7:3,而這個比值在標準二維碼中,只有三個特徵區域才符合。
程式碼的21到24行中的數值,便是嘗試過了四個不同的二維碼得出的比值,都接近7:3。
最後我們繪製出四個邊框,完成二維碼的定位: