熱線電話(hua):0755-23712116
郵箱:contact@legoupos.cn
地址:深圳市(shi)寶安(an)區沙井(jing)街(jie)道后(hou)亭茅洲山工(gong)業園(yuan)工(gong)業大(da)(da)廈全至科技創新園(yuan)科創大(da)(da)廈2層(ceng)2A
綜(zong)述(shu)
2012年iOS應(ying)用(yong)(yong)(yong)商店中(zhong)發布了一(yi)個名為FuelMate的(de)Gas跟蹤(zong)應(ying)用(yong)(yong)(yong)。小伙伴們可以(yi)使(shi)用(yong)(yong)(yong)該應(ying)用(yong)(yong)(yong)程序跟蹤(zong)汽(qi)油行(xing)駛里程,以(yi)及有一(yi)些有趣(qu)的(de)功能(neng),例如Apple Watch應(ying)用(yong)(yong)(yong)程序、vin.li集成(cheng)以(yi)及基于(yu)趨勢mpg的(de)視覺(jue)效果。
燃料伴侶
對此我們(men)有(you)一個(ge)新想(xiang)法,該如何添加(jia)一個(ge)功能幫(bang)助我們(men)在泵中掃描燃油(you),并(bing)在應(ying)用程序中輸入燃油(you)信(xin)息?讓我們(men)深入研究如何實(shi)現這一目標。
技術
對于這個項目的我們首先應該編寫一個簡單的Python應用程序以拍攝汽油泵的圖像,然后嘗試從中讀取數字。OpenCV是用(yong)于計算機(ji)視覺應用(yong)程序的流行的跨(kua)平(ping)臺(tai)庫。它包括各種(zhong)圖像處理(li)實用(yong)程序以(yi)(yi)及某些機(ji)器學習功能。除此之外我們希望可(ke)以(yi)(yi)先使用(yong)Python對其進行原型(xing)設計,然后(hou)將(jiang)處理(li)代碼(ma)轉換為C ++以(yi)(yi)在iOS應用(yong)程序上運行。
目標
我們首先要考慮以(yi)下兩個問題:
1.我(wo)們可以(yi)從圖像中(zhong)分離出數字嗎?
2.我們可以確定圖像代表(biao)哪個數字嗎?
數字分割
如何確定圖像中的數字有多種方法,但是我提出了使用簡單的圖像閾(yu)值法來嘗試(shi)查找(zhao)數字的(de)方(fang)法。
圖(tu)像(xiang)(xiang)(xiang)閾值化的基本思想是將圖(tu)像(xiang)(xiang)(xiang)轉換為(wei)(wei)灰度(du)(du),然后(hou)說(shuo)灰度(du)(du)值小(xiao)于某個常數(shu)的任何像(xiang)(xiang)(xiang)素,則(ze)該(gai)像(xiang)(xiang)(xiang)素為(wei)(wei)一個值,否則(ze)為(wei)(wei)另一個。最后(hou),您得到的二進制(zhi)圖(tu)像(xiang)(xiang)(xiang)只(zhi)(zhi)有兩種顏色,在(zai)大多數(shu)情況下只(zhi)(zhi)是黑白圖(tu)像(xiang)(xiang)(xiang)。
這(zhe)個概念在OCR應(ying)用(yong)(yong)中(zhong)非(fei)常有(you)(you)效(xiao),但是主要問題是決定(ding)(ding)對該閾(yu)值(zhi)使(shi)(shi)用(yong)(yong)什(shen)么。我(wo)們可(ke)以(yi)選(xuan)擇一(yi)些(xie)常量,也可(ke)以(yi)使(shi)(shi)用(yong)(yong)OpenCV選(xuan)擇其他一(yi)些(xie)選(xuan)項。我(wo)們可(ke)以(yi)使(shi)(shi)用(yong)(yong)自適應(ying)閾(yu)值(zhi)而不(bu)(bu)是使(shi)(shi)用(yong)(yong)常數(shu),這(zhe)將使(shi)(shi)用(yong)(yong)圖像的(de)較小部分并確定(ding)(ding)要使(shi)(shi)用(yong)(yong)的(de)不(bu)(bu)同閾(yu)值(zhi)。這(zhe)在具有(you)(you)不(bu)(bu)同照(zhao)明情況的(de)應(ying)用(yong)(yong)中(zhong)特別(bie)有(you)(you)用(yong)(yong),特別(bie)是在掃描氣泵(beng)中(zhong)。
將圖像設置為閾值后,可以使用OpenCV的findContours方(fang)法查找圖像中連接了白(bai)色像素部分的區域。繪制輪(lun)廓(kuo)后(hou),便可以裁剪出這些區域并確定它(ta)們是否(fou)可能是數字以及它(ta)是什么數字。
基(ji)本(ben)圖像處理流程
這是(shi)我在測試(shi)圖(tu)像(xiang)處(chu)理中使用(yong)的(de)原始圖(tu)像(xiang)。它有(you)一些眩光點,但是(shi)圖(tu)像(xiang)相當干凈(jing)。讓我們逐步(bu)完成(cheng)獲取此源圖(tu)像(xiang)的(de)過程,并嘗(chang)試(shi)將其分解為單個數(shu)字(zi)。
原始圖片
影像準備
在開始圖像處理流程之前,我們決定先調整一些圖像屬性,然后再繼續。這有點試驗和錯誤,但注意到,當我們調整圖像的曝光度時,可以獲得更好的結果。下面是使用Python調整后的圖像,相當于曝光(阿爾法)的圖像cv::Mat::convertTo這是剛剛在圖像墊乘法操作cv2.multiply(some_img, np.array([some_alpha]),
調整曝光
灰階
將圖像轉換為灰度。
轉換為灰度
模糊
模糊(hu)圖像以減少噪點。我們嘗(chang)試了許多(duo)不同的模糊(hu)選項(xiang),但僅用輕微(wei)的模糊(hu)就(jiu)找到了最佳結果。
稍微模糊
閾值圖(tu)像(xiang)轉(zhuan)換為黑白圖(tu)像(xiang)
在下圖中,使用cv2.adaptiveThreshold帶有cv2.ADAPTIVE_THRES_GAUSSIAN_C選(xuan)項(xiang)的(de)方法。此方法采(cai)用兩個參數,塊大小和(he)要調(diao)整的(de)常數。確定這兩者需要一些(xie)試(shi)驗(yan)和(he)錯誤,更多有關優化(hua)部分的(de)內容(rong)。
閾值為黑/白
填補空白
由于大多數燃油泵都使用某種7段LCD顯示屏,因此數字中存在一些細微的間隙,無法使用輪廓繪制方法,因此我們需要使這些段看起來相連。在這種情況下,我們將轉到erode圖像來彌補這些差距。由于大家可能希望使用,所以這似乎向后看,dilate但是這(zhe)些方(fang)法通常適用于圖像的白(bai)色部(bu)分(fen)。在我們的案例(li)中,我們正在“侵蝕”白(bai)色背景以使數(shu)字看起來更大。
侵蝕出來的數字
反轉圖像
在嘗試在圖像中查找輪廓之前,我們需要反轉顏色,因為該findContours方(fang)法將找到(dao)白色的(de)連接(jie)部(bu)分(fen),而當前的(de)數(shu)字是黑色。
顏色反轉
在圖像上找到輪廓
下圖(tu)顯示了(le)我們(men)的原(yuan)始圖(tu)像(xiang),該圖(tu)像(xiang)在上(shang)圖(tu)的每個輪廓上(shang)都有包圍框。大家可以看到它(ta)找(zhao)到了(le)數字,但也找(zhao)到了(le)一堆不是數字的東西,因此我們(men)需要將它(ta)們(men)過濾掉。
紅(hong)色框顯(xian)示所有找(zhao)到的輪廓
輪廓過濾
1.現(xian)在我們(men)有(you)了許多輪(lun)廓,我們(men)需要找出我們(men)關心(xin)的(de)(de)輪(lun)廓。瀏覽(lan)了一堆氣泵的(de)(de)顯示和場(chang)景后,使用(yong)一套適(shi)用(yong)于(yu)輪(lun)廓的(de)(de)快速規(gui)則。
2.收(shou)集所有我們將分類(lei)為潛(qian)在(zai)小數的(de)正方形輪(lun)廓。
3.扔掉任何(he)不是正方形或高矩(ju)形的東西。
4.使輪廓與某些長寬比匹配。LCD顯示屏中的十個數字中有九個數字的長寬比類似于下面的藍色框高光之一。該規則的例外是數字“ 1”,其長寬比略有不同。通過使用一些樣本輪廓,我將0–9!1方面確定為0.6,將1方面確定為0.3。它將使用這(zhe)些(xie)比率和+/-緩沖區來(lai)確定輪廓(kuo)是(shi)否是(shi)我(wo)們(men)想要的(de)東西,并收集這(zhe)些(xie)輪廓(kuo)。
5.對潛(qian)(qian)在(zai)數(shu)字應用一組(zu)附加規則,在(zai)這(zhe)里我(wo)們將(jiang)確定輪(lun)(lun)廓(kuo)邊(bian)界是否偏離所有其他潛(qian)(qian)在(zai)數(shu)字的(de)(de)平(ping)均高度或垂直位置。由于數(shu)字的(de)(de)大小應相(xiang)同(tong),并且(qie)在(zai)相(xiang)同(tong)的(de)(de)Y上對齊,因(yin)此(ci)我(wo)們可以丟棄它認為是數(shu)字的(de)(de)任何輪(lun)(lun)廓(kuo),但不能像(xiang)其他輪(lun)(lun)廓(kuo)那(nei)樣將(jiang)其對齊和調整大小。
藍色矩形顯示我們(men)的數(shu)字/十進制,紅色被忽略(lve)
預測
有兩個等高線輪廓,一個帶(dai)潛(qian)在位數,一個帶(dai)潛(qian)在小數位,我們(men)可以使用這(zhe)些(xie)輪廓邊界裁剪圖像,并(bing)將其輸入經過訓(xun)練(lian)的(de)系統中以預測(ce)其值。有關此(ci)過程的(de)更多(duo)信息,請參見“數字(zi)培訓(xun)”部分。
查找小數
在圖(tu)像中查找(zhao)小數(shu)(shu)點(dian)是要解決的(de)(de)另(ling)一個問題。由于(yu)它(ta)很小,有時會連接到(dao)它(ta)旁邊(bian)的(de)(de)手指(zhi),因此使(shi)用我(wo)(wo)們在手指(zhi)上(shang)使(shi)用的(de)(de)方(fang)法(fa)來確(que)定它(ta)似乎有問題。當我(wo)(wo)們過濾輪廓時,我(wo)(wo)們收(shou)集(ji)了可能(neng)是十(shi)進制(zhi)的(de)(de)正方(fang)形(xing)輪廓。從(cong)上(shang)一步獲得(de)經過驗(yan)證的(de)(de)數(shu)(shu)字(zi)(zi)輪廓之后,我(wo)(wo)們將找(zhao)到(dao)數(shu)(shu)字(zi)(zi)的(de)(de)最(zui)左x位(wei)置(zhi)和最(zui)右x位(wei)置(zhi),以(yi)確(que)定我(wo)(wo)們期望的(de)(de)小數(shu)(shu)位(wei)數(shu)(shu)。然后,我(wo)(wo)們將遍(bian)歷那些潛在的(de)(de)小數(shu)(shu),確(que)定它(ta)是否在該(gai)空(kong)間(jian)以(yi)及該(gai)空(kong)間(jian)的(de)(de)下(xia)半部分(fen),并將其分(fen)類為小數(shu)(shu)。找(zhao)到(dao)小數(shu)(shu)點(dian)后,我(wo)(wo)們可以(yi)將其插入到(dao)我(wo)(wo)們上(shang)面預測的(de)(de)數(shu)(shu)字(zi)(zi)字(zi)(zi)符(fu)串(chuan)中。
只在黃色部(bu)分中查找小(xiao)數
數字培訓
在機(ji)器學(xue)習的(de)世界中(zhong)(zhong),解決OCR問題是一個分(fen)類問題。我們(men)建立了一組訓(xun)(xun)練有素的(de)數據,例如圖(tu)像(xiang)處(chu)理中(zhong)(zhong)的(de)數字(zi)(zi),將它(ta)們(men)分(fen)類為(wei)某種東(dong)西,然后(hou)使(shi)用該數據來匹配(pei)任(ren)何新(xin)圖(tu)像(xiang)。一旦基本的(de)圖(tu)像(xiang)隔(ge)離(li)功能開始工作,我就創建了一個腳本,該腳本可以遍歷圖(tu)像(xiang)文件夾(jia),運(yun)行(xing)數字(zi)(zi)隔(ge)離(li)代碼,然后(hou)將裁剪(jian)的(de)數字(zi)(zi)保存到新(xin)文件夾(jia)中(zhong)(zhong)供我查看。運(yun)行(xing)完之后(hou),我會有一個未(wei)經(jing)訓(xun)(xun)練的(de)數字(zi)(zi)文件夾(jia),然后(hou)可以用來訓(xun)(xun)練系統。
由(you)于(yu)OpenCV已(yi)經包含(han)了(le)k近鄰(k-NN)實現,因此(ci)無(wu)需引入任何其他庫(ku)。為了(le)進行訓(xun)練,我(wo)們(men)瀏覽(lan)了(le)數(shu)(shu)字(zi)作物的(de)(de)文(wen)件夾(jia),然后將其放入標(biao)有0–9的(de)(de)新文(wen)件夾(jia)中(zhong),因此(ci)每個文(wen)件夾(jia)中(zhong)都有一(yi)個數(shu)(shu)字(zi)的(de)(de)不同(tong)版(ban)本(ben)的(de)(de)集(ji)合。我(wo)們(men)沒有大(da)量的(de)(de)這(zhe)(zhe)些(xie)圖(tu)像,但是有足夠的(de)(de)證據來證明這(zhe)(zhe)是可行的(de)(de)。由(you)于(yu)這(zhe)(zhe)些(xie)數(shu)(shu)字(zi)是相當(dang)(dang)標(biao)準(zhun)的(de)(de),我(wo)認為我(wo)不需要大(da)量訓(xun)練有素的(de)(de)圖(tu)像就可以相當(dang)(dang)準(zhun)確。
k-NN工作(zuo)原(yuan)理的(de)基礎是,我(wo)(wo)們(men)將以黑白(bai)方式加載每個圖像(xiang)(xiang)(xiang),將該圖像(xiang)(xiang)(xiang)存(cun)儲在每個像(xiang)(xiang)(xiang)素(su)處于打開或關閉狀態的(de)數(shu)組(zu)中(zhong),然(ran)后(hou)(hou)(hou)將這(zhe)(zhe)些打開/關閉像(xiang)(xiang)(xiang)素(su)與特(te)定的(de)數(shu)字相(xiang)關聯。然(ran)后(hou)(hou)(hou),當我(wo)(wo)們(men)要預測一個新圖像(xiang)(xiang)(xiang)時(shi),它(ta)將找(zhao)出(chu)哪(na)個訓(xun)練圖像(xiang)(xiang)(xiang)與這(zhe)(zhe)些像(xiang)(xiang)(xiang)素(su)最匹配,然(ran)后(hou)(hou)(hou)向我(wo)(wo)們(men)返回最接近的(de)值(zhi)。
整理好數字后,將(jiang)(jiang)創建一個新的(de)(de)腳本(ben),該腳本(ben)將(jiang)(jiang)遍歷這些文件夾(jia),獲(huo)取每個圖像(xiang)并(bing)將(jiang)(jiang)該圖像(xiang)與數字關(guan)聯(lian)。到目前(qian)為(wei)止,在大多(duo)數代(dai)碼中,一般的(de)(de)圖像(xiang)處理概念在Python和(he)C ++中都應用相同,但是在這里會有細微的(de)(de)差別。
在大多數此類應用程序的Python示例中,分類被寫入兩個文件,一個包含分類,另一個包含該分類的圖像內容。通常使用NumPy和標準文本文件完成此操作。但是,由于我想在iOS應用程序上重用該系統,因此我需要想出一種可以擁有跨平臺分類文件的方式。當時,我什么都找不到,因此最終編寫了一個快速實用程序,該實用程序將從Python中獲取分類數據并將其序列化為JSON文件,我可以在OpenCV的FileStorage系統的C ++端使用它。這不漂亮,但是我寫了一個簡單的MatPython中的序列化方法,它將為OpenCV創建合適的結構以在iOS端讀取。現在,當我訓練數字時,我將獲得NumPy文件供我的Python測試使用,然后獲取一個JSON文檔,我可以將其拖到我的iOS應用程序中。您可以在此處看到該代碼。
優化
一旦確定了數字(zi)隔離和(he)預(yu)測的(de)兩個目(mu)標,就需要對算法進(jin)行優化,以預(yu)測泵的(de)新圖像上的(de)數字(zi)。
在優化的初始階段,創建了一個簡單的Playground應用程序,其中使用了OpenCV提供的一些簡單的UI組件。使用這些組件,可以創建一些簡單的軌跡欄,以左右滑動并更改不同的值并重新處理圖像。圍繞該cv2.imshow方(fang)法創建了一個(ge)小(xiao)包裝程序,該方(fang)法可以平鋪顯示的窗口(kou),因為我討(tao)厭總是重新放置它(ta)們,
嘗試不同的變量
我們可以(yi)加載(zai)不同的(de)圖像(xiang),并(bing)在圖像(xiang)處理中嘗試變量(liang)的(de)不同變化,并(bing)確定最(zui)佳的(de)組合。
自動化
在每個圖像上(shang)測(ce)試(shi)不同的(de)變量(liang)是(shi)上(shang)手的(de)好方法,但是(shi)我(wo)們想要(yao)一種更好的(de)方法來驗證(zheng)是(shi)否更改(gai)了一個圖像的(de)變量(liang)是(shi)否會對其他任何圖像產生(sheng)影響。為此,我(wo)們想出了針(zhen)對這些圖像進行一些自動化測(ce)試(shi)的(de)系統。
我(wo)拍攝了每(mei)個(ge)(ge)測試圖像,并(bing)將它們(men)放在文(wen)件(jian)夾(jia)中。然(ran)后(hou),我(wo)用(yong)圖像中期望的數(shu)(shu)(shu)字來命名每(mei)個(ge)(ge)文(wen)件(jian),并(bing)用(yong)小數(shu)(shu)(shu)點“ A”表示。應用(yong)程(cheng)序可(ke)(ke)以(yi)加載該(gai)目錄中的每(mei)個(ge)(ge)圖像并(bing)預測數(shu)(shu)(shu)字,然(ran)后(hou)將其與文(wen)件(jian)名中的數(shu)(shu)(shu)字進行比較以(yi)確定是否匹配。這使我(wo)們(men)可(ke)(ke)以(yi)針對所有不同的圖像快速(su)嘗試更改。
自動測試輸出
更進(jin)一步,我(wo)創建了(le)此腳本的(de)(de)不(bu)同版本,該腳本將(jiang)嘗試(shi)對(dui)這組圖像進(jin)行(xing)模(mo)糊,閾值等變(bian)量(liang)的(de)(de)幾乎(hu)每種(zhong)組合,并找出(chu)最(zui)優化的(de)(de)變(bian)量(liang)集將(jiang)具(ju)有(you)最(zui)佳的(de)(de)性能。準確性。該腳本在(zai)計算機上(shang)花費(fei)了(le)相當(dang)長的(de)(de)時(shi)間才能運行(xing),大約需要7個小時(shi),但是最(zui)后提出(chu)了(le)一組不(bu)同的(de)(de)變(bian)量(liang),這些(xie)變(bian)量(liang)在(zai)我(wo)們手動測(ce)試(shi)時(shi)找不(bu)到。
結論
這是否(fou)是任何人實際上都會使用的(de)(de)功能尚待確(que)定,但(dan)這在(zai)實現某些(xie)機器(qi)學習概念和使用OpenCV方面是一(yi)個有趣的(de)(de)練習。到目(mu)前為止,在(zai)我們的(de)(de)測試(shi)中,應(ying)用程序(xu)最(zui)大的(de)(de)問(wen)題是泵(beng)顯示屏上的(de)(de)眩光。根(gen)據泵(beng)上的(de)(de)照(zhao)明和手機的(de)(de)角度,可(ke)能會導致某些(xie)掃(sao)描失效。
# train_model.py
import os
import cv2
import numpy as np
# Current version of training
version = '_2_1'
RESIZED_IMAGE_WIDTH = 20
RESIZED_IMAGE_HEIGHT = 30
int_classifications = []
npa_flattened_images = np.empty((0, RESIZED_IMAGE_WIDTH * RESIZED_IMAGE_HEIGHT))
npa_classifications = []
trained_folder = 'knn'
trained_json_path = 'training' + version + '.json'
# Classify a digit
def train_file(file_path, char):
global npa_flattened_images, int_classifications, npaRawFlattenedImages
if char == 'dot':
char = 'A'
img = cv2.imread(file_path)
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
imgThreshCopy = imgGray.copy()
imgROIResized = cv2.resize(imgThreshCopy, (RESIZED_IMAGE_WIDTH, RESIZED_IMAGE_HEIGHT))
int_classifications.append(ord(char))
npaFlattenedImage = imgROIResized.reshape((1, RESIZED_IMAGE_WIDTH * RESIZED_IMAGE_HEIGHT))
npa_flattened_images = np.append(npa_flattened_images, npaFlattenedImage, 0)
# Write out the dictionary as a string
def serialize_dict(dict):
output = '{'
count = 1
proplen = len(dict)
for key in dict:
vals = dict[key]
output += '"{}": {}'.format(key, vals)
if count < proplen:
output += ','
count += 1