寫 在 前 面
本文來自 Jetson 開發者 - 素為先生供稿。文章裡,素為先生對從項目發起,到項目中遇到的挑戰,以及攻克瓶頸的點點滴滴均做了詳細陳述。我們在此將此篇文章共享給 NVIDIA Jetson NANO 開發者朋友們,以期共勉!
正 文
做象棋機器人基於兩個原因:
第一個原因,家裡的小朋友喜歡在手機電腦裡找遊戲玩,我不想讓他總對著屏幕,傷眼睛,而我也不是總有時間陪他下棋,所以如果有一個不帶屏幕的下棋機器人陪他下棋,既體驗了科技智能,又不傷眼睛。
第二個原因,老爺子是個中國象棋高手,從小我就下不過他,現在我已不惑之年,依然不是他的對手,我想藉助「巨人的肩膀」打敗他。雖然本質上都是算法,但與機械臂的對戰和與電腦屏幕的對戰,感受是不同的。
pipeline三個階段:識別棋盤+棋局智能+機械操控
整個項目的技術架構圖:
01
識 別 棋 盤
2018 年底,通過攝像頭拍攝象棋棋盤的圖片,然後通過深度學習的方法來識別棋盤的棋子所在位置情況。具體的過程如下:
1、攝像頭拍照
為了從頂部拍照,我買了第一組裝備:一個攝像頭和一個支架。那時的考慮都比較樸素,就地取材,棋盤用的是比較大的(50cm*45cm),支架的高度有限,拍照的位置比較低,所以 USB 攝像頭略貴,帶防畸變效果的。
2、從照片中抽取棋盤部分
因為技術不行,不能適應所有的背景,所以為了將棋盤主體部分從背景中抽離出來,把以前做設計桌遊原型剩下的藍色卡紙墊在棋盤下,作為背景,這樣,就可以很方便地從中抽取棋盤主體部分了。
3、將棋盤照片四點對齊
這個步驟其實是很久以後才加上的,因為看到單位配備的高拍儀,就是用攝像頭當掃描儀的裝置,發現它除了相對固定的位置,還有四點對齊的矯正功能。高拍儀能做,我也能做,於是四點對齊就做出來了,再也不怕拍攝的棋盤歪歪扭扭影響識別了。
4、將棋盤切成 9*10 的小圖片
對齊後的棋盤,就可以切成 90 張小圖,每個小圖,或有棋子,或沒有棋子。
我是準備用深度學習最簡單的 Classification 來做棋子的分類,每方7種棋子,加上空白棋盤位置,總共15類。
為了做深度學習,我用前面部署的一套環境和流程,生成了四千多張各種棋子在各個位置的圖片,有的齊整一些,有的故意擺放歪一點,挪動棋子的時候,也專門旋轉一定角度,有的燈光亮一些,有的故意製造一些陰影。切出來的棋子,手工進行分門別類,然後加上旋轉、噪點各種處理,製造了幾萬張的深度學習訓練數據。
5、分別判斷每個棋子上的信息
2019 年春天 Jetson NANO 發售了,於是決定拋棄掉 PC 機的方案,決定用 NANO 做,這一次,我本來也是想過用樹莓派,然後用雲端或者同 一 wifi 網絡的 PC 機提供算力的方案,現在有 NANO 了,就可以在無需聯網的情況下單機實現了。不聯網,對環境也更加魯棒!
終於把深度學習Classification 的程序改造好了(其實就是拿初階教程裡花朵識別的 cnn 模型改的),結果運行效果大失所望,識別率不行,根本達不到訓練程序告訴我的 97% 以上,達不到心中的及格線,期間,我也考慮過,不用 Classification,而用 Object Detection,也曾用很大的耐心標註棋盤數據,但正確率和位置的準確率沒有分類好,畢竟,如果不考慮效率和成本,把問題控制在最小可控單元是最明智的,能不依賴深度學習黑箱是最好的。
直到今年5月中旬,發現百度居然推出了 「EasyDL 前端智能計算-軟硬一體方案」,可以把之前訓練的那一套模型移植到 Jetson NANO 等設備上離線運行,按照大致文檔,終於部署上去了,不但離線了,NANO 的 GPU 功能用起來了,而且速度飛快無比啦!
6、將棋子信息融合成棋盤信息
elephantfish 作為智能引擎,它需要輸入一套它既定的命令行格式,這便是本項目生成的第四組棋局信息格式了。至此,pipeline 的第一步「識別棋盤」終於搞定啦!
02
棋 局 智 能
通過棋盤局面,計算下一步棋如何走,這是本階段的輸入和輸出。
我用的智能引擎是 elephantfish。在 github 上,你可以看到,它也是幾個月之前才剛剛開源。
03
機 械 操 控
最拿不準的就是機械控制部分,因為之前完全沒有做過這方面的創作,一路跌跌撞撞,最終居然給整成了
1、絲杆直線滑臺模組方案
還記得最初構思這個項目的時候,因為找到的資料有個視頻,是國外一個人做的西洋棋,用的是 XYZ 絲杆滑臺模組,所以我最初的思維也局限在用巨大的絲杆滑臺模組。這屬於 CNC 數控工具機的範疇。最關鍵的是,XYZ 絲杆滑臺模組 3 個含步進電機及控制器一套配下來要三千多,冷靜下來後,確實覺得,本來就是好玩的事情,搞太認真就輸了。於是,自此開始,放棄了最早的大棋盤、大棋子,換成小棋盤、小棋子的方案了。
2、機械臂方案
這次放棄直線滑臺模組方案之後,我覺得可以考慮機械臂,畢竟機械臂比直線滑臺模組更像人,也更酷。
剛拿到機械臂時,我是迷茫的,還是那句話,之前根本沒碰過。後來七摸索八摸索,終於能用 python 的 pyfirmata 庫來控制自如了。其實也不難,就是三個舵機控制三個自由度,一個左右方向劃圓圈,兩個縱向的軸(就像人的手臂一樣,一個主動軸,一個次動軸),設定特定的值,數字舵機就會挪動到特定位置,機械臂三軸就組成指向特定坐標的點。然後上面的氣泵吸盤也很有意思,我原本以為就是個吸氣裝置,它還有個電磁閥,當我弄懂這一套的原理,就像回到高中的物理課堂一樣的爽。氣泵吸盤也是用 pyfirmata 庫來控制。用 Arduino UNO 來輸出 PWM 信號,當舵機的控制器。
這個機械臂也有缺點,不過可以克服。缺點就是,它的縱向只有兩個軸,而棋子很小,如果要切到平面,其實是容易碰到旁邊臨近的棋子的,不過我有辦法,用增加路徑的方式彌補,在臨近棋盤的地方多折一些小的往返,就可以躲過臨近的棋子。
機械臂比直線滑臺模組酷多了,我原本我要與它廝守終身了,看這個視頻,就是我曾經發的一個朋友圈,其實只是一個概念演示,當時卻獲贊無數啊!哪知道,有一天,情況急轉直下,隨著機械臂工作,誤差完全是一個沒法預測。原本還想學學機械臂的空間坐標系,通過計算來定位 90 個點的,結果發現這一茬,那叫一個沒法用啊!
3、SCARA機械臂
總之,普通的機械臂方案是被否了,我也曾看過精準度較高的機械臂,曾經想過狠心地幾千元整一個,然而去年考上了夢寐以求的法理學博士,是在職讀書,學費嗨貴,還要養家餬口,實在是沒錢買高精度的機械臂了。
所以,看到有 SCARA 機械臂,結構很奇特(大家可自行搜索,設計很奇特),感覺精準度也有保障,但始終沒忍心買一臺嘗試。
4、皮帶滑臺模組
無論如何,下棋機器人的項目要繼續,我的目光又回到了直線滑臺模組,我準備買一臺皮帶滑臺模組的寫字機器人來改造,從尺寸來看,也能夠勝任目前的棋盤大小,而且寫字機器人連字都能寫,精度一定不成問題。
其中最麻煩的一個問題,是滑塊的鋼珠好多都掉了,也不知道是它本來就掉了好多,還是在運輸過程中包裝不善才掉的,總之是不能正常使用了,於是我就去淘寶買新的滑塊。諮詢的時候,淘寶客服不屑地丟給我一個網址,讓我自己看圖紙,我居然自己會看圖紙了,型號是 LWL9B,然後客服又丟給我一個對照表,LWL9B 應該是進口的型號,與之對應的國產型號叫 MGN9C,於是我自信滿滿地說,這個給我來3個吧!
最後,滑塊終於算是配套上了。皮帶滑臺模組的硬體搞定啦!
5、CoreXY 結構的皮帶滑臺的控制
搞定了硬體,軟體的時候傻了,我總是很傻很天真,以為天下所有的滑臺都是直線滑臺那種 XY 笛卡爾坐標系的,XYZ 軸分別每個軸一個步進電機控制,結果眼前的這個皮帶滑臺模組的寫字機器人,它的 XY 軸是一體的,一根皮帶聯動兩個軸,兩個電機同時向前則 X 軸向前移動,同時向後則 X 軸向後移動,左前右後則 Y 軸向右移動,左後右前則 Y 軸向左移動,一番摸索之後,發現答案是有的!
這套寫字機的3個步進電機也是通過 arduino UNO 來輸出 PWM 作為控制器的。我們的需求也比較簡單,就是定位象棋棋盤的90個點。
6、小補丁
最大的一個問題是,從近處的棋子挪到遠處沒問題,但是,把遠處的棋子挪回近處,涉及到 X 軸的換向問題時,它這個一體結構的缺點就暴露了,X 軸有個很明顯的偏移誤差。不過,幸好這個偏移誤差是個固定值,我只需要在程序中加個補丁,判斷是否是這種換向折返的情況,如果是,加上這個固定值就行了。
7、技術不夠,裝備來湊
我一直奉行的「技術不夠、裝備來湊」的山寨理念。這只是一個原型 demo,如果將來有機會量產,很多東西不會這麼花錢和浪費的。
8、氣泵吸盤
由於滑臺的 XYZ 軸是用 serial 庫來控制,由一個燒錄了 gbrl 的 arduino 來控制,所以氣泵吸盤就只能用機械臂時的老辦法 pyfirmata 庫了,這是另一個 arduino。前一個 arduino 接口不夠,加了個擴展板,還需要額外的電源支持,後面這個不用擴展板和額外電力支持,從 NANO 的 USB 口供電,經測試沒問題。
我把氣泵架在寫字機原本的筆架子上,尺寸距離都剛剛夠用,好不愜意。
04
整體移植到 NANO 上
移植到 NANO 上,這就是近幾天的事情了。
首先是百度 easyDL 的離線 SDK 只適用於 JetPack 4.2.2 版本,
再就是,終於我要換 DC 電源了,依然是求助供應商,她慷慨地順豐給我寄了一個過來,在等待電源的幾天裡,就像要到人生巔峰的感覺,充滿了期待。果然拿到電源之後,一切都非常順利,如果一切都能像這幾天這麼順利,就不會前面嘰裡呱啦寫一萬多字了。
唯一就是發現,前面矯正圖像畸變(四點對齊)的過程比較慢,大約足有兩分鐘,應該是默認的openCV用的不是GPU的緣故,於是按照JetsonHacksNano的buildOpenCV方案,略微修改了下shell腳本,就編譯帶CUDA加速的openCV成功啦!然後矯正圖像畸變的過程就比較快了(大約10秒,可以忍受的範圍,知足了)。
將Windows上python代碼移植到NANO上,其實沒什麼難度,對本項目來說,唯一需要修改的,就是接arduino的USB口的名稱要改,Windows 電腦上叫 COM3,到了 NANO的 ubuntu 系統裡,可能就叫 ttyACM0 或 ttyUSB0 了。
目前,項目已經可以順暢地跑起來了。我們家的老老小小已經與機器鏖戰過幾盤了。接下來,加上一些循環和邏輯,設置成開機啟動,然後接上 GPIO 的按鈕(每次人類下完一步之後,就按按鈕,然後機器就開始識別-思考-執行),這些應該都沒難度了,都是很久以前都搞定的代碼了。
最後,奉上最終效果視頻的另一段,是父親節當天晚上老爺子與機器裝置的對戰