高度運転支援向け単眼カメラの実装(4.道路認識エンジンの実装)
前回は全結合ニューラルネットワークの試作までをしました。通常は畳み込み層を多段追加したCNNを利用して画像認識を行います。
一方で、ハードリアルタイムの要件を満たすには、この畳み込み層のえげつない量の行列のドット積を次フレームが来る前に素早く計算していかなければなりません。
学習を済ませた重み係数だけ使うとしてもそれだけは変わりません。計算量が多すぎます。それで、本当に深層学習に頼る必要があるか、それとも一部だけ軽量NNにするのか、線形解、k-NNやSVMで済むのか、といった問題を再考慮しました。
軽量特徴量の調査
画像認識も条件を追加すればもう少し楽になるはずと色々調査しました。
例として、画像認識における回転不変性。これもCNNは各層のフィルタが学習することによって実装されていきます。
車載カメラの場合は物体識別の際に、回転不変性はあまり必要ではないかもしれません。例えば立っている人を認識するのと、倒れている人を認識するのは別フィルタを使用した方が良いでしょう。(後者の事故危険性が高いため、別の認識が必要)
www.aist.go.jp
2005年のニュースであるので、深層学習がはやる前にはなりますが、このHLAC、とても軽量です。
JSで実装されている例
rest-term.com
視点の変更
車、人、自転車、バイク、大型車両....
とセマンティックセグメンテーション方式で認識するのは大変です。
まずは道路とそれ以外を認識すべきと思いました。単純に考えて、前方が道路ではない=これ以上進んではいけないということです。
道路の識別
さきほどのHLACを用いました。先程のブログも参照するとわかる通り、1次のフィルタは画像の明度勾配になっています。2次はエッジの向きが読み取れます。
道路データはドラレコのデータの下1/4くらいを切り取り、(一応、前もって「道路」以外が映り込んでいないことを確認。) 永延とフレームごとにHLAC特徴量を流してヒストグラムを記録して閾値を設定しました。道路条件であまりにもヒストグラムがかけ離れている場合はk-NNなどの手法で、それでも無理な場合は全結合型の少ない段数のNNで分類を行います。
幸い、いまのところ舗装路ではそれほどかけ離れたデータはないため、1つの閾値だけ(つまり線形解) で「道路」を認識しています。
FPGA実装
人為的なバグを減らすため、カメラをFPGA直結でデータを取得するのではなく、PCからストリームを流し込み、計算のみCyclone10LP FPGA上で行い、また計算結果をPC上のSimulinkに返す、Simulinkは計算結果をビデオストリームに重ね合わせて着色する。という方式にしています。
www.youtube.com
青で着色:道路と認識
ピンクで着色:それ以外
撮影区間:さいたま市別所坂上(国道17号線:武蔵浦和駅から北浦和方面)
平面テクスチャが多い車両も「道路」とみなされているきらいがありますね...。でもこのくらいであれば認識に問題はなさそうです。
下記の、ビデオのデータと合成していないデータも見ると納得いただけるかと思います。
www.youtube.com
進行方向に車やバイクが現れた場合、大きい「ノイズ」が現れるので、速度抑止制御はそれほど難しくないかと思います。
これから
道路状況の変化をもろに受けているので「ちょっとノイズが強いかな」という感想です。
また、未舗装路の場合も調査しようと思います。
この辺りをもう少し閾値制御でどうにかなるのか調査してみます。
MATLABによる軽量NNの開発(2:CIFAR-10の分類テスト)
前回はMNISTでした。白黒で文字だけということで、限定的な状況であるので、全結合でも層を深くしなくてもそこそこの成績は出るかとおもいます。
今後のCNN(FPGA-BNN)ではチャネルが3(=RGB)で進めていくと思うのでチャネルを増やした時に正常動作するかの確認です。
あと独り言ですが、CIFAR-10でもRGB空間の例は多くありますが、分類をするという目的ならL * a * b空間か、HSV変換後にHとV使ってチャネル減らすとかの方がいい気もするんですがどうなんでしょ
CIFAR-10
lynxeyed.hatenablog.com
今回、Convolution層+ReLU層をそれぞれ一つ増やしました。カーネルは3x3にしました。
前回の記事で取り上げたようにそれぞれの層をクラス化しているので追加も減層も楽々です。
学習率をepochが進むにつれて、減らすようにしています。
結果
あまり思わしくはありませんでした。60%後半台。もう少し学習を進めれば70%半ばくらいはいきそうですが、このあたりで止めます。あくまでチャネルが増えた時の動作の確認というところなので、深追いはやめます。
考察
あまり改善しない理由として、バッチ正規化層、または重み係数のL2ノルムを(またはその二乗を)損失に加算する手法であるWeightDecay層、またはドロップアウト層などを追加していないというのが大きな理由といえます。
これらの手法を実装するのは特に難しくないのですが、BinaryNetでのアプローチはまた少し異なるので、この辺りにしておこうと思った次第です。
コード
以下からcloneできます。
github.com
次回あらすじ
次回から読み解いて実装していく論文ネタはこちらです。
ieeexplore.ieee.org
内容を簡単に:
2値化CNNではバッチ正規化がより重要になりますが、そのためには学習パラメータのスケール、シフト量、平均値、分散を求め、学習率を調整する必要があります。
上記論文ではこれを代替するバイアス値を確認し、バッチ正規化フリーのBinaryNetをシミュレートしていきます。
感想:
この英語論文めっちゃわかりやすい!
とおもったら筆者日本人だった、ナルホド
MATLABによる軽量NNの開発(1:環境の整備)
ゼロから作るDeepLearning
前回の話題からの続きになります。
こんな本をいただきました。有難うございます。
5章の誤差逆伝播を重点的にお勉強して、4~7章を読みました。SURF/SIFT/k-means/KL変換など古典的な画像処理をしていた人は4,5章だけでもいいかもしれません。それにしてもとても良い本ですね。この本。最近のNN論文を読むスピードが格段に上がります。
そういえば学生時代はまだ勾配から誤差を求めていて遅すぎて死にかけたことがありました。懐かしい。
MATLABを使用
MATLAB Deep Learning Toolboxいいですよね。僕もライセンス持ってて、よく使います。
しかし今回は、論文を見ながら各層をプリミティブに変更していくと思うので、自前で用意してしまってもいいかなという気持ちです。
今回の最終的に実装するエッジデバイスはFPGAですので、GPUとアプローチがかなり異なります。
そう考えると最終的にフレームワーク以外全部自前、ということになりそうです。もちろん、ある程度レイヤが固まったら、DLTつかってもいいかも。高速に動作するはずだし。兎にも角にも、Simulinkとの連携への期待も込めてMATLABで設計していきます。
雛形を作る
ニューラルネットワークの各レイヤの理論はすごいですが、プログラム上はそんなに複雑ではない*1ので、ほぼフルスクラッチでも大変ではないと思います。
上記の「ゼロD」本のコードをそのままMATLABに逐次翻訳は、できなくはないと思いますが、やる意味はなさそうです。
PythonとMATLABは1:1で逐次翻訳できそうでいて、全く思想が違う言語同士なので、動きを理解したら、1から書いた方が早いでしょう。
とはいえ、全部1から書くのも骨が折れます。雛形を用意し改造していく方がいいでしょう。今回のエンジニアリングの目的はコードを書くことではなく、論文を実機レベルに落とし込む設計をし、検証、実装することです。
で、いいものをMATLAB File Exchangeで見つけました。フルスクラッチの単純なMNIST向けCNNです。
jp.mathworks.com
ここの要となるコードは勾配を求めるnet_grad.m
ですが、ニューラルネットワーク各レイヤの伝播はこうなってました。
%%順伝播 z = w3*fc+b3; % Affine z = relu(z); % ReLu out = w4*z + b4; %Affine out = relu(out); probs = softmax(out); L = cross_entropy(probs, lab); %%------(中略)------------ %% 逆伝播 dw4= dout*z'; db4 = sum(dout,2); dz = w4'*dout; % ん?どこからどこまでが一つのレイヤかがわからん dw4 = sum(dw4,5); dz= diff_relu(z).*dz; dw3 = dz*fc'; db3 = dz; db3 = sum(db3,2); dfc = w3'*dz; %% うんこうんこ
ちょっと順伝播、逆伝播がわかりづらかったので書き直しました。
例えば、Affineレイヤを「ゼロD」本の様に記述すると、MATLABではこうなると思います。
%% Affineレイヤ classdef my_affine properties W b x dW db end methods function obj = my_affine(W, b) if nargin == 2 obj.W = W; obj.b = b; obj.dW = []; end end %% 順方向 function [obj, out] = forward(obj, x) obj.x = x; out = obj.W * obj.x + obj.b; end %% 逆方向 function [obj, dx] = backward(obj, dout) %size(obj.x) dx = obj.W' * dout; obj.dW = dout * obj.x'; obj.db = sum(dout, 2); end end end
こんな感じで各レイヤを記述すると、先程のnet_grad.m
のコードは以下の様になります。
%% 順伝播 [Affine1,out] = Affine1.forward(fc); [Relu3,out] = Relu3.forward(out); [Affine2,out] = Affine2.forward(out); [lastLayer, ~] = lastLayer.forward(out, label); %%------(中略)------------ %% 逆伝播 [lastLayer, dout] = lastLayer.backward(dout); [Affine2, dout] = Affine2.backward(dout); [Relu3, dout] = Relu3.backward(dout); [Affine1, dfc] = Affine1.backward(dout);
スッキリ書けます。今回各レイヤ(Convolution/Affine/ReLu/Softmax/Cross-entropy-error)を書き直し、この雛形に押し込んだ形にしました。
コード全文は以下から。
github.com
MNISTの文字の学習です。最初は文字認識率が80%後半台と畳み込みをしてる割にあまり向上しません。
4~5epochくらい回せば、95%付近まで改善します。暇な時に回してみるのもいいかもしれません。
↓ 半日くらい根気強く回せば、このくらい(96~97%ほど)
これから
FPGA向けBinaryNetの検証と、その派生論文を調べ、実装していきます。畳み込み層の計算量の低減を当面の課題とします。
このネタも、去年のRISCVネタ並みに引き摺りまくっていく次第ですのでよろしくお願いします。
*1:カルマンフィルタなどもそうですね
MATLABでCIFAR-10のファイル読み込み(だけ)してみる
中身のない内容なんだけれども、いざ使う時に忘れるので覚書きです。
物体認識用のデータ・セットに使われるThe CIFAR-10 datasetをMATLABで表示する方法です。
準備
読み込みと表示
画像を10000点分全て読み込み、ランダムな番号の画像ファイルと紐づく正解ラベルを表示します。
clear; close all; tic; load('./cifar-10-batches-mat/data_batch_1.mat'); % これは1~5,test_batchどれでも良い load('./cifar-10-batches-mat/batches.meta.mat'); imData = uint8(zeros(10000, 32, 32, 3)); dataColor = uint8(zeros(32, 32, 3)); %% 画像をロードする for i = 1:10000 data1 = data(i,:); dataColor = reshape(data1, [32, 32, 3]); imData(i,:,:,:) = dataColor; end %% ランダムに画像とタイトルを表示してみる selectNo = randi(10000); lab = labels(selectNo); ti = label_names(lab + 1); % MATLABは1スタートなので+1する IM1 = reshape(imData(selectNo,:,:,:),[32 32 3]); figure(1);imshow(imrotate(IM1,270),'InitialMagnification',500);title(ti); toc;
高度運転支援向け単眼カメラの実装(3.ニューラルネットワークの基本実装)
パーセプトロンの収束定理の壁
深度推定のフィルタを試作し様々な環境で調査して、ある程度どんな環境にも対応できそうなパラメータを決定しました。
が、すこし問題が出てきました。
- すごく白い道路を道路と認識せず、壁と認識し、警告が止まらなくなる。(協業しているイスラエルの会社からの報告。こちらでも再現がある程度出来ている)
- 昼間のフィルタパラメータでは薄暗い時に特徴点が減る。だが、深夜になると昼間と同等になったりする。薄暗い→夜の境目でのパラメータが全く不明。深度推定に問題が著しく出るわけではないが、運動視差などで補完できないレベルではある。
と、線形的にフィルタの閾値などを変更しても解決が難しい事象が出てきました。
この非線形な事象の解決にはニューラルネットワークが必要に思われます。ただしクラス分類は最小限にするなどして実時間での処理を目指します。
もちろん、高性能GPUで解決する方法はなくはありませんが、搭載するターゲット(軽車両、超小型車、既存車両への後付け)から考え、初期開発にお金をかけ、量産では費用をかけないものを目指します。
分類問題と回帰問題
行いたいのは深度推定とセマンティックセグメンテーションです。
この二者はディープラーニングでは少し異なる設計を必要とされます。
- 深度推定は回帰問題
- 車、人、二輪車、などは分類問題
これで出力層の設計が変わります。具体的には活性化関数が変わります。最終的にはあまり大した問題にはならないでしょう。
MATLABでNNの基本を設計する
実装にはMATLABを使います。Deep Lerning ToolboxやTensorFlowは使わず、バニラMATLABを使用します。それらのパッケージを使用しリソースリッチに実験するのも素敵ですが、その環境をFPGAや組み込みプロセッサへネイティブに動作させるとなると、移植に必要なエネルギーが膨大なのと、ハードリアルタイムに動作できなくなる可能性が高くなります。また、提供されている機能の大半は使用しないと思われます。今回利用するNNの機能は限定的であり、使用するニューロンの層数は2ないし3層と決定しています。FPGAに期待するのはTFが提供する機能が動くことではなく、行列積の演算を素早く終えること、ただそれだけです。逆にいうと、MATLABはその限られた環境に無理矢理押しこめるか見積もるための飛び道具です。
NN実装の素振り
CNNではなくNNから行います。非線形の分類=NNを試してみて、畳み込みNN(=CNN)のカーネルがどの程度必要とされているか見積もりたいからです。
スタンフォード大のニューラルネットワークの授業が公開されています。とても理解しやすく書かれています。実装例としてPythonを使っていますが、あまりPythonコードの解説にならないように努力が払われているように感じられます。NNはもとをただせば行列式の積和なので数式で説明された方が、証明もできますし、エンジニアとしては嬉しいです。ライセンスもアカデミックらしくMITです。
勉強がてらこのスタンフォード大の授業を和訳しています。(まだ途中)
github.com
Module.1の最終章「Putting it together: Minimal Neural Network Case Study」までやってみて、例題の線形分類と非線形分類をMATLABで実装しました。
線形分類
「Putting it together: Minimal Neural Network Case Study」の実践です。 線形分類スコアをS、分類付けをする重み行列をW、入力データをX(サンプル数i個、j番目まである行列)、バイアスをb、教師信号yと置くと、
と記述できます。どれだけ教師信号yとかけ離れていたかを示す、損失関数はソフトマックス関数を用いて、下記の様に表せます。ここから勾配を求め、重みを改良します。これを反復することによってどのくらい分類に成功しているかを求めます。詳細はこちらか英語の原文に当たってください。MATLABで記述しました。コード全文は以下。
github.com
MATLAB homeをお使いの方でも基本パッケージだけで動くはずです。試したバージョンはMATLAB/Simulink R2021aです。
色分けしたスパイラル散布図を線形分類しました。黒丸の青、緑、赤を分類します。
ニューラルネットワーク
隠れ層を導入し、2層ニューラルネットワークを構築し活性化関数にReLUを導入したものです。
MATLABコード全文は
github.com
自作RISC-Vでスイッチサイエンスの測距ToFセンサー VL53L1X を動かす
2000円もせずに400cm測れる超優秀なセンサーモジュールがあります。
4mまで測れるので単眼カメラの停止時の車間詰め補助に使おうと思っています。
ADAS搭載車両で、前方車両が発進したことを知らせる機能がある車両がありますが、大抵4m以上車両間隔が開くと通知されます。
チッ どいつもこいつもI2C使いやがって
というわけで自作RISC-VにI2Cモジュールを接続します。
といっても、私がI2Cモジュールを一生懸命1から作成するわけではなく、すでにintel Quartus Prime/Platform Designerに用意されているので、これを接続するだけです。
Avalon-MM Masterに対応するとこのようにモジュールの低レイヤを意識せず接続できるのでとても便利です。
CPU側から見ると、スレーブモジュールがSDRAMだろうとSPIメモリだろうと特定のアドレスに向けてLoad/Store命令を実行してデータのRWをしているだけです。
I2C HALの整備
NiosII用のHALを使用することはライセンス違反となる可能性があります。なので、取り急ぎHALを作りました。
Embedded Peripherals IP User Guideのp.170から、Figure 47~51,54を参考にすればさほど難しいモジュールではありません。
全体は以下を参照ください。
KyogenRV/qsys_i2c.c at master · panda5mt/KyogenRV · GitHub
VL53L1X API
STMicroからC API、PololuからArduino用のC++ APIが提供されています。Pololu提供のAPIが使いやすく思えましたので、C用に書き直しました。
全体は以下を参照ください。ライセンスはPololuをベースにしているため、3条項修正BSDライセンスに準拠しています。
KyogenRV/VL53L1X.c at master · panda5mt/KyogenRV · GitHub
動かしてみた
以下にmain.c
を示します。
#include <stdio.h> #include <stdint.h> #include "krv_utils.h" #include "xprintf.h" #define USE_VL53L1X (1) #define USE_SDRAM (1) #ifdef USE_VL53L1X #include "qsys_i2c.h" #include "VL53L1X.h" #endif //USE_VL53L1X #ifdef USE_SDRAM int32_t sdram_test(void) { uint32_t data,length; length = SDRAM_0_END - SDRAM_0_BASE; xprintf("SDRAM write start\r\n"); for (int k = 0 ; k < length ; k = k + 4) { put32(SDRAM_0_BASE + k, k); } xprintf("SDRAM read start\r\n"); for (int k = 0 ; k < length ; k = k + 4) { data = get32(SDRAM_0_BASE + k); if(data != k) { xprintf("error fount at 0x%x: expecting %d but got %d\r\n",k,k,data); return -1; } } return 0; } #endif // USE_SDRAM // main function int32_t main(int argc, char *argv[]) { uint64_t i; xdev_out(&uart_putc); // override xprintf #ifdef USE_VL53L1X xprintf("I2C init\r\n"); i2c_init(I2C_0_BASE); i2c_disable_isr(I2C_0_BASE); if(true == VL53L1X_init()) { xprintf("VL5351X init OK.\r\n"); VL53L1X_setDistanceMode(VL53L1X_Long); VL53L1X_setMeasurementTimingBudget(50000); VL53L1X_startContinuous(50); } else { xprintf("VL5351X init failed.\r\n"); } #endif //USE_VL53L1X uint32_t data = 0; #ifdef USE_SDRAM if(0 == sdram_test()) { xprintf("SDRAM r/w test OK!\r\n"); } else { xprintf("SDRAM r/w test fail......\r\n"); } #endif //USE_SDRAM xprintf("KyogenRV (RV32I) Start...\r\n"); while(1){ wait_ms(500); put32(PIO_0_BASE, 0x55); wait_ms(500); put32(PIO_0_BASE, 0xAA); #ifdef USE_VL53L1X data = VL53L1X_read(true); xprintf("value = %d\r\n",data); #endif //USE_VL53L1X i = get_time_ms() / 1000; xprintf("machine time = %llu second\r\n",i); } return 0; }
define文の
#define USE_VL53L1X (1) #define USE_SDRAM (1)
はそれぞれ、VL53L1Xを使う場合、及びSDR SDRAMを接続する場合の設定です。
使わない場合はそれぞれ0にしてください。
動作風景
高度運転支援向け単眼カメラの実装(2.深度推定と夜間走行対応)
前回、パーティクルフィルタを用いた車両の追跡を簡易的に実装しました。
こちらをさらに展開していきます。
対象車両
この装置の対象は、4輪の軽自動車、超小型車を想定しています。また車両への後付けにも対応したいと思っています。
つまり、先進運転支援システム(ADAS)にあまりお金がかけられない車両を想定しています。
高級グレードですと、LIDAR、デュアルカメラ、ミリ波レーダから何種類かを組み合わせセンサーフュージョンを構築するはずです。
単眼カメラのみを利用している車種もあります。HONDAのFITなどです。
単眼カメラ単体の場合はカメラ自体の要求仕様がかなり厳しいと思われます。
ダイナミックレンジが広くなくてはいけません。例えば、晴天時も夜間時も等しく深度推定を行う必要があります。白飛びすると測定は困難です。
また、夜間時に前方の車がブレーキをかけると制動灯が強めに光ります。この時、ダイナミックレンジがせまいと、距離が正確に測れない場合があります。
いずれにしても、車高、ボディ、フロントガラス形状などで形が決まります。
つまり車両設計時に全てが決まってしまうため、あまり柔軟性がないと言えます。とくにデュアルカメラブレーキサポートシステムの場合、車体の剛性も決まってしまいます。
想定環境
今回は単眼カメラと、停車時の車間詰め用の補助として4,5メートルくらいを測定できる安価なToFセンサを想定しています。単眼カメラはダイナミックレンジはそれほど良くなくても動くよう設計しました。
イメージセンサはビデオ会議用の2〜3千円で変えるwebカメラを想定しています。
制御側も、高性能なGPUを搭載したものではなく、ローエンドFPGAを想定しています。
実装
対象物の特徴点の変化により、深度を推定するようにしました。
雑な説明をすると、物体が近づく程この特徴点は増えます。遠ざかるほど減ります。
もちろん、対象物の大きさはまちまちなので、MATLABにより大きさを推定するロジックを作りました。
大きさによる正規化をし、深度推定を行います。
ダイナミックレンジが狭い安価なカメラで夜間の深度推定に対応する
問題は夜間です。特にバイパスなどを走行していると、遠方が明るく見えます。遠近法で街灯が集中しているように見えるからです。以下の画像のような感じになります。カメラからみると実際には遠方にあるのに近く見える事故が起きます。
深度推定がすこし雑になる部分がありますが、安全側に倒した深度推定になっていると思います。
ADASご相談【法人限定】
コメントにて、ADAS試作設計など窓口を開設して欲しいとの意見を複数頂戴しています。
取り急ぎとなり恐縮ですが、フォームを作成いたしました。法人のみですが、設計のご相談・イベント展示用から量産前設計までのコンサルティングを賜ります。お問合わせフォームからご連絡ください。
(お問合せフォームは大まかな位置情報・ブラウザ情報を取得しています。ご了承ください)
https://kyogens.wixsite.com/-site-1