eMoi-pic が 2024年06月13日10時07分55秒 に編集
コメント無し
本文の変更
# RGBやYUVの2次元配列で画像処理 EMOIC-PIC用にSpresense-Arduinoスケッチを開発する際のサンプルコードです。 - takeStillPicture_SD(); //静止画(640*480)を1枚撮影し、SDカードにJPEG形式で保存する。 - putStringOnLCD(String str, int line, int color); //ディスプレイの下半分に指定行(0-5)の位置に文字列を指定色で左右中央に1行表示する。
- displayImage(); //RGB565の3配列(160*120)をカラー画像としてディスプレイ右に表示する。
- displayImage(); //RGB毎のbuf_R, buf_G, buf_Gの2次元配列(160*120)をカラー画像としてディスプレイ右に表示する。
- グローバル変数picture_eval_score //連続撮影される入力画像の評価値を納める。 - グローバル変数Y[160][120] //Y(輝度)信号の配列。値は0-255の範囲 - グローバル変数U[160][120] //U(色)信号の配列。値は0-128-255の範囲(128が無彩色) - グローバル変数V[160][120] //V(色)信号の配列。値は0-128-255の範囲(128が無彩色) - グローバル変数R[160][120] //画像処理出力用配列R。値は0-255の範囲 - グローバル変数G[160][120] //画像処理出力用配列G。値は0-255の範囲 - グローバル変数B[160][120] //画像処理出力用配列B。値は0-255の範囲 # トラブル例(忘れがちな準備作業等) - [使用PC(Win/Mac)に応じて指定のUSBシリアルドライバをインストールする必要があります。](https://developer.sony.com/spresense/development-guides/arduino_set_up_ja.html#_usb_%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) - [Spresense Arduino board packageをArduino IDEにインストールする必要があります。](https://developer.sony.com/spresense/development-guides/arduino_set_up_ja.html#_spresense_arduino_board_package_%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) - [新品のSpresenseには、Spresenseブートローダーをインストールする必要があります。](https://developer.sony.com/spresense/development-guides/arduino_set_up_ja.html#_spresense_%E3%83%96%E3%83%BC%E3%83%88%E3%83%AD%E3%83%BC%E3%83%80%E3%83%BC%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB) - [液晶ディスプレイには、Sony社提供のAdafruit-GFX-LibraryとAdafruit_ILI9341の2つのライブラリをインストールする必要があります。)](https://developer.sony.com/spresense/development-guides/arduino_tutorials_ja.html#_adafruit_ili9341_%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95) Adafruit社提供の同名ライブラリをインストール済の場合は、一旦削除してからインストールしてください。 - [液晶モジュールを3.3Vで駆動する場合は、SpresenseのI/O電圧を3.3Vに切り換える必要があります。](https://developer.sony.com/spresense/development-guides/hw_docs_ja.html#_%E6%8B%A1%E5%BC%B5%E3%83%9C%E3%83%BC%E3%83%89%E3%81%AE%E3%83%94%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E5%8B%95%E4%BD%9C%E9%9B%BB%E5%9C%A7%E8%A8%AD%E5%AE%9A%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6) - [メインボードと拡張ボードを繋ぐB2Bコネクタはしっかり嵌合する必要があります。](https://developer.sony.com/spresense/development-guides/introduction_ja.html#_spresense_lte%E6%8B%A1%E5%BC%B5%E3%83%9C%E3%83%BC%E3%83%89) 嵌合が不完全だと各種エラーが発生します。メインボードと拡張ボードを指で強く挟み、パチッと大きな音がするまで強めに押し込んでください。メインボードの四隅にある樹脂サポート棒が穴に嵌まるときにも音が発生しますが、その音と混同することがよくあります。 # ユーザコード開発 ユーザが記述しないといけない場所が2箇所あります。 - 画像評価(CamCB()関数内):連続撮影される入力画像を評価し、評価値をpicture_eval_scoreに納める。 - メイン処理(loop()関数内): picture_eval_scoreに基づき静止画を撮影するかどうか、ロボットを駆動させるか等の判断を行い、静止画撮影する場合はtakeStillPicture_SD()をコールする。 ``` // Spresense EMOI-PIC sample code (2024/6/10) #include <stdio.h> #include <Camera.h> #include <Adafruit_ILI9341.h> #include <SDHCI.h> #define TFT_DC (9) #define TFT_CS (10) // ディスプレイの縦横の大きさ #define DISPLAY_WIDTH (320) #define DISPLAY_HEIGHT (240) // CAM_IMGSIZE_VGA_H(640), CAM_IMGSIZE_VGA_V(480)の1/4 #define IMAGE_X_SIZE (160) #define IMAGE_Y_SIZE (120) #define TOTAL_PICTURE_COUNT (9) SDClass theSD; Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC); uint8_t R[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-255] uint8_t G[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-255] uint8_t B[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-255] uint8_t Y[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-255] uint8_t U[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-128-255] uint8_t V[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-128-255] uint8_t buf_R[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-255] uint8_t buf_G[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-255] uint8_t buf_B[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-255] uint16_t drawImageBuf[CAM_IMGSIZE_QQVGA_H*CAM_IMGSIZE_QQVGA_V]; int take_picture_count = 0; int picture_eval_score = 0; // 320*240液晶ディスプレイの下半分に指定行(0-5)の高さに文字列を指定色で左右中央に1行表示する。 // ILI9341_BLACK (_RED, _GREEN, _BLUE, _WHITE etc.) // コードを変更しないでください。 void putStringOnLCD(String str, int line, int color) { int len = str.length(); display.fillRect(0, DISPLAY_HEIGHT/2+line*18+6, DISPLAY_WIDTH-1, DISPLAY_HEIGHT/2+(line+1)*18+5, ILI9341_BLACK); display.setTextSize(2); int sx = DISPLAY_WIDTH/2 - len/2*12; if (sx < 0) sx = 0; if (line < 0) sx = 0; else if (line > 5) sx = 5; display.setCursor(sx, DISPLAY_HEIGHT/2+line*18+8); display.setTextColor(color); display.println(str); } // 静止画(640*480)を1枚撮影し、SDカードにJPEG形式で保存する。 // コードを変更しないでください。 void takeStillPicture_SD() { putStringOnLCD("Taking Still Picture", 3, ILI9341_WHITE); CamImage still_img = theCamera.takePicture(); if (still_img.isAvailable()) { char filename[16] = {0}; sprintf(filename, "PICT%03d.JPG", take_picture_count); theSD.remove(filename); File myFile = theSD.open(filename, FILE_WRITE); myFile.write(still_img.getImgBuff(), still_img.getImgSize()); myFile.close(); putStringOnLCD(filename, 3, ILI9341_WHITE); putStringOnLCD("SD card saved", 4, ILI9341_WHITE); } else { putStringOnLCD("ERROR: Take Still Picture", 5, ILI9341_RED); } } // RGB565の3配列(buf_R, buf_G, buf_B, 160*120)をカラー画像としてディスプレイ右に表示する。 // コードを変更しないでください。 void displayImage() { for (int n = 0; n < IMAGE_X_SIZE*IMAGE_Y_SIZE; n++) { uint8_t r5 = buf_R[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H]; uint8_t g6 = buf_G[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H]; uint8_t b5 = buf_B[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H]; drawImageBuf[n] = (uint16_t)(((uint16_t)r5 & 0x00f8)<<8) | (((uint16_t)g6 & 0x00fc)<<3) | (((uint16_t)b5 & 0x00f8)>>3); } display.drawRGBBitmap(DISPLAY_WIDTH/2, 0, drawImageBuf, CAM_IMGSIZE_QQVGA_H, CAM_IMGSIZE_QQVGA_V); } // Spresenseカメラライブラリのコールバック関数。撮影フレーム毎に入力カラー画像をYUVの3配列(160*120)に格納する。 // ユーザはこの中に画像を評価するコードを記述し、得られた評価値をグローバル変数picture_eval_scoreに書き込む。 void CamCB(CamImage img) { if (!img.isAvailable()) return; display.drawRGBBitmap(0, 0, (uint16_t *)img.getImgBuff(), CAM_IMGSIZE_QQVGA_H, CAM_IMGSIZE_QQVGA_V); uint16_t* imgbuf_RGB565 = (uint16_t*)img.getImgBuff(); for (int n = 0; n < IMAGE_X_SIZE*IMAGE_Y_SIZE; n++) { R[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf_RGB565[n] & 0xf800) >> 8); G[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf_RGB565[n] & 0x07e0) >> 3); B[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf_RGB565[n] & 0x001f) << 3); } img.convertPixFormat(CAM_IMAGE_PIX_FMT_YUV422); // YUV422: ビッグエンディアンの2画素をまとめたYUYVのPacked形式、4byte="Y0(1byte)+V0(1byte)+Y1(1byte)+U0(1byte)" uint32_t* imgbuf_YUV422 = (uint32_t*)img.getImgBuff(); for (int n = 0; n < IMAGE_X_SIZE*IMAGE_Y_SIZE; n+=2) { Y[(int)(n%CAM_IMGSIZE_QQVGA_H+1)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf_YUV422[n/2] & 0xff000000) >> 24); Y[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf_YUV422[n/2] & 0x0000ff00) >> 8); U[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)(imgbuf_YUV422[n/2] & 0x000000ff); U[(int)(n%CAM_IMGSIZE_QQVGA_H+1)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)(imgbuf_YUV422[n/2] & 0x000000ff); V[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf_YUV422[n/2] & 0x00ff0000) >> 16); V[(int)(n%CAM_IMGSIZE_QQVGA_H+1)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf_YUV422[n/2] & 0x00ff0000) >> 16); } //////// USER CODE ///////// // R[X座標(0-159)][Y座標(0-119] 値域[0-255] // G[X座標(0-159)][Y座標(0-119] 値域[0-255] // B[X座標(0-159)][Y座標(0-119] 値域[0-255] // Y[X座標(0-159)][Y座標(0-119] 値域[0-255] // U[X座標(0-159)][Y座標(0-119] 値域[0-128-255] 無色は128 // V[X座標(0-159)][Y座標(0-119] 値域[0-128-255] 無色は128 // 画像左上が原点で、Xは右方向、Yは下方向 /////////////// ここから //////////////// // 例:RGB、YUVの平均値を算出し、ディスプレイに表示する。 int ave_R = 0, ave_G = 0, ave_B = 0; for(int y = 0; y < 120; y++) { for(int x = 0; x < 160; x++) { ave_R += R[x][y]; ave_G += G[x][y]; ave_B += B[x][y]; } } ave_R /= 160*120; ave_G /= 160*120; ave_B /= 160*120; int ave_Y = 0, ave_U = 0, ave_V = 0; for(int y = 0; y < 120; y++) { for(int x = 0; x < 160; x++) { ave_Y += Y[x][y]; ave_U += U[x][y]; ave_V += V[x][y]; } } ave_Y /= 160*120; ave_U /= 160*120; ave_V /= 160*120; // 例:Rの平均値-Bの平均値を画像評価値とし、グローバル変数のpicture_eval_scoreに保存する。 picture_eval_score = ave_R - ave_B; // 仮の代入 char message[32] = {0}; sprintf(message, "R:%3d G:%3d B:%3d", ave_R, ave_G, ave_B); putStringOnLCD(message, 0, ILI9341_WHITE); sprintf(message, "Y:%3d U:%3d V:%3d", ave_Y, ave_U, ave_V); putStringOnLCD(message, 1, ILI9341_WHITE); sprintf(message, "Eval:%5d", picture_eval_score); putStringOnLCD(message, 2, ILI9341_WHITE); // 例:画像処理した結果をディスプレイに表示するため、画像バッファに処理結果を保存。 for(int y = 0; y < 120; y++) { for(int x = 0; x < 160; x++) { if (Y[x][y] > 128) { // Yが128より明るい画素をグリーンにする。 buf_R[x][y] = 0; buf_G[x][y] = 255; buf_B[x][y] = 0; } else { // Yが128以下の画素をグレーにする。 buf_R[x][y] = 64; buf_G[x][y] = 64; buf_B[x][y] = 64; } } } displayImage(); //RGB565の3配列をカラー画像としてディスプレイ右に表示する /////////////// ここまで //////////////// } // Spresenseとっディスプレイを初期化します。Spresenseカメラのプレビュー画像のフレームレートを5fps、160*120画素、YUV422形式に設定します。 // Spresenseカメラの静止画を640*480画素のJPEGに設定します。 // コードを変更しないでください。 void setup() { CamErr err; display.begin(); display.setRotation(3); display.fillScreen(ILI9341_BLACK); while (!theSD.begin()) { putStringOnLCD("Insert SD card", 0, ILI9341_GREEN); } display.fillScreen(ILI9341_BLACK); err = theCamera.begin(1, CAM_VIDEO_FPS_5, CAM_IMGSIZE_QQVGA_H, CAM_IMGSIZE_QQVGA_V, CAM_IMAGE_PIX_FMT_RGB565); err = theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_AUTO); err = theCamera.setStillPictureImageFormat(CAM_IMGSIZE_VGA_H, CAM_IMGSIZE_VGA_V, CAM_IMAGE_PIX_FMT_JPG); err = theCamera.startStreaming(true, CamCB); } // ユーザはこの中に画像の評価値に基づいて静止画を撮影するか否かを判断するコードを記述します。 // takeStillPicture_SD()をコールすると、撮影画像1枚をSDカードに保存します。保存処理に1秒程度要することに留意してください。 void loop() { /////////////// ここから //////////////// delay(100); // ロボットの移動&停止動作 // 撮影枚数が規定数のTOTAL_PICTURE_COUNTに達したら終了 if (take_picture_count == TOTAL_PICTURE_COUNT) { putStringOnLCD("Picture Count END", 5, ILI9341_GREEN); theCamera.end(); exit(0); } // グローバル変数のpicture_eval_scoreの評価値に従い静止画撮影する。例として、赤カードをカメラ撮影することで静止画撮影する。 if(picture_eval_score > 50) { takeStillPicture_SD(); take_picture_count++; } char message[32] = {0}; sprintf(message, "Picture Count: %d", take_picture_count); putStringOnLCD(message, 4, ILI9341_GREEN); // 何かの基準に従い愁傷する。ここではグローバル変数のpicture_eval_scoreの評価値に基づき、青カードをカメラ撮影することでプログラムを終了させている。 if(picture_eval_score < -50) { putStringOnLCD("Stop command", 5, ILI9341_YELLOW); theCamera.end(); exit(0); } /////////////// ここまで //////////////// } ```