FPGAデザインコンテスト@FPT2018 開発記
FPGAデザインコンテスト@FPT2018 開発記
はじめに
この記事はFPGA Advent Calendar 15日目の記事です。
先日FPT2018にて行われたFPGAデザインコンテストに参加しました。
FPGA搭載の自動運転ロボットが、決められたコースを走りながらいくつかの課題(障害物回避・信号検知・人間検知)をこなすといった内容のものでした。
FPGAボードに搭載されているハードマクロCPUの使用は認められています。外部との通信が完全に禁止されているのですべての判断・計算をロボットに搭載されたシステムで行う必要があり、効率的なシステムを構築する必要があります。
第8回 相磯秀夫杯 FPGAデザインコンテスト
大学院の同じ研究室の友人と2人で参加し、なんとか優勝することができました。
ここではその開発記・自分が担当した実装の内容について記録したいと思います。
友人とは完全に分業制で開発を行ったため、自動運転システムの進捗は全くわかりません。
まずは走行の様子の動画です。障害物の回避・信号の検知に成功しています。途中でコースアウトしてしまったのが残念・・
ZytleBot 本戦
時系列と、信号検出の詳細について紹介します。
だらだら書いてたらだいぶ長くなってしまいました・・ブログってもっと完結に書いたほうがいいのかな、
自分が作ったものはココにおいてます。全く整理していないので整理していきます・・
github.com
時系列
5月・6月
コンテストの存在を知る
7月
- コンテストに参加したいと指導教員に伝えて、ZYBO + TurtleBot3を使用することを決めた
(研究室にTurtleBot3が存在したから選んだだけ)
- ココの記事を参考にZYBOにUbuntu OSを搭載した
( USBの端子が2つある8ポートハブの片方をZYBOに、もう片方をモバイルバッテリーに接続することで無理矢理電源を安定させている)
BSH8U01 USB2.0ハブ 8ポートタイプ : USBハブ | バッファロー
8月
(デバイスドライバのソースコードを読んだり、デバイスツリーについて勉強した)
- 画像が取得できるようになり、自動運転SWの開発を完全にチームメイトに丸投げする
大学の地下室のコース設置なども丸投げしてしまって申し訳なかった
- 某社夏期インターンシップに1ヶ月間参加
9月
- 慶応大学日吉キャンパスにて国内大会が開かれる。
カメラ画像の取得以外は完全にSWで頑張っている状態で3位入賞。自分の貢献はほぼゼロ。
1位で優勝したチームが圧倒的強く、FPGAをうまく活用していることに憧れる、Twitterで交流する
10月
- あと2ヶ月しかないという状況のなかFPTに向けて開発開始
- PCamで取得した画像をFPGAで処理してからCPUに転送しようとしていろいろやってみる。
- 結局1ヶ月かかって失敗(現在も未解決)
10月末〜11月
あと1ヶ月で何ができるかを考えた
方針は、
- むやみやたらに挑戦せず、頑張れば出来そうなラインをみつけて頑張る
- 実装が公開されているものを探しまくってパクる・ひたすらググる
- 必ず1ヶ月で完成させる、妥協するところは妥協する
信号検出ならなんとかなりそうということになり、信号の検出を目指すことに。
とりあえず3Dプリンタを購入してもらったので信号機を作った。
ガバガバ工作だけどFPTデザインコンテストのために信号作った pic.twitter.com/aP7ECKkFRR
— lp6m (@lp6m2) 2018年11月7日
「ゼロから作るディープラーニング」を斜め読みして、「無理」となったので、ディープラーニング以外の機械学習アルゴリズムで、実装を完全に理解してフルスクラッチで実装できるアルゴリズムを探した。
以下の記事を参考にした。ありがとうございます。。
HOG特徴量とSVMを使った自動車の検出 - くーろんログ
https://qiita.com/mikaji/items/3e3f85e93d894b4645f7
ランダムフォレストとSVMの使い分け - 静かなる名辞
ランダムフォレストのつくりかた(C++の実装例つき) - じじいのプログラミング
ZYBOにMIPI経由で接続されているPCamとは別にWebカメラを取り付けて、Webカメラから取得した画像から信号を検出する。
PCamは下向き、Webカメラは上向きに取り付けた。
次に実装が落ちているものを探した。以下のリポジトリにたどり着いた。
①HOG+SVMによる人間検出 on Zedboard
GitHub - nikkatsa7/HOG_Zedboard: A real time Histogram of Oriented Gradients Implementation on FPGA
Real time HOG implementation on Zedboard - Xilinx XOHW18-222 - YouTube
・こちらはXilinxのコンペで優勝したらしい
・LinuxからAXI4/AXI-Lite経由でHLS IPを使う実装が公開されているので採用
・実はこのHOGのHLS実装が間違っていることに後々気づく
②Python scikit-learnでRFによる車の検出
GitHub - t-lanigan/vehicle-detection-and-tracking: Detecting vehicles in a video stream using machine learning. Adds on to lane detection project.
・scikit-learnなんて使ったことないけどとりあえず実装が落ちていたので採用
この2つを参考に信号検出器を作成した。どうにか1ヶ月で完成させないといけなかったのでスケジュール管理したりしていた。
HOG特徴量とRFを使った信号検出器の作成
結局やったことは①と②の実装を組み合わせただけなんですが、、、まあ何をやったか書いていきます。
ランダムフォレスト・物体認識の仕組みの理解
まずは、②のソースコードやRFに関する記事を読んで、何をやっているのかを理解した。
(たぶん当たり前すぎて今更な内容なのでしょうが、恥ずかしながらそれすら知りませんでした。学部でこんな勉強したっけ・・?自分にとっては初めてだったので、まとめておきます。)
・学習
信号の画像(32x64)→特徴量の抽出(画素・ヒストグラム・HOG) → RFの決定木を作成
・推論
カメラから取得した画像 → ウインドウをずらしながら画像を切り取り → 切り取った画像それぞれに対して → 特徴量の抽出(画素・ヒストグラム・HOG) → RFによる認識
RFについての詳しい説明は省略しますが、いっぱい推論してくれる木が複数あって、
特徴量は1次元の配列に格納され、それぞれのif文の木をたどると(赤信号のサンプル数、赤信号以外のサンプル数)が得られます。
特徴量抽出・RF推論器のフルスクラッチ実装
②の実装において特徴量の抽出はscikit-learnとnumpyを使うことで数行で実現されている。
ライブラリのソースコードを読んでC++へ移植。scikit-learnのHOGをC++でそのまま実装すると大変な行数になった。
https://gist.github.com/lp6m/349948c876bf1b80abe06bb9bfaed37a#file-hog-cpp
RF推論器のフルスクラッチ実装は簡単で、Scikit-learnで学習したモデルのを再帰的に辿ることで、if文の木を作成できる。というかほぼ答えみたいなのがあった
stackoverflow.com
フルスクラッチ実装ができたことで、Pythonとscikit-learnを用いて学習させ、学習モデルをC++コード化してC++から利用することができるようになった。
ちなみに、本来は抽出した特徴量に対する正規化を行ってから、RFに推論させるが、正規化処理はそれぞれの特徴量の配列要素に対して独立した計算なので、RFのif文の中のしきい値を逆正規化することで、推論時の正規化処理を省略している。
学習用画像作成ツール・学習済みモデルテスト用ツールの作成
・32x64の画像を学習用に大量に作成する必要があり、撮影した動画から生成した連番画像から信号の部分を手作業で切り抜く必要があった
・自分用にツールを作ったほうが早かったのでTkinterを使って作成
Python + Tkinterで連番画像ファイルを素早く切り抜くGUI画像トリミングツール - Qiita
・これを応用して、学習済みモデルを簡単に試すためのツールも後に作成した。(左下に赤信号である確率が表示される)
わりと外乱があっても精度がでていることがわかる。
ZYBO用にHOG計算の簡単実装・HLS実装
C++で実装したリアルタイム信号検出器をZYBOのCPUで動作させると1フレームにつき1.5secほどかかってしまった。主にHOG特徴量の抽出が非常に重たかった。(sqrtしたりatanしてるので当たり前)
そこで①の実装を参考にする。ユークリッド距離の計算・ヒストグラム正規化を簡略化したりatanのテーブルをLUTに持たせることで高速化していることを参考にする。
ただ、この実装をそのままパクって使うと、SWでの計算結果と実際にFPGAにインプリメントした際のHWの計算結果が異なった。
理由はVivado HLSにおけるpragmaにあった。`#pragma HLS DEPENDENCE inter false`で配列インデックスへに対するループ依存がないことを高位合成ツールに伝えてパイプライン化を実現しているが、実際にはループ依存が存在する。
ただしこのpragmaを外すとレイテンシが大きいHWが生成されて頭を抱えた。
ふとインターンでやったラインバッファのことを思い出して、「HOG LineBuffer FPGA」で検索をかけたところ、以下の論文アーカイブ(?)に到達した。
(https://arxiv.org/ftp/arxiv/papers/1802/1802.02187.pdf)[A High-Performance HOG Extractor on FPGA]
各画素のgradientを計算する際に4近傍の画素の情報が必要になるが、ラインバッファで縦=3, 横=画像の幅=64の画素をもっておくことでループ内での画素読み込み(ブロックRAMからの読み込み)が1回で済む。
これを参考に、ラインバッファを使用したHOG特徴量抽出HLSコードを作成。レイテンシも短く、SWとHWで結果が一致するHLSコアが完成した!
■参考にしたリポジトリのHLSコード:ラインバッファなし
HOG_Zedboard/hog.cpp at master · nikkatsa7/HOG_Zedboard · GitHub
■実装したHLSコード:ラインバッファあり
ImageDetectionHW/main.cpp at master · lp6m/ImageDetectionHW · GitHub
比較すると、画素情報が格納されている`image_buffer`へのアクセス(=ブロックRAMへのアクセス)がループ内で1回で済んでいることがわかる。
↓これはなんかHLS実装してたときのメモ
FPGAへの実装・Linuxからの利用
作成したHLS IPコアは1枚の画像のHOG特徴量を計算してくれるコアなので、ウインドウが300個あるときは300回HLS IPコアを実行する必要がある。
Vivado上でHLS IPを4つならべて、4つの画像のHOG特徴量を同時に計算してくれるようにした。
LinuxからUIOを用いてHLS IPを利用するにあたっては①の実装を丸パクリさせていただいた。ありがとうございます。。。
結果的にSWで計算するよりも5,6倍高速になり、ZYBO上で12〜15fpsの信号検出を達成した。FPGAを活用したといえる(?)状態にはなった。
完成したものを友人に投げて、自動運転システムに組み込んでもらった。
おわりに
なんとか1ヶ月でFPGAを活用した信号検出プログラムを作成することができた。
画像認識や機械学習も初めてで、HLSツールもまともに使ったことがなかったのでなかなか頑張ったとは思っている。
開発中は、コースの整備やロボット本体の作成などに予想外に時間を取られた。
開発記には書いてないが、OpenCVを使ってカメラ画像を取得するとCPU使用率が非常に高くなったのでV4L2 APIをゴリゴリ叩いてカメラ画像を取得したりしている。
そういった絶妙なハマりポイントに陥りまくった。
手元のPCでは余裕で30fpsで信号を検出できるのにZYBOで実行したら1.5fpsとかになったりして、エッジデバイスの弱さを実感した。
通信プロトコルがAXI4/AXI4-LiteなのでCPUが通信を制御する必要があり、結局SW/HW間の通信がボトルネックになっているのが残念。
AXI-Streamプロトコルを使ってDMA転送することでもっとCPUの負荷を減らせるとは思う。DMAをLinuxから使えるようになりたい。
使えるようになったらブログにまとめたいと思う。
まだまだ課題もあるが、コンテストのおかげで色々と勉強することができた。
Twitter等で助言を頂いた方々、ありがとうございました。
FPGAデザインコンペ@FPT2018優勝しました🏆
— lp6m (@lp6m2) 2018年12月12日
自分の担当は信号検出のHW実装でした
やり残したこともたくさんあるので次回も頑張ります(?)
ありがとうございました pic.twitter.com/spdVpjta5d