本文首發在集智專欄
導讀:計算機視覺專家Adrian Rosebrock近日分享瞭如何藉助OpenCV和Zbar,編寫出能夠實時識別二維碼和條形碼的掃描程式,最後部署在樹莓派上,成功製作一款實用的條形碼&二維碼掃描裝置。
最近有朋友問我(作者Adrian Rosebrock——譯者注)OpenCV裡有沒有什麼模組能直接識別條形碼和二維碼,很遺憾,答案是沒有。但是OpenCV能夠加快讀取條形碼和二維碼的過程,包括從硬碟載入影象,從視訊流中抓取新的幀,並進行處理。 等我們獲取影象或視訊幀後,就可以將其傳入Python中專用的條形碼解碼庫,比如Zbar。 然後Zbar會對條形碼或二維碼進行解碼。OpenCV可以接著執行進一步的影象處理工作以及展示結果。 聽起來有些複雜,其實整個處理過程相當簡單明瞭。程式庫Zbar也衍生了很多變體,其中pyzbar是我的最愛。
在本文,我會教你怎樣用OpenCV和Zbar讀取條形碼和二維碼。而且,我還會展示怎樣將我們製作的這個條形碼&二維碼掃描器部署到樹莓派上!!
使用OpenCV和ZBar打造一款條形碼及二維碼掃描器
本文主要分為四部分。
- 在第一部分,我會教你如何安裝Zbar庫(Python繫結)。
- Zbar庫會連同OpenCV一起用於掃描條形碼和二維碼。
- 等正確配置好Zbar和OpenCV以後,我會展示如何用它們掃描一張影象上的條形碼和二維碼。
- 先識別一張影象上的條形碼和二維碼練練手後,我們就進入下一階段:用OpenCV和Zbar實時讀取二維碼和條形碼。
- 最後,我會展示如何將製作好的實時二維碼&條形碼掃描器部署到樹莓派上。
安裝Zbar(帶Python繫結)用於解碼條形碼&二維碼
前段時間Staya Mallick在LearnOpenCV部落格上發表了一篇實用教程,講解如何用Zbar掃描條形碼。
本文關於Zbar安裝部分基本上是根據這篇博文的指導,但是做了一點改進,主要是圍繞安裝Python Zbar繫結部分,目的是確保我們能:
使用Python3(官方Zbar Python繫結只支援Python 2.7) 準確地檢測和定點陣圖像中二維碼及條形碼
安裝所需的軟體,只需簡單三步。
第一步:從apt或brew庫中安裝Zbar
在Ubuntu或樹莓派上安裝Zbar
$ sudo apt-get install libzbar0
複製程式碼
在MacOS系統中安裝Zbar
使用brew在macOS系統中安裝Zbar也很容易(假定你已經安裝了Homebrew):
$ brew install zbar
複製程式碼
第二步:建立一個虛擬環境,安裝OpenCV。
這裡你有倆個選擇: 使用現成的已經安裝好了OpenCV的虛擬環境(跳過這一步,看第三步)。 或者建立一個新的獨立的虛擬環境,安裝OpenCV。
虛擬環境對於Python開發來說是非常實用的做法,我非常鼓勵使用虛擬環境。
我選擇建立一個新的獨立的Python 3 虛擬環境,然後安裝了OpenCV,並將環境命名為barcode:
$ mkvirtualenv barcode -p python3
複製程式碼
注:如果你已經安裝好了OpenCV,就可以跳過OpenCV編譯過程,只需將你的cv2.so繫結符號連結(sym-link)入你的新Python虛擬環境中的site-pakages目錄。
第三步:安裝Pyzbar 現在我已經安裝了Python 3
虛擬環境,命名為barcode,然後啟用了barcode環境,安裝pyzbar:
$ workon barcode
$ pip install pyzbar
複製程式碼
如你不是用的Python 虛擬環境,只需:
$ pip install pyzbar
複製程式碼
如果想將pyzbar安裝到Python版系統中,確保你也使用sudo命令。
用OpenCV解碼單張影象上的條形碼和二維碼
在我們實現能實時讀取條形碼和二維碼之前,我們首先建立一個單張影象掃描器練練手。
開啟一個新檔案,命名為barcode_scanner_image.py,插入如下程式碼:
# 匯入所需工具包
from pyzbar import pyzbar
import argparse
import cv2
# 構建引數解析器並解析引數
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="path to input image")
args = vars(ap.parse_args())
複製程式碼
在第2-4行程式碼,我們匯入了所需的工具包。
需要按照上一部分的指令安裝pyzbar和cv2(OpenCV)。不過,在Python安裝中包含了argparse,負責解析命令列引數。
對於該指令碼我們有一個必需的命令列引數(--image),在7-10行進行解析。
你會在這部分結尾處看到在傳入包含輸入影象路徑的命令列引數時,如何執行這裡的指令碼。
現在,我們獲取輸入影象,執行pyzbar:
# 載入輸入影象
image = cv2.imread(args["image"])
# 找到影象中的條形碼並進行解碼
barcodes = pyzbar.decode(image)
複製程式碼
在第13行,我們通過影象的路徑(包含在我們很方便的args目錄中)載入影象。
從這裡,我們調取pyzbar.decode來發現和解碼影象中的條形碼(第16行)。
我們還沒完成——現在我們需要解析包含在barcode變數中的資訊:
# 迴圈檢測到的條形碼
for barcode in barcodes:
# 提取條形碼的邊界框的位置
# 畫出影象中條形碼的邊界框
(x, y, w, h) = barcode.rect
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
# 條形碼資料為位元組物件,所以如果我們想在輸出影象上
# 畫出來,就需要先將它轉換成字串
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
# 繪出影象上條形碼的資料和條形碼型別
text = "{} ({})".format(barcodeData, barcodeType)
cv2.putText(image, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 0, 255), 2)
# 向終端列印條形碼資料和條形碼型別
print("[INFO] Found {} barcode: {}".format(barcodeType, barcodeData))
# 展示輸出影象
cv2.imshow("Image", image)
cv2.waitKey(0)
複製程式碼
從19行開始,我們迴圈檢測到的barcodes。 在這裡的迴圈中,我們繼續:
從barcode.rect物件(22行)提取邊界框(x,y)座標,這樣能讓我們定位和確定當前條形碼在輸入影象的位置。
圍繞著檢測到的barcode(第23行),在影象上畫出邊界框。
將barcode解碼為“utf-8”字串,提取barcode的型別(第27行和28行)。調取.decode(“utf-8”)函式將物件從位元組陣列轉換為字串,非常關鍵。你可以通過刪除或新增註釋,試驗一下結果。
在影象上格式化和繪製barcodeData和barcodeType(第31-33行)。
最後,輸出同樣的資料,朝終端輸入資訊以進行除錯(第36行)。
我們測試一下搭建的OpenCV條形碼掃描器。 從這裡,開啟你的終端,執行如下命令:
$ python barcode_scanner_image.py --image barcode_example.png
[INFO] Found QRCODE barcode: {"author": "Adrian", "site": "PyImageSearch"}
[INFO] Found QRCODE barcode: https://www.pyimagesearch.com/
[INFO] Found QRCODE barcode: PyImageSearch
[INFO] Found CODE128 barcode: AdrianRosebrock
複製程式碼
可以在終端中看到,全部4個條形碼均被正確的發現和解碼!
如圖所示,識別出了影象中的條形碼和二維碼,以紅框標出,並顯示出了它們包含的資訊。
用OpenCV實時讀取條形碼和二維碼
在前面部分中,我們學習瞭如何為單張影象建立一個Python+OpenCV條形碼掃描器。
我們的條形碼和二維碼掃描器效果很好——但是問題來了,我們能實時檢測和解碼條形碼+二維碼嗎?
我們試試看。開啟一個新檔案,命名為barcode_scanner_video.py,插入如下程式碼:
# 匯入所需工具包
from imutils.video import VideoStream
from pyzbar import pyzbar
import argparse
import datetime
import imutils
import time
import cv2
# 建立引數解析器,解析引數
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--output", type=str, default="barcodes.csv",
help="path to output CSV file containing barcodes")
args = vars(ap.parse_args())
複製程式碼
在第2-8行,我們匯入了所需的工具包。 這裡回想一下上面的解釋,你應該識別pyzbar,argparse和cv2. 我們會使用VideoStream以高效和單執行緒的方式處理獲取的視訊幀。如果你的系統中沒有安裝imutils,只需使用如下命令:
$ pip install imutils
複製程式碼
我們接著解析一個可選的命令列引數--output,其包含了指向輸出結果CSV檔案的路徑。該檔案會包含從視訊流中檢測到和解析出的條形碼的時間戳及載荷。如果該引數沒有指定,那麼CSV檔案就會被我們當前名為“barcodes.csv”的工作目錄所替換(第11-14行)。
在這裡,我們初始化視訊流,開啟CSV檔案:
# 初始化視訊流,讓攝像頭熱熱身
print("[INFO] starting video stream...")
# vs = VideoStream(src=0).start()
vs = VideoStream(usePiCamera=True).start()
time.sleep(2.0)
# 開啟輸出CSV檔案,用來寫入和初始化迄今發現的所有條形碼
csv = open(args["output"], "w")
found = set()
複製程式碼
在第18-19行,我們初始化和啟動了視訊流,你可以: 使用自己的USB網路攝像頭(無註釋行第18行及註釋行第19行) 或者如果你是使用樹莓派的話(和我一樣),可以用PiCamera(無註釋行第19行及註釋行第18行)。
我選擇使用我的樹莓派 PiCamera,下部分會說到。
然後我們等上幾秒鐘,讓攝像頭熱熱身(第20行)。
我們會將發現的所有條形碼和二維碼以CSV檔案寫入硬碟(確保不要寫重複)。這裡只是一種記錄條形碼的例子,當然你可以按照自己的喜好來,比如檢測到條形碼後,將它們讀取為:
- 將其儲存在SQL資料庫中
- 將其傳送到伺服器
- 將其上傳至雲端
- 傳送郵件或文字資訊
實際操作隨意,我們只是用CSV檔案作為示例。
我們在第24行程式碼開啟CSV檔案以寫入硬碟。如果你修改了程式碼以新增到檔案,你可以只需將第二個引數從“w”改為“a”(但是你後面只能換種方式搜尋重複檔案)。
我們也初始化一個set用於found條形碼。這個set會包含獨一無二的條形碼,防止出現重複。
我們開始獲取和處理視訊幀:
# 迴圈來自視訊流的幀
while True:
# 抓取來自單執行緒視訊流的幀,
# 將大小重新調整為最大寬度400畫素
frame = vs.read()
frame = imutils.resize(frame, width=400)
# 找到視訊中的條形碼,並解析所有條形碼
barcodes = pyzbar.decode(frame)
複製程式碼
在第28行,我們開始迴圈,繼續抓取來自視訊流中的frame,並調整大小(第31和32行)。
在這裡,我們調取pyzbar.decode以檢測和解碼frame中的全部條形碼和二維碼。
我們接著迴圈檢測到的barcodes:
# 迴圈檢測到的條形碼
for barcode in barcodes:
# 提取條形碼的邊界框位置
# 繪出圍繞影象上條形碼的邊界框
(x, y, w, h) = barcode.rect
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
# 條形碼資料為位元組物件,所以如果我們想把它畫出來
# 需要先把它轉換成字串
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
# 繪出影象上的條形碼資料和型別
text = "{} ({})".format(barcodeData, barcodeType)
cv2.putText(frame, text, (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 如果條形碼文字目前不在CSV檔案中, write
# 就將時間戳+條形碼 to disk and update the set
if barcodeData not in found:
csv.write("{},{}\n".format(datetime.datetime.now(),
barcodeData))
csv.flush()
found.add(barcodeData)
複製程式碼
如果看看前面的迴圈部分,你會發現這裡迴圈和之前的很像。
實際上,第38-52行和前面識別單張影象的指令碼是一樣的。這部分程式碼詳情解釋,參見單張影象條形碼檢測和掃描部分。
第56-60行程式碼比較新。在這些行程式碼中,我們是檢查是否發現了獨有的(此前沒有發現)條形碼(第56行)。
如果是這種情況,我們將時間戳和資料寫成CSV檔案(第57-59行)。此外還可以將barcodeData新增到found集,作為一種處理重複檔案的簡單方法。
在實時條形碼掃描指令碼的剩餘程式碼行中,我們展示視訊幀,檢查是否按退出鍵,並進行清除:
# 展示輸出幀
cv2.imshow("Barcode Scanner", frame)
key = cv2.waitKey(1) & 0xFF
# 如果按下”q”鍵就停止迴圈
if key == ord("q"):
break
# 關閉輸出CSV檔案進行清除
print("[INFO] cleaning up...")
csv.close()
cv2.destroyAllWindows()
vs.stop()
複製程式碼
在第63行,我們展示輸出幀。
然後在第64-68行,我們檢查是否按了“q”,執行主迴圈。
最後,我們在第72-74行執行清除。
在樹莓派上部署條形碼和二維碼掃描器
我決定使用樹莓派、觸控式螢幕和一個充電寶打造一款自己的實時條形碼掃描器。
下圖顯示的是我的組裝成果。如果你也想自己做一個,以下是需要的部件:
- 樹莓派3(你也可以用最新的 3 B+)
- 樹莓派攝像頭模組
- Pi Foundation 7英寸觸控式螢幕
- RAVPower 22000mAh充電寶
很容易就能組裝好。
在這裡,開啟你樹莓派上的終端,用如下命令啟動應用(這一步需要一個鍵盤/滑鼠,但是後面就用不著了):
$ python barcode_scanner_video.py
[INFO] starting video stream...
複製程式碼
等一切準備就緒後,就可以將條形碼展示給攝像頭了,可以開啟barcode.csv檔案(或者如果你願意,也可以在另一個終端上執行tail -f barcodes.csv,檢視開啟CSV檔案時的資料)。
我首先向攝像頭展示了一個黑色背景上的二維碼,Zbar很輕鬆的檢測到了它:
然後又在我家廚房裡用這套裝置發現了另一個二維碼:
成功了!而且能多角度掃描識別二維碼。
現在,我們試試一個包含了json-blob資料的二維碼:
最後,我試了試傳統的1維條形碼:
1維的條形碼對於我們這套系統來說略微難些,因為攝像頭不支援自動對焦。但是最後還是成功的檢測和解碼了條形碼。
如果你用有自動對焦功能的USB網路攝像頭的話,效果要好得多。
結語
在本文,我們討論了怎樣用OpenCV和Python庫Zbar打造一款條形碼和二維碼掃描器。
將Zbar和OpenCV安裝後,我們建立了兩個Python指令碼:
- 第一個用於掃描單張影象上的條形碼和二維碼。
- 第二個用於實時讀取條形碼和二維碼的資訊。 在這兩種情況中,我們都使用了OpenCV來加快程式。
最終,我們將建立好的程式部署到樹莓派上,並且能成功實時識別條形碼和二維碼。
可以自己試著去做一條這樣的條形碼&二維碼掃描器,專案原始碼下載
0806期《人工智慧-從零開始到精通》限時折扣中!
談笑風生 線上程式設計 瞭解一下?
(前25位同學還可領取¥200優惠券哦)