使用Tensorflow Object Detection API進(jìn)行集裝箱識別并對集裝箱號進(jìn)行OCR識別
兩年多之前我在“ex公司”的時候,有一個明確的項目需求是集裝箱識別并計數(shù),然后通過OCR識別出之前計數(shù)的每一個集裝箱號,與其余業(yè)務(wù)系統(tǒng)的數(shù)據(jù)進(jìn)行交換,以實現(xiàn)特定的整體需求。當(dāng)時正好Tensorflow Object Detection API 發(fā)布了,就放棄了YOLO或者SSD的選項,考慮用TF實現(xiàn)Demo做POC驗證了。
背景
之前也并未接觸過Deep Learning相關(guān)的事情,為了驗證這個需求可以實現(xiàn)并滿足交付要求,從入門到了解到選擇到學(xué)習(xí)到準(zhǔn)備圖片并標(biāo)注到完成基本的Python Demo驗證一個人前后差不多用了2個月時間(那時候用的還是12年中的MacBook Air),驗證完成后,交給了C++和C#的小伙伴們?nèi)崿F(xiàn)。在這個過程中感受到了Deep Learning的魅力與強大以及對于未來的無限可能,就帶著我的研發(fā)小伙伴們走向這條路前進(jìn)了,人臉、語音這些都嘗試了,也都嵌入了已有都產(chǎn)品中,也不會因為只有第三方算法而各種踩坑了。
最近重裝了Mac,重寫個Demo驗證下Inteld對于CPU的DL支持有多大改善,說實話,代碼不重要,一點不復(fù)雜,我更想借這個Demo說一下做AI技術(shù)工程化落地的思考方法和實現(xiàn)過程。
問題分析
1、明確要具體解決的問題
這個需求有兩個關(guān)鍵點,主要應(yīng)用場景是在只有單攝像頭監(jiān)控的位置,實現(xiàn)集裝箱裝卸過程中:如何準(zhǔn)確識別集裝箱,識別后如何去重才能保證“計數(shù)”的準(zhǔn)確;如何在單攝像頭且非約束的場景下做集裝箱號的OCR。
已有的解決方案,集裝箱識別——沒有;集裝箱號OCR——有,但是都是約束場景下的識別。即:集裝箱與攝像頭的位置關(guān)系有明確限制且很有可能使用多達(dá)3個攝像頭做聯(lián)合判斷后輸出結(jié)果,與實際場景不符,等于沒有。市面上大多在“卡口”做集裝箱號識別的方案主流是在停車發(fā)卡的位置固定三路攝像機拍攝。
所以這個需求能否實現(xiàn),關(guān)鍵就在于單攝像頭非約束場景下的detection與OCR了。
2、為什么不用SSD與YOLO
這兩個目標(biāo)檢測的方法都很快,識別準(zhǔn)確率也足夠,之前確實考慮選其中之一來實現(xiàn),但是想明白了這件事怎么做,也準(zhǔn)備好了幾千張圖片準(zhǔn)備驗證的時候 Object Detection API 發(fā)布了,DL這種對未來影響深遠(yuǎn)的事情,選擇一個最有可能成為大生態(tài)環(huán)境的平臺就很重要了,Google雖然之前有“說關(guān)就關(guān)”的“前科”但是在我的理解,不論軟件還是硬件層面,DL的inference能力就是未來的OS之上最重要的事情了,甚至可以理解為就是OS的一部分(比如SoC),所以,這事情Google任性可能性微乎其微,從這兩年發(fā)展來看,Google對TF持續(xù)投入,而且做為“大廠”的標(biāo)志之一似乎就是你有沒有開源的DL相關(guān)工作。
3、當(dāng)時不擔(dān)心搞不定么?
在此之前整個計數(shù)團隊僅有一人在微軟參與過人臉識別相關(guān)的工作,而且還是機器學(xué)習(xí)實現(xiàn),非深學(xué)習(xí)實現(xiàn)。非常擔(dān)心,真的擔(dān)心搞不定,所以連一塊1080Ti的采購需求都沒敢提,前期工作全部用我的12年老MBA完成的。就是由于真的怕搞不定,所以我沒有玩弄所謂的“權(quán)術(shù)”。我是公司的研發(fā)總,所以理論上,我把這個Demo的預(yù)研工作交出去,找兩個人去搞定,搞定了我工作依然完成了,搞不定我還有“替死鬼”。我也不是沒事干,產(chǎn)品、架構(gòu)、評審、項目、質(zhì)量保證、銷售以及沒完沒了的各種會議,但是這個事情真沒底,不知道能不能搞定,安排工作簡單,但是安排自己都沒把握的工作出去雖然是一種“管理智慧”,但是不是本人做事、為人風(fēng)格。于是就白天干工作職責(zé)之內(nèi)的事情,下班后再來做這件事,能通宵就通宵,周末?周末是不可能有的。所以說起來2個月時間,其實是2個月的業(yè)余時間。
深度學(xué)習(xí)在2018年持續(xù)火爆,想上車的同學(xué)越來越多,在此如果你機會看到這段文字,以我的實際情況來看,我可以負(fù)責(zé)任的告訴你:只要你全情投入,肯定可以上車并且走的很遠(yuǎn)。
基本pipeline
內(nèi)容很長,先說結(jié)論。
我把這套pipeline的定義分析過程叫做“最大誤差分析法”。即:先找到整個環(huán)節(jié)中潛在誤差最大的環(huán)節(jié),先推導(dǎo)出有可能實現(xiàn)這個誤差最大環(huán)節(jié)的最優(yōu)解,再基于這個最優(yōu)解向前后尋找pipeline其余環(huán)節(jié)的最優(yōu)解,這樣定義出來的pipeline有極大概率是解決整個問題的最優(yōu)解。
定義問題
如前所述,需求說起來很簡單,只有三個核心:單攝像頭、計數(shù)、OCR。
定義問題解決路線
做為一個做泛安防與視頻的公司,多少是有一些技術(shù)積累和對計算機視覺的認(rèn)識,站在DL與CV的角度,從當(dāng)時看到的資料整體判斷與分析,集裝箱識別的實現(xiàn)以及準(zhǔn)確性肯定是要優(yōu)于集裝箱號的OCR的,要整體解決工程需求,實現(xiàn)工程落地的準(zhǔn)確性,應(yīng)該先設(shè)計并尋找讓集裝箱號OCR準(zhǔn)確性能提高的技術(shù)路線,因為這個才是工程最終準(zhǔn)確率的關(guān)鍵瓶頸。
所有的工作都要在一個攝像頭的視頻流中完成,那么我們解決問題的路線圖應(yīng)該是:–>尋找OCR的最優(yōu)解–>基于OCR的最優(yōu)解尋找集裝箱識別與計數(shù)的最優(yōu)解–>整體分析最優(yōu)解的實現(xiàn)可能并預(yù)估最終結(jié)果的可接受度。如果,判斷最終結(jié)果不可接受,那就回歸到原點重新?lián)Q思路。
先大致說一下集裝箱。集裝箱做為一個立方體,共有6個面,底面肯定是擺放用的,不需要考慮,那么剩下5個面都有什么信息?一般來說5個面都會噴涂有集裝箱號,不過是大小和細(xì)節(jié)位置的區(qū)別,其中集裝箱門(標(biāo)準(zhǔn)用詞:端門,door)這一面的信息最全,除了集裝箱編號以外其余的信息都在這一面。集裝箱的設(shè)計為了輕量化和能承受堆疊載荷,側(cè)面都是用的瓦楞鋼板可以大幅提高軸向載荷能力,另外在頂面也噴涂有集裝箱號。
確定OCR目標(biāo)區(qū)域
集裝箱的側(cè)面都是用的瓦楞鋼板可以大幅提高軸向載荷能力,對于OCR來說這就是極大的挑戰(zhàn)了,因為不是一個平面了,視角不同,肉眼看都會出錯,先排除4個側(cè)面,就剩一個頂面的集裝箱號了。事實上,這個位置的集裝箱號做OCR確實很合適,第一位置相對規(guī)范,噴涂位置在集裝箱的鋼架上,第二此處是一個平面,第三此處的集裝箱號很少出現(xiàn)污損的情況。如果要確保能準(zhǔn)確采集到這個位置的集裝箱號,那么攝像機一定是俯拍,才有可行性,那么帶來的問題就是,如果要俯拍,那么就要確保有安裝位置。但是由于業(yè)務(wù)場景限制,并非集裝箱裝卸一定是龍門吊,還有可能是正面吊(這兩個名詞,還請自行Google了),如果都是龍門吊,是可以考慮在吊臂安裝攝像機的(大家要是天天看新聞聯(lián)播,說過一次“自動化無人碼頭”,這里的自動化就是用的吊臂安裝攝像機做OCR后,告訴無人車把這個箱子送到什么位置)。但是,對于正面吊,裝攝像機是沒有任何可能性的,就算想法子裝上去,估計損毀幾率也非常高,所以,首先放棄的就是條件最好的這個位置。
回頭再看初步放棄的四個側(cè)面,只有集裝箱門這一面絕大多數(shù)的集裝箱所有者都會把集裝箱號噴涂在相對較小的那塊平面上,所以,對于我們的場景,取這個位置的集裝箱號做OCR是有且僅有的靠譜方案。
如何確保OCR的基礎(chǔ)準(zhǔn)確性
怎么理解這個“基礎(chǔ)準(zhǔn)確性”呢?熟悉OpenCV的同學(xué)都知道,用cv2.dilate,cv2.erode的方法膨脹、腐蝕來一組,再找一下輪廓,也能實現(xiàn)文字塊的檢測,前面已經(jīng)初步確定了要用集裝箱門的集裝箱號來做OCR,如果把整幅圖片交給OpenCV來找到文字塊,再交給后端的OCR接手理論上是可以,但是,仔細(xì)觀察一下集裝箱門,文字塊太多了,先不談檢測準(zhǔn)確性,僅集裝箱號檢測這一個任務(wù)“噪音與干擾”太大了,最終結(jié)果不好保障,所以,要先解決集裝箱號這個文字塊的提取工作,后面的OCR才會更有準(zhǔn)確率保障。
不用OpenCV,直接用NN網(wǎng)絡(luò)來一步到位解決文字檢測和OCR的問題是不更好呢?嗯,理論上NN肯定效果更好,但是,如何做Demo的驗證,如何找大量數(shù)據(jù)集做訓(xùn)練?這是個短時間無解的問題,大家可以了解下FSNS dataset的規(guī)模,做為中期技術(shù)路線規(guī)劃,可以考慮用NN去做OCR。天下武功,唯快不破,但是需求就在眼前,客戶可不會聽你解釋幾個小時技術(shù)方案和技術(shù)先進(jìn)性,然后說我要半年去收集數(shù)據(jù),半年后我拿Demo來,你看滿意不。你覺得,你是客戶,你答應(yīng)么?所以還要思考別的解決方案。
目前兩難的情況是:讓OpenCV去大面積找字符,準(zhǔn)確性不好保障,用NN來做,準(zhǔn)確性有可能保障,但是時間窗口是沒有的。后者,無解;那么就看前者有沒有破局的解法。目標(biāo)檢測是干嘛的?為什么不能把集裝箱號這個文字區(qū)域當(dāng)做一個“目標(biāo)”來檢測呢?這樣得到的就是一個僅有集裝箱號的小文字塊,后面不管怎么OCR都不需要再去考慮多余文字的噪音干擾問題了。想到就干,標(biāo)了一個通宵的圖,老破本跑了一天的訓(xùn)練,測試下來行得通,單檢測集裝箱號準(zhǔn)確率非常高。OCR的輸入問題解決了,起碼在這個層面上不會對最終準(zhǔn)確性有大的影響了。
到此,整個pipeline是這樣了:目標(biāo)檢測識別集裝箱號–>送入OCR引擎–>OCR結(jié)果輸出 。下面就是思考OCR引擎如何實現(xiàn)了。
OCR引擎選擇
之前已經(jīng)放棄了短期內(nèi)用NN實現(xiàn)OCR的考慮了,那么只能回到傳統(tǒng)的機器學(xué)習(xí)的思路了,這就簡單了,不用選了顯然是考慮Tesseract了。那時候還是3.05版本,4.0的LSTM版本還沒有正式發(fā)布,不敢用,Tesseract怎么訓(xùn)練可以參考我的這篇文章:使用Tesseract訓(xùn)練并識別集裝箱號。到真正開干的時候,手頭有大幾千張集裝箱圖片,手動扣出來,再做點處理,湊個萬把張集裝箱號圖片還是可以的,訓(xùn)練tesseract還是夠用的,但是想都不用想,準(zhǔn)確性超8成都不容易,原因是在這里Attention-based Extraction of Structured Information from Street View Imagery有這么一段話:
Disclaimer This code is a modified version of the internal model we used for our paper. Currently it reaches 83.79% full sequence accuracy after 400k steps of training. The main difference between this version and the version used in the paper - for the paper we used a distributed training with 50 GPU (K80) workers (asynchronous updates), the provided checkpoint was created using this code after ~6 days of training on a single GPU (Titan X) (it reached 81% after 24 hours of training), the coordinate encoding is disabled by default.
所以,在Tesseract之外,應(yīng)該還有預(yù)備另一套解決方案,否則就是明知有可能死還要去死了。聽起來像是笑話:以你手頭的資源和技術(shù),你已經(jīng)沒路可選了,你還要給自己多留一條路?嗯,世上無難事,只怕肯琢磨。在訓(xùn)練Tesseract標(biāo)注的過程中,最大的收獲就是基本理解了Tesseract OCR的實現(xiàn)pipeline:圖片二值化–>字符邊框–>分割–>識別。一遍一遍的訓(xùn)練,再傻也琢磨出來了:關(guān)鍵還是邊框要準(zhǔn),邊框準(zhǔn),識別就會準(zhǔn)很多。前面已經(jīng)驗證過了用目標(biāo)檢測的方法找到集裝箱號這個區(qū)域是很準(zhǔn)確的,那么我把檢測到到的集裝箱號再做一次標(biāo)注,再訓(xùn)練一個36分類的目標(biāo)檢測,不是就變相的實習(xí)了OCR么?
集裝箱號是順序的,這么目標(biāo)檢測打散了輸出,怎么再拼回原來的特定順序?這個不難。修改一下visualization_utils.py里的visualize_boxes_and_labels_on_image_array函數(shù)的定義,讓他多返回一個box_to_color_map參數(shù),這個返回值是一個Dictionary,其中key是我們訓(xùn)練定義的label,value是一個四個值組成的List,四個值分別是輸出的bounding box的相對原始圖片坐標(biāo)原點的相對偏移值,拿這個list沿著X軸,Y軸分別做一次排序,label的順序就是集裝箱號的順序了。
集裝箱識別與計數(shù)
到目前為止,構(gòu)想中的pipeline已經(jīng)實現(xiàn)了OCR部分的設(shè)計,下面就是集裝箱識別與計數(shù)了。之前已經(jīng)給定了一個條件:單攝像機,現(xiàn)在又多了一個條件:需要集裝箱門(端門)出現(xiàn)在攝像機畫面中才行,否則無法找到我們需要用來做OCR的集裝箱號區(qū)域。所以,集裝箱識別就簡單了,理論上,只需要標(biāo)注集裝箱門、集裝箱號這兩個標(biāo)簽,就夠用了。這又帶來一個新問題:“如何確保始終是集裝箱門可以對著攝像機位置?”。與用戶溝通、現(xiàn)場實地勘查后確定:工作過程中不可能要求并約束集裝箱門的朝向,即使這樣要求,也無法確保工作人員一定就按你要求來。
繼續(xù)考慮單攝像機方案,就意味著至少50%概率要OCR識別的是集裝箱后門(標(biāo)準(zhǔn)用詞:盲端,end_door)噴涂的集裝箱號,結(jié)果就是即使短時間做出來Demo了,驗證準(zhǔn)確性也不會好,做出來和做不出來會變成一個結(jié)果。
毫不謙虛的說,這個時候就體現(xiàn)出了我這個非典型碼農(nóng)的復(fù)合知識結(jié)構(gòu)價值了。
直接在現(xiàn)場勘查階段和用戶溝通確定,既然做不到肯定給我識別到集裝箱門,那么只有現(xiàn)場架設(shè)兩個攝像機對拍,只要集裝箱出現(xiàn),肯定有一個攝像機畫面中是集裝箱門。然后,進(jìn)一步確定了攝像機的安裝位置、角度、光照條件(正好東西走向,早上、傍晚總有一個攝像機是大逆光)、網(wǎng)絡(luò)、供電等一系列施工要求;再然后,描述了整個識別業(yè)務(wù)流程和基本的頁面展示效果、會展示的數(shù)據(jù)以及對他們對接數(shù)據(jù)對接要求,最后明確了最終效果:不可能做到100%準(zhǔn)確這個標(biāo)準(zhǔn),大致準(zhǔn)確率在8成以上,這樣的結(jié)果如何進(jìn)行人工后續(xù)復(fù)核與干預(yù)。一切談攏,回去就是一個字:干!
算法訓(xùn)練與產(chǎn)品實現(xiàn)
劃重點:不會銷售的產(chǎn)品不是好碼農(nóng)
用戶溝通三要素:
- 明確需求、目標(biāo)
- 明確權(quán)利、義務(wù)
- 明確交付標(biāo)準(zhǔn)、控制期望
采集數(shù)據(jù)、產(chǎn)品設(shè)計
前期做簡單驗證的時候,圖片數(shù)據(jù)只有一千張不到,這個量級肯定不夠,發(fā)動所有銷售和現(xiàn)場交付人員,有條件沒條件都要創(chuàng)造條件去集裝箱貨場、碼頭拍照片,到最后總算弄了、8千張照片回來,逐一篩選后,留下了七千張能用的。本來數(shù)據(jù)集就小,不去人工做篩選,弄點臟數(shù)據(jù)進(jìn)去,坑自己啊。 普通的產(chǎn)品經(jīng)理接到這樣的任務(wù)可能就好干了:pipeline你也確定了,頁面展示和數(shù)據(jù)你也確定了,目標(biāo)檢測的標(biāo)簽和標(biāo)注內(nèi)容你也確定了,那我就按你說的干唄,肯定沒錯,領(lǐng)導(dǎo)說的嘛。嗯,我真遇到了一個這樣的普通產(chǎn)品經(jīng)理,一點沒有擴展思路和增加產(chǎn)品價值的想法,于是,我把她開了,這事情從頭你就在跟,現(xiàn)場你也跟著去了看了,和用戶怎么談的全程你也參與了,回來我也給你說了我的想法和這個產(chǎn)品怎么做出更多讓用戶看到后驚喜的東西,但是,你都沒有g(shù)et到……
好的產(chǎn)品經(jīng)理不是萬里挑一就是千里挑一,比例不會再高了。
最后,定了6個label,3個是不同位置的集裝箱編號,3個是集裝箱的側(cè)面和兩個端面。因為,做的是一個用戶的需求的事情,站在產(chǎn)品的層面需要考慮的是一個行業(yè)的事情,這個事情做出來,落地了,在行業(yè)內(nèi)別的用戶怎么把這種技術(shù)導(dǎo)入到人家的需求里面,這個是我在標(biāo)注圖片的時候考慮的最多的事情,反正標(biāo)注圖片是體力活,腦袋閑著也是閑著。只有多定義標(biāo)簽后續(xù)才有更多的利用可能。
選擇模型、訓(xùn)練
目標(biāo)檢測模型選擇這個事情么,之前更小的數(shù)據(jù)集已經(jīng)做過訓(xùn)練驗證,對于最終的準(zhǔn)確性是放心的,反正我做的是遷移學(xué)習(xí),訓(xùn)練集也不大,10萬步也不要幾天,只需要在快和準(zhǔn)之間做好平衡就好,最后定的是生產(chǎn)用faster_rcnn_inception_v2,后來做人臉,我也用的他做了人臉檢測的嘗試,也把數(shù)據(jù)集從VOC格式轉(zhuǎn)到Y(jié)OLO格式,在DarkNet下也訓(xùn)練了兩個YOLO v2的模型出來,然后把tiny版本的部署到NVIDIA Jetson TX2上面,跑跑看效果,琢磨下如果用Tetson TX 2做邊緣設(shè)備,把這個落地方案做成邊緣版本的可能性。后面這一波操作都是自娛自樂,總要有學(xué)習(xí)新東西的快樂么,當(dāng)然了,到這個階段我辦公室已經(jīng)有2*1080Ti的PC和4*1080Ti的服務(wù)器\4*P100的服務(wù)器各一臺了,我可以一邊訓(xùn)練一邊愉快的玩耍了。
如果說目標(biāo)檢測訓(xùn)練是玩耍的話,那么Tesseract的訓(xùn)練就是折磨,訓(xùn)練結(jié)果果然不出意外最初就6、7成的樣子,泛化能力很一般。剛開始百思不解為什么結(jié)果會這么意外,都已經(jīng)準(zhǔn)備動手執(zhí)行目標(biāo)檢測的36類方案了。花了大概兩周的時間,一遍一遍的訓(xùn)練,驗證(Tesseract3.05就這點好,數(shù)據(jù)準(zhǔn)備好,訓(xùn)練一輪分分鐘的事情)。最后找到了泛化差的原因:訓(xùn)練的圖片原始分辨率高低波動很大,訓(xùn)練出的lang文件打開來看,內(nèi)部本來就有訓(xùn)練的錯誤,所以別指望輸出能對。
于是同時做了兩件事:1、花了好幾天,把幾千張圖片按初始分辨率分為了4類,又再每一類里再細(xì)分3類,也就是說把訓(xùn)練樣本定義為了12類,每個小類單獨訓(xùn)練。因為Tesseract的Clustering操作有個特性是可以把多個樣本的訓(xùn)練輸出數(shù)據(jù)放在一起聚類打包,所以,我就愉快的在這15類之間做排列組合;2、讓銷售協(xié)調(diào)用戶盡快按最終的部署場景給采集一批實際作業(yè)視頻與圖片,協(xié)調(diào)的結(jié)果是銷售哥們兒自己買了2個攝像機配上三腳架,問人家晚上有作業(yè)的時間,頂著刺骨寒風(fēng),坐在集裝箱上面用筆記本錄了兩晚上的視頻,然后,嗯感冒了。然后,目標(biāo)檢測的算法已經(jīng)好了,視頻拿來跑一圈,把集裝箱號裁剪出來,拿來做Tesseract的驗證,就這樣痛苦的折磨了自己差不多2個月,總算基本搞定,再加上一些工程化的技巧和數(shù)據(jù)處理,基本具備拿出去給用戶POC的條件了。
pipeline的demo實現(xiàn)
代碼在這里:https://github.com/lonelygo/container_detection
寫在最后的話
寫到這里,篇文章也算要結(jié)尾了。
重裝了電腦,裝了一個Intel預(yù)編譯版的TF也不知道干點啥好,據(jù)說是比Google預(yù)編譯的CPU版本的快很多,但是我現(xiàn)在手頭沒有GPU的電腦啊,沒法比,所以就拿手里有的一部分?jǐn)?shù)據(jù),重新把17年的這個過程再玩一遍,當(dāng)時CPU什么速度,GPU什么速度我還是記得的,這樣比對我而言最容易。
最后,發(fā)張檢測效果圖。
【本文是51CTO專欄機構(gòu)“AiChinaTech”的原創(chuàng)文章,微信公眾號( id: tech-AI)”】