基于OpenCV的條形碼區(qū)域分割
點(diǎn)擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時(shí)間送達(dá)

本期,我們將一起學(xué)習(xí)如何從圖像中提取出含有條形碼的區(qū)域。下面的代碼,我們將在Anaconda中采用Python 2.7 完成,當(dāng)然OpenCV中的圖像處理庫也是必不可少的。
分割是識別圖像內(nèi)一個(gè)或多個(gè)對象的位置的過程。我們要介紹的技術(shù)其實(shí)非常簡單,它利用了形態(tài)算子的擴(kuò)張和侵蝕,以及諸如開運(yùn)算,閉運(yùn)算和黑帽算子的組合。
01.簡介
安裝Anaconda后,讓我們從Anaconda的提示符下使用以下命令轉(zhuǎn)到OpenCV安裝:
conda install -c https://conda.anaconda.org/menpo opencv
現(xiàn)在,讓我們從Anaconda啟動(dòng)器啟動(dòng)Spyder IDE。

Anaconda啟動(dòng)器
一旦運(yùn)行了Spyder,建議驗(yàn)證OpenCV安裝是否成功。在Python控制臺的右下角,我們進(jìn)行以下測試:
import cv2代碼講解
我們已經(jīng)創(chuàng)建了一個(gè)啟動(dòng)GitHub存儲庫。小伙伴可以使用以下方法直接克隆它:
git?clone?--branch?step1https://github.com/lucapiccinelli/BarcodesTutorial.git
現(xiàn)在,我們將要下載測試圖像,并對他們進(jìn)行讀取和顯示。

測試圖片
import cv2import?matplotlib.pyplot?as?pltim?=?cv2.imread(r’img\barcodes.jpg’,?cv2.IMREAD_GRAYSCALE)plt.imshow(im, cmap=’Greys_r’)
接下來,我們將對圖像進(jìn)行二值化處理,這樣可以通過閾值的設(shè)定來提取出我們感興趣的部分。使用黑帽運(yùn)算符,我們可以增加較暗的圖像元素。我們可以首先使用簡單的全局閾值安全地對圖像進(jìn)行二值化處理。黑帽運(yùn)算符使我們可以使用非常低的閾值,而不必過多地關(guān)注噪聲。
在應(yīng)用blackhat時(shí),我們使用的內(nèi)核會更加重視垂直圖像元素。內(nèi)核具有固定的大小,因此可以縮放圖像,這也可以提高性能(并支持某種輸入歸一化)。

黑帽+閾值處理
它遵循其他形態(tài)運(yùn)算符的采用,順序地將它們組合在一起以獲得條形碼位置中的連接組件。
#riscalatura dell'immaginescale = 800.0 / im.shape[1]im = cv2.resize(im, (int(im.shape[1] * scale), int(im.shape[0] * scale)))#blackhatkernel = np.ones((1, 3), np.uint8)im = cv2.morphologyEx(im, cv2.MORPH_BLACKHAT, kernel, anchor=(1, 0))#sogliaturathresh, im = cv2.threshold(im, 10, 255, cv2.THRESH_BINARY)
膨脹和閉合的這種組合在測試圖像上效果很好,但可能無法在其他圖像上達(dá)到相同的效果。這沒有關(guān)系,大家可以嘗試改變參數(shù)和運(yùn)算符的組合,直到對結(jié)果滿意為止。

膨脹+閉運(yùn)算
最后的預(yù)處理步驟是應(yīng)用具有很大內(nèi)核的開運(yùn)算符,以刪除太少而無法適合條形碼形狀的元素。
kernel = np.ones((21, 35), np.uint8)im = cv2.morphologyEx(im, cv2.MORPH_OPEN, kernel, iterations=1)
這是我們希望得到的最終結(jié)果:

使用35x21內(nèi)核打開
現(xiàn)在,我們可以運(yùn)行連接的組件的檢測算法,并檢索帶有坐標(biāo)和尺寸的條形碼矩形。如大家在上一張圖像中所看到的那樣,最后的形態(tài)學(xué)步驟并未濾除全部的噪聲。但是,在這種情況下,將它們過濾掉非常簡單,以矩形區(qū)域值作為閾值就可以了。
#rilettura dell'immagine, stavolta a coloriim_out = cv2.imread(r'img\barcodes.jpg')#estrazione dei componenti connessicontours, hierarchy = cv2.findContours(im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)unscale = 1.0 / scaleif contours != None:for contour in contours:# se l'area non è grande a sufficienza la saltoif cv2.contourArea(contour) <= 2000:continue#estraggo il rettangolo di area minima (in formato (centro_x, centro_y), (width, height), angolo)rect = cv2.minAreaRect(contour)#l'effetto della riscalatura iniziale deve essere eliminato dalle coordinate rilevaterect = \((int(rect[0][0] * unscale), int(rect[0][1] * unscale)), \(int(rect[1][0] * unscale), int(rect[1][1] * unscale)), \rect[2])#disegno il tutto sull'immagine originalebox = np.int0(cv2.cv.BoxPoints(rect))cv2.drawContours(im_out, [box], 0, (0, 255, 0), thickness = 2)plt.imshow(im_out)#scrittura dell' immagine finalecv2.imwrite(r'img\out.png', im_out)
最后,在上面的代碼中,我使用提取的矩形繪制它們,并將其覆蓋在原始圖像上。

最終結(jié)果,條形碼以綠色框突出顯示。
結(jié)論
? 提出的技術(shù)非常簡單有效,但存在一些令人討厭的缺點(diǎn):
? 它對條形碼偏斜非常敏感;它可以很好地工作到大約45度,然后您必須執(zhí)行第二遍,修改內(nèi)核的方向。
? 它只能在固定尺寸范圍內(nèi)找到條形碼。
? 盡管對矩形區(qū)域施加了過濾,但仍有可能無法清除某些非條形碼。
第一個(gè)和第二個(gè)可能不是真正的問題,但是最后一個(gè)可能會花費(fèi)大家大量時(shí)間來嘗試解碼非條形碼的內(nèi)容。
一個(gè)很好的解決方案是將條形碼特征(圖像梯度,傅立葉變換)輸入給神經(jīng)網(wǎng)絡(luò)(或一些其他一些分類器),并在第二時(shí)刻過濾掉噪聲。
下面給出完整的示例代碼。
import cv2import matplotlib.pyplot as pltimport numpy as npim = cv2.imread(r'img\barcodes.jpg', cv2.IMREAD_GRAYSCALE)im_out = cv2.imread(r'img\barcodes.jpg')#riscalatura dell'immaginescale = 800.0 / im.shape[1]im = cv2.resize(im, (int(im.shape[1] * scale), int(im.shape[0] * scale)))#blackhatkernel = np.ones((1, 3), np.uint8)im = cv2.morphologyEx(im, cv2.MORPH_BLACKHAT, kernel, anchor=(1, 0))#sogliaturathresh, im = cv2.threshold(im, 10, 255, cv2.THRESH_BINARY)#operazioni morfologichekernel = np.ones((1, 5), np.uint8)im = cv2.morphologyEx(im, cv2.MORPH_DILATE, kernel, anchor=(2, 0), iterations=2) #dilatazioneim = cv2.morphologyEx(im, cv2.MORPH_CLOSE, kernel, anchor=(2, 0), iterations=2) #chiusurakernel = np.ones((21, 35), np.uint8)im = cv2.morphologyEx(im, cv2.MORPH_OPEN, kernel, iterations=1)#estrazione dei componenti connessicontours, hierarchy = cv2.findContours(im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)unscale = 1.0 / scaleif contours != None:for contour in contours:# se l'area non è grande a sufficienza la saltoif cv2.contourArea(contour) <= 2000:continue#estraggo il rettangolo di area minima (in formato (centro_x, centro_y), (width, height), angolo)rect = cv2.minAreaRect(contour)#l'effetto della riscalatura iniziale deve essere eliminato dalle coordinate rilevaterect = \((int(rect[0][0] * unscale), int(rect[0][1] * unscale)), \(int(rect[1][0] * unscale), int(rect[1][1] * unscale)), \rect[2])#disegno il tutto sull'immagine originalebox = np.int0(cv2.cv.BoxPoints(rect))cv2.drawContours(im_out, [box], 0, (0, 255, 0), thickness = 2)plt.imshow(im_out)#scrittura dell' immagine finalecv2.imwrite(r'img\out.png', im_out)
交流群
歡迎加入公眾號讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動(dòng)駕駛、計(jì)算攝影、檢測、分割、識別、醫(yī)學(xué)影像、GAN、算法競賽等微信群(以后會逐漸細(xì)分),請掃描下面微信號加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三?+?上海交大?+?視覺SLAM“。請按照格式備注,否則不予通過。添加成功后會根據(jù)研究方向邀請進(jìn)入相關(guān)微信群。請勿在群內(nèi)發(fā)送廣告,否則會請出群,謝謝理解~
