OpenCV之輪廓檢測(檢測銀行卡上的黑色磁條)
最近有需要做銀行卡上黑色磁條的提取的工作。因為這是比較典型的輪廓檢測問題。用DL的方法需要大量的標註資料集,所以想到用openCV來做。下面梳理一下流程:
前言
這篇博文的目的是應用計算機視覺和影象處理技術,展示一個銀行卡上黑色磁條的基本實現。
需要注意的是,這個演算法並不是對所有銀行卡都有效,但會給你基本的關於應用什麼型別的技術的直覺,這種感覺的積累對於解決工程問題來說,是有益的。
假設我們要檢測下圖銀行卡中的黑色磁條:
下面的程式碼是基於新版的opencv3.3的。
opencv3.x跟網上很多基於opencv2.x版本的api和呼叫都有不同了,注意調整。
解決思路
① 圖片讀取,轉換成灰度圖片
import numpy as np
import cv2
img = cv2.imread(image) # image為你的圖片地址
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 轉換了灰度化
② 使用Scharr操作(指定使用ksize = -1)構造灰度圖在水平和豎直方向上的梯度幅值表示。
gradX = cv2.Sobel(grey, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradY = cv2.Sobel (grey, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
gradient = cv2.subtract(gradX, gradY)
gradient = cv2.convertScaleAbs(gradient)
這一步,使用Scharr操作(指定使用ksize = -1)構造灰度圖在水平和豎直方向上的梯度幅值表示。Scharr操作之後,我們從x-gradient中減去y-gradient,通過這一步減法操作,最終得到包含高水平梯度和低豎直梯度的影象區域。
得到的處理後圖片如下:
③ 注意黑色條形區域是怎樣通過梯度操作檢測出來的。下一步將通過去噪僅關注磁條區域。
# blur and threshold the image
blurred = cv2.blur(gradient, (16, 16))
retval, grey = cv2.threshold(blurred, 30, 255, cv2.THRESH_BINARY_INV)
這裡要做的第一件事是使用16 * 16的核心對梯度圖進行平均模糊,這將有助於平滑梯度表徵的圖形中的高頻噪聲。對我這個例子,越大越好。
此外,cv2.THRESH_BINARY_INV是跟cv.THRESH_BINARY正好相反的:作用是把畫素點(pixel)低於30全轉成0(黑色),其他的畫素點(pixel)變成白色。
得到圖片如圖:
④ 上面處理的結果發現磁條不是完整的,有縫隙,需要填充
為了消除這些縫隙,並使我們的演算法更容易檢測到條形碼中的“斑點”狀區域,我們需要進行一些基本的形態學操作:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 10))
closed = cv2.morphologyEx(grey, cv2.MORPH_CLOSE, kernel)
上面程式碼的含義是:我們首先使用cv2.getStructuringElement構造一個長方形核心kernel。這個核心的寬度大於長度,因此我們可以消除條形碼中垂直條之間的縫隙。
然後把這個kernel扔進去,進行形態學操作。將上一步得到的核心應用到我們的二值圖中,以此來消除豎槓間的縫隙。
當然,現在影象中還有一些小斑點,不屬於真正條形碼的一部分,但是可能影響我們的輪廓檢測。
讓我們來消除這些小斑點:
closed = cv2.erode(closed, None, iterations=5)
grey = cv2.dilate(closed, None, iterations=5)
我們這裡所做的是首先進行5次腐蝕(erosion),然後進行5次膨脹(dilation)。腐蝕操作將會腐蝕影象中白色畫素,以此來消除小斑點,而膨脹操作將使剩餘的白色畫素擴張並重新增長回去。
如果小斑點在腐蝕操作中被移除,那麼在膨脹操作中就不會再出現。
經過我們這一系列的腐蝕和膨脹操作,可以看到我們已經成功地移除小斑點並得到條形碼區域。
可以看出,小的斑點被清除了。
⑤ 最後,畫出磁條的矩形輪廓
image, contours, hierarchy = cv2.findContours(grey.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 找到影象中的最大輪廓
c = sorted(contours, key=cv2.contourArea, reverse=True)[0]
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))
cv2.drawContours(img, [box], -1, (0, 0, 255), 3)
cv2.imshow('Image', img)
cv2.waitKey()
這一部分比較容易,我們簡單地找到影象中的最大輪廓,如果我們正確完成了影象處理步驟,這裡應該對應於磁條區域。
然後用cv2.minAreaRect為最大輪廓確定最小邊框。
最後就是顯示檢測到的磁條:
雖然不是完全取到,但是思路是這樣的。
cv2.findContours接收的引數中,第一個引數是要檢索的圖片,必須是為二值圖,即黑白的(不是灰度圖)。第二個引數是輪廓的檢索模式:
- cv2.RETR_EXTERNAL表示只檢測外輪廓。
- cv2.RETR_LIST檢測的輪廓不建立等級關係。
- cv2.RETR_CCOMP建立兩個等級的輪廓,上面的一層為外邊界,裡面的一層為內孔的邊界資訊。如果內孔內還有一個連通物體,這個物體的邊界也在頂層。
- cv2.RETR_TREE建立一個等級樹結構的輪廓。
第三個引數為輪廓的近似方法:
- cv2.儲存所有的輪廓點,相鄰的兩個點的畫素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
- cv2.CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點座標,例如一個矩形輪廓只需4個點來儲存輪廓資訊
cv2.drawContours在影象上繪製輪廓。
第一個引數是指明在哪幅影象上繪製輪廓
第二個引數是輪廓本身,在Python中是一個list
第三個引數指定繪製輪廓list中的哪條輪廓,如果是-1,則繪製其中的所有輪廓
第四個引數是輪廓線條的顏色 ((0, 0, 255), (0, 255, 0), …)
第五個引數是輪廓線條的粗細 (1,2,3…)
總結
演算法概要如下:
- 計算x方向和y方向上的Scharr梯度幅值表示
- 將x-gradient減去y-gradient來顯示磁條區域
- 模糊並二值化影象
- 對二值化影象應用閉運算核心
- 進行系列的腐蝕、膨脹
- 找到影象中的最大輪廓,大概便是磁條區域
需要注意的是,該方法做了關於影象梯度表示的假設,因此只對水平磁條有效。
如果你想實現一個更加魯棒的條形碼檢測演算法,你需要考慮影象的方向,或者更好的,應用機器學習技術如Haar級聯或者HOG + Linear SVM去掃描影象的磁條區域。