HakoHiro が 2022年09月26日11時17分31秒 に編集
初版
タイトルの変更
画像認識による自律走行Vehicle
タグの変更
spresense
Arduino_IDE
NNC
メイン画像の変更
記事種類の変更
製作品
ライセンスの変更
(MIT) The MIT License
本文の変更
# 目的 Spresense のカメラ画像により、画像を認識し、Vehicleを自律走行させるのが目標です。 現状、充分な仕上がりとは言えませんが、自律走行の技術要素の確認はできました。(達成したとは言えませんが・・・・・) # 部品 - Spresense Main Board - Spresense 拡張Board - Spresense カメラBoard - ツイストクローラー (タミヤ) ![キャプションを入力できます](https://camo.elchika.com/06978c8c99b4a880f6f903c96201cc063eb86111/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38333537363361302d393332382d343665392d383165622d3234323436633435373839342f35376234616364622d623838372d346664372d393563642d616434326133653131653339/) - DCモータードライバ DRV8835(秋月電子通商) ![キャプションを入力できます](https://camo.elchika.com/64ccab36155944104a4a98c2928507344f3b8708/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38333537363361302d393332382d343665392d383165622d3234323436633435373839342f30663237386436652d373331392d343134612d386536662d613261363932306530326565/) - USB用バッテリー (Spresense 駆動用) - 単3電池*2 (DCモータ駆動用) # ブロック図 ![キャプションを入力できます](https://camo.elchika.com/7aaa0c84eb8278e9de0d26a7c329eb3829c9cad2/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38333537363361302d393332382d343665392d383165622d3234323436633435373839342f31346162653765652d643531302d343434632d393038322d356665383135346165336437/) # 組み立て状態 ![上面](https://camo.elchika.com/040d45e063c908f4de23710c6d63d8b6fdf69043/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38333537363361302d393332382d343665392d383165622d3234323436633435373839342f61653337313562342d623565302d343837302d613266612d353131373064626265646332/) ![正面](https://camo.elchika.com/f06cf2d245d4a5fa29c72c96867831c692f1a018/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38333537363361302d393332382d343665392d383165622d3234323436633435373839342f31333833633566652d616439362d346262302d396461652d306232383639386233353162/) ![背面](https://camo.elchika.com/72acc22050c71c48a2b2099f779a8bd5d6d8f0ef/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38333537363361302d393332382d343665392d383165622d3234323436633435373839342f38376163373266312d643231342d343231352d626133362d666461373730643763623538/) # Soft ## NNC(Neural Network Console) Youtubeのチュートリアルにて、学習。 Macは、PC版が無いので、クラウド版にて行う。 殆どチュートリアルのコピペ。 【number_recognition.sdcproj】参照 ## ソースコード NNCを適用するためには、Aruduino IDEが必要。VSCodeでもできるのだろうが、圧倒的に情報が足りないため、Aruduino IDEをインストール。 画像処理の部分は、チュートリアルSpresense | 14. Neural Network Consle を使って組込みAIを体験する(画像編)【ソニー公式】から転用。 【Spresense_number_recognition.ino】をベースに、0〜9の数字を認識して、『4』だけをターゲットとしてサーチします。 ```arduino:Vehicle自律走行 #include <Camera.h> #include <DNNRT.h> #include <SDHCI.h> SDClass theSD; #define DNN_IMG_W 28 #define DNN_IMG_H 28 #define CAM_IMG_W 320 #define CAM_IMG_H 240 #define CAM_CLIP_X 104 #define CAM_CLIP_Y 0 #define CAM_CLIP_W 112 #define CAM_CLIP_H 224 uint8_t buf[DNN_IMG_W*DNN_IMG_H]; DNNRT dnnrt; DNNVariable input(DNN_IMG_W*DNN_IMG_H); static uint8_t const label[11] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // io pin define //全大文字を使うのが嫌なので・・・・ int R_Forward = 5; // RightMotor Forward // PWM0 int R_Backward = 6; // RightMotor back // PWM1 int L_Forward = 3; // Left Forward // PWM2 int L_Backward = 9; // Left back // PWM3 int speed = 160; // IO remote操作のときに使う int cameraShutter = 2; // cameraShutter int speedDown = 0; // Speed down int speedUp = 1; // Speed Up int moveCCW = 4; int moveCW = 7; int moveForward = 10; int moveBackward = 8; int moveStop = 11; int findCountSum = 0; unsigned long previousMillis = 0; bool firstDirectionCW = true; int cwCCWdelay = 100; void CamCB(CamImage img) { if (!img.isAvailable()) { Serial.println("Image is not available. Try again"); return; } CamImage small; CamErr err = img.clipAndResizeImageByHW(small , CAM_CLIP_X, CAM_CLIP_Y , CAM_CLIP_X + CAM_CLIP_W -1 , CAM_CLIP_Y + CAM_CLIP_H -1 , DNN_IMG_W, DNN_IMG_H); if (!small.isAvailable()){ Serial.println("Clip and Reize Error:" + String(err)); return; } small.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); uint16_t* tmp = (uint16_t*)small.getImgBuff(); float *dnnbuf = input.data(); float f_max = 0.0; for (int n = 0; n < DNN_IMG_H*DNN_IMG_W; ++n) { dnnbuf[n] = (float)((tmp[n] & 0x07E0) >> 5); if (dnnbuf[n] > f_max) f_max = dnnbuf[n]; } /* normalization */ for (int n = 0; n < DNN_IMG_W*DNN_IMG_H; ++n) { dnnbuf[n] /= f_max; } String gStrResult = "?"; dnnrt.inputVariable(input, 0); dnnrt.forward(); DNNVariable output = dnnrt.outputVariable(0); int index = output.maxIndex(); if (index < 10) { gStrResult = String(label[index]) + String(":") + String(output[index]); switch (index) { case 3: // // Serial.println("************* 3"); break; case 4: // findCountSum ++; Serial.print("Find "); Serial.print(gStrResult); Serial.print(" "); Serial.println(findCountSum); break; case 5: // // Serial.println("************* 5"); break; default: break; } } else { gStrResult = String("?:") + String(output[index]); } // Serial.println(gStrResult); } void setup() { Serial.begin(115200); pinMode(R_Forward, OUTPUT); pinMode(R_Backward, OUTPUT); pinMode(L_Forward, OUTPUT); pinMode(L_Backward, OUTPUT); analogWrite(R_Forward, 0); analogWrite(R_Backward, 0); analogWrite(L_Forward, 0); analogWrite(L_Backward, 0); // 外部リモートで操作する場合 pinMode(cameraShutter, INPUT_PULLUP); pinMode(speedDown, INPUT_PULLUP); pinMode(speedUp, INPUT_PULLUP); pinMode(moveCCW, INPUT_PULLUP); pinMode(moveCW, INPUT_PULLUP); pinMode(moveBackward, INPUT_PULLUP); pinMode(moveForward, INPUT_PULLUP); pinMode(moveStop, INPUT_PULLUP); while (!theSD.begin()) { } // Neural Network Console クラウド版にて学習した結果 File nnbfile = theSD.open("result_v1.nnb"); int ret = dnnrt.begin(nnbfile); if (ret < 0) { Serial.println("dnnrt.begin failed" + String(ret)); return; } theCamera.begin(); theCamera.startStreaming(true, CamCB); } void loop() { unsigned long currentMillis = millis(); if (cwCCWdelay > 200) { cwCCWdelay = 100; } if (currentMillis - previousMillis >= 1000) { findCountSum = 0; previousMillis = currentMillis; // remoteIO_Check(); } if (findCountSum > 3) { motorForward(); //方向が正しいので進め cwCCWdelay = 100; } else { motorStop(); if (firstDirectionCW) { motorCCW(); firstDirectionCW = false; } else { motorCW(); firstDirectionCW = true; } cwCCWdelay += 5; delay(cwCCWdelay); motorStop(); } } // Motor Control Analog(PWM) void motorStop() { Serial.println("++++Stop"); analogWrite(R_Forward, 0); analogWrite(R_Backward, 0); analogWrite(L_Forward, 0); analogWrite(L_Backward, 0); } void motorForward() { Serial.println("++++Forward"); analogWrite(R_Forward, speed); analogWrite(L_Forward, speed); analogWrite(R_Backward, 0); analogWrite(L_Backward, 0); } void motorBackward() { Serial.println("++++Backward"); analogWrite(R_Backward, speed); analogWrite(L_Backward, speed); analogWrite(R_Forward, 0); analogWrite(L_Forward, 0); } void motorCW() { Serial.println("++++CW"); analogWrite(R_Forward, 0); analogWrite(R_Backward, 0); analogWrite(L_Forward, speed+10); analogWrite(L_Backward, 0); } void motorCCW() { Serial.println("++++CCW"); analogWrite(R_Forward, speed); analogWrite(R_Backward, 0); analogWrite(L_Forward, 0); analogWrite(L_Backward, 0); } void motorSpeedUp() { speed += 10; } void motorSpeedDown() { speed -= 10; } void remoteIO_Check() { int val; val = digitalRead(cameraShutter); if (val == 0) { Serial.println("cameraShutter On"); } val = digitalRead(speedDown); if (val == 0) { speed -= 10; Serial.print("speedDown Speed = "); Serial.println(speed); } val = digitalRead(speedUp); if (val == 0) { speed += 10; Serial.print("speedUp Speed = "); Serial.println(speed); } val = digitalRead(moveCCW); if (val == 0) { Serial.println("moveCCW On"); analogWrite(R_Forward, speed); analogWrite(L_Backward, speed); } val = digitalRead(moveCW); if (val == 0) { Serial.println("moveCW On"); analogWrite(R_Backward, speed); analogWrite(L_Forward, speed); } val = digitalRead(moveBackward); if (val == 0) { Serial.println("moveBackward On"); analogWrite(R_Backward, speed); analogWrite(L_Backward, speed); } val = digitalRead(moveForward); if (val == 0) { Serial.println("moveForward On"); analogWrite(R_Forward, speed); analogWrite(L_Forward, speed); } val = digitalRead(moveStop); if (val == 0) { analogWrite(R_Forward, 0); analogWrite(R_Backward, 0); analogWrite(L_Forward, 0); analogWrite(L_Backward, 0); } } ``` # 動作状況 調整中の段階ですが、現状の動作状況の動画を御覧ください。 @[youtube](https://www.youtube.com/shorts/m2pwo4eEuPY) # 反省点、改善点 - 当初の構想が甘すぎ、紆余曲折してしまいました。 - NNCの理解が不十分で、チュートリアルから外れると手が出ない。 - そもそも画像認識はかなり高度な技術な技術なのに、NNCの見た目の簡便さで勉強不足だった。 - 本来は、物体追跡で画像認識を行いたかったのだが、そのレベルには程遠い状況です。大学入試に中学生レベルで挑戦したようなものです。 - 現状では、光検知センサー数個でライントレースする方がはるかにスムーズかつ、容易に実現できます。 - それでも、これだけの画像処理ができるSpresense の能力は魅力的ですね。 - VSCode+PlatformIOの環境になれすぎて、Arduino IDE がなかなか思う様に使いこなせませんでした。 # Spresenseの感想 - このサイズで、マルチコア、GPS、オーディオ、カメラ等の魅力たっぷり、かつ、画像処理を楽々こなせる能力が素晴らしい。 - ただし、外部との通信機能が弱い点が気になる。 (利用方法次第とも言えるが・・・) - NNCをワンボードで活用できるのには驚いた。ただあまりにも分野が広いので学習コストが高そう。 # 疑問点 - マルチコアをシングルで使っているためか、タイマー割り込みが異様に遅く使えなかった。(画像処理、Serial.printを排除しても・・・) 同じ処理をタイマー割り込み内で使うのと、void loop内で使うのと速度が大幅に異なる。 誰か教えて下さい。 本ソフトでは、タイマー割り込みを使わず、millis()を使っています。 ```arduino:タイマー割り込み確認テスト int cnt=1; void timerWup() { // Serial.print("Wup "); // Serial.println(cnt); //cnt ++; if (cnt == 1 ) { digitalWrite(11, HIGH); cnt = 0; } else { digitalWrite(11, LOW); cnt = 1; } // Serial.println("Wup"); } void setup() { Serial.begin(115200); pinMode(11, OUTPUT); attachTimerInterrupt(timerWup, 200000); } void loop() { // if (cnt == 1 ) { // digitalWrite(11, HIGH); // cnt = 0; // } else { // digitalWrite(11, LOW); // cnt = 1; // } // delay(200); } ``` - eMMCライブラリは、SDKでは動くが、Arduino IDE ではうまく動きませんでした。あまり深く追求していませんが・・・・