關(guān)于二維碼生成與識別,網(wǎng)上有很多資料,生成是比較容易的python相關(guān)的庫也很多养匈,但是識別才是技術(shù)難度比較高的,這里指的是復(fù)雜背景下的二維碼快速識別都伪;所謂復(fù)雜背景就是說二維碼占畫幅比例很小呕乎、存在扭曲、變形陨晶、模糊猬仁,比如隨手拍的照片上二維碼,這種情況下往往很隨意的角度先誉,給二維碼識別帶來不小困難湿刽。這兩天查了不少資料也對比了很多開源實(shí)現(xiàn),但沒有找到可以媲美微信掃一掃識別效果的方案褐耳,不得不說這種成熟的商用產(chǎn)品即便是一個很小的細(xì)分功能也是下了不少功夫的诈闺。
我也研究了下,這里給出我自己的初步方案漱病。
基于yolov5網(wǎng)絡(luò)的二維碼定位 ===> 二維碼提取 ===> 二維碼矯正===> 二維碼圖像增強(qiáng) ===> pyzbar識別
我面對的場景是像下面這樣的圖片:
這種圖片如果直接用zbar識別的話买雾,不僅耗時(shí)較長而且識別率極低;但是如果直接形態(tài)學(xué)處理干擾又太多杨帽,因此這里先用目標(biāo)檢測網(wǎng)絡(luò)yolov5做定位漓穿。
- yolo的訓(xùn)練這里就不再贅述了,訓(xùn)練數(shù)據(jù)是從網(wǎng)絡(luò)上找的各種二維碼圖片
識別效果很好幾乎100%注盈,需要訓(xùn)練數(shù)據(jù)或者訓(xùn)練好的代碼及模型的私信聯(lián)系我晃危。以下著重介紹定位后的預(yù)處理。定位并提取后的二維碼如下:
- 盡管已經(jīng)提取了二維碼老客,但直接用pyzbar識別效果非常差僚饭,識別率不到10%,因此考慮對這些圖像進(jìn)行矯正胧砰。
以下面這張?zhí)崛『蟮亩S碼為例:
首先將圖像放縮到統(tǒng)一大小鳍鸵,然后識別二維碼邊界,最后進(jìn)行仿射變換矯正圖像尉间,代碼如下:
import cv2
import imutils
from skimage import measure
import numpy as np
image = cv2.imread('image.png')
image = cv2.resize(image, (600, 600))
height, width = image.shape[:2]
#size = (int(width * 0.25), int(height * 0.25))
#shrink = cv2.resize(image, size, interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite('gray.jpg',gray)
ret2, image_binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
ret, binary = cv2.threshold(gray, ret2 * 0.85, 255, cv2.THRESH_BINARY)
#ret, binary = cv2.threshold(gray, 135, 255, cv2.THRESH_BINARY)
cv2.imwrite('binary.jpg',binary)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (80, 80))
iOpen = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
iClose = cv2.morphologyEx(iOpen, cv2.MORPH_CLOSE, kernel)
cv2.imwrite('iClose.jpg',iClose)
# cv2.imwrite('tempcolse.jpg',iClose)
img = 255 - iClose
cv2.imwrite('img.jpg',img)
def Get_cnt(edged):
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[1] if imutils.is_cv3() else cnts[0]
docCnt = None
if len(cnts) > 0:
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
docCnt = approx
break
return docCnt
def order_points(pts):
rect = np.zeros((4, 2), dtype="float32")
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
def four_point_transform(simage, pts,gap=50):
# print(pts)
rect = order_points(pts)
(tl, tr, br, bl) = rect
tl[0] = tl[0]-gap
tl[1] = tl[1]-gap
tr[0] = tr[0]+gap
tr[1] = tr[1]-gap
br[0] = br[0]+gap
br[1] = br[1]+gap
bl[0] = bl[0]-gap
bl[1] = bl[1]+gap
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
dst = np.array([
[0, 0],
[maxWidth - 1, 0],
[maxWidth - 1, maxHeight - 1],
[0, maxHeight - 1]], dtype="float32")
M = cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
warped = cv2.copyMakeBorder(warped,50,50,50,50, cv2.BORDER_CONSTANT,value=[255,255,255])
return warped
#69,64 555,551
#sx,sy = _black_edges(img)
#print(sx,sy)
ss = Get_cnt(img)
print(ss)
warped = four_point_transform(image,ss.reshape(4, 2),gap=35)#這個gap閾值控制下仿射變換的余量偿乖,避免有些二維碼變換后識別不出來
cv2.imwrite('warped.jpg',warped)
- 接下來击罪,就是對二維碼做增強(qiáng)然后調(diào)用pyzbar做識別:
import pyzbar.pyzbar as pyzbar
from PIL import Image,ImageEnhance
from pyzbar.pyzbar import ZBarSymbol
import qreader
image2 = 'warped.jpg'
img = Image.open(image2)
#img = img.resize((600,600),Image.ANTIALIAS)
img = ImageEnhance.Brightness(img).enhance(2.0)#增加亮度
#
#img = ImageEnhance.Sharpness(img).enhance(1.5)#銳利化
#
#img = ImageEnhance.Contrast(img).enhance(2.0)#增加對比度
#
img = img.convert('L')#灰度化
img.save('cc.png')
barcodes = pyzbar.decode(img)
print(barcodes)
for barcode in barcodes:
barcodeData = barcode.data.decode("utf-8")
print(barcodeData)
識別結(jié)果如下:
[Decoded(data=b'WMWHSE6:2000041942,', type='QRCODE', rect=Rect(left=73, top=73, width=459, height=453), polygon=[Point(x=73, y=82), Point(x=78, y=526), Point(x=532, y=517), Point(x=521, y=73)])]
WMWHSE6:2000041942,