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