eMoi-pic が 2024年06月09日18時48分14秒 に編集
コメント無し
本文の変更
# RGBやYUVの2次元配列で画像処理 EMOIC-PIC用にSpresense-Arduinoスケッチを開発する際のサンプルコードです。
- camInit(); //カメラを初期化する。 - camCap128(Y[128][128], U[128][128], V[128][128], CamImage); //128*128YUV422画像を2次元配列に格納する。 - camCapRGB128(R[128][128], G[128][128], B[128][128], CamImage); //128*128RGB565画像を2次元配列に格納する。 - camJPG(filename); //画像を1枚撮影し、1024*1024のJPG画像でSDカードに格納する。 - camTake128(filename, Y_2次元配列, U_2次元配列, V_2次元配列); //画像を1枚撮影し、フルサイズでJPG画像でSDカードに格納する。128*128画像に縮小し、2次元配列に格納する。 関数はヘッダファイルに記述しており、書籍「SPRESENSEではじめるローパワーエッジAI」サポートページのサンプルコードをベースに作成しています。 https://github.com/TE-YoshinoriOota/Spresense-LowPower-EdgeAI/tree/main/Chap07/sketches ### displayUtil.ino 液晶表示サンプル
- takeStillPicture_SD(); //静止画(640*480)を1枚撮影し、SDカードにJPEG形式で保存する。 - putStringOnLCD(String str, int line, int color); //ディスプレイの下半分に指定行(0-5)の位置に文字列を指定色で左右中央に1行表示する。 - displayImage(); //RGB565の3配列(160*120)をカラー画像としてディスプレイ右に表示する。 - グローバル変数picture_eval_score //連続撮影される入力画像の評価値を納める。 - グローバル変数Y[160][120] //Y(輝度)信号の配列。値は0-255の範囲 - グローバル変数U[160][120] //U(色)信号の配列。値は0-15の範囲 - グローバル変数V[160][120] //V(色)信号の配列。値は0-15の範囲 - グローバル変数R[160][120] //画像処理出力用配列R。値は0-31の範囲 - グローバル変数G[160][120] //画像処理出力用配列G。値は0-63の範囲 - グローバル変数B[160][120] //画像処理出力用配列B。値は0-31の範囲 ### ユーザコード開発 - 画像評価:CamCB()関数内に連続撮影される入力画像を評価し、評価値をpicture_eval_scoreに納める。 - メイン処理: loop()関数内でpicture_eval_scoreに基づき静止画を撮影するかどうか、ロボットを駆動させるか等の判断を行い、静止画撮影する場合はtakeStillPicture_SD()をコールする。
```
/* * displayUtil.ino - Graphic procedure for image_collection.ino * Copyright 2022 Sony Semiconductor Solutions Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#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)
// 記録した学習データ用画像を表示する位置 #define GRAY_OFFSET_X (290) #define GRAY_OFFSET_Y (100)
// 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 (5)
// 液晶ディスプレイの下部に文字列を表示する void putStringOnLcd(String str, int color) {
SDClass theSD; Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC); 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-15] uint8_t V[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-15] uint8_t R[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-31] uint8_t G[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-63] uint8_t B[CAM_IMGSIZE_QQVGA_H][CAM_IMGSIZE_QQVGA_V]; // 160*120, [0-31] 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,224, 320, 240, ILI9341_BLACK);
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 = 160 - len/2*12;
int sx = DISPLAY_WIDTH/2 - len/2*12;
if (sx < 0) sx = 0;
display.setCursor(sx, 225);
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); }
// 液晶ディスプレイにLINE_THICKNESSの太さの四角形を描画する void drawBox(uint16_t* imgBuf, int offset_x, int offset_y, int width, int height, int thickness, int color) { /* Draw target line */ for (int x = offset_x; x < offset_x+width; ++x) { for (int n = 0; n < thickness; ++n) { *(imgBuf + DISPLAY_WIDTH*(offset_y+n) + x) = color; *(imgBuf + DISPLAY_WIDTH*(offset_y+height-1-n) + x) = color; }
// 静止画(640*480)を1枚撮影し、SDカードにJPEG形式で保存する。 // コードを変更しないでください。 void takeStillPicture_SD() { putStringOnLCD("Taking Still Picture", 2, 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("SD card saved", 2, ILI9341_WHITE); } else { putStringOnLCD("ERROR: Take Still Picture", 2, ILI9341_RED);
}
for (int y = offset_y; y < offset_y+height; ++y) { for (int n = 0; n < thickness; ++n) { *(imgBuf + DISPLAY_WIDTH*y + offset_x+n) = color; *(imgBuf + DISPLAY_WIDTH*y + offset_x + width-1-n) = color; } }
}
// 生成した学習データ用画像を表示する void drawGrayImg(uint16_t* imgBuf, uint8_t* grayImg) { int j = 0; for (int y = GRAY_OFFSET_Y; y < GRAY_OFFSET_Y + DNN_HEIGHT; ++y, ++j) { int i = 0; for (int x = GRAY_OFFSET_X; x < GRAY_OFFSET_X + DNN_WIDTH; ++x, ++i) { uint16_t gray8 = grayImg[j*DNN_WIDTH + i]; uint16_t gray16 = ((gray8 & 0xf8) << 8) | ((gray8 & 0xfc) << 3) | ((gray8 & 0xf8) >> 3); *(imgBuf + DISPLAY_WIDTH*y + x) = gray16; }
// RGB565の3配列(160*120)をカラー画像としてディスプレイ右に表示する。 // コードを変更しないでください。 void displayImage() { for (int n = 0; n < IMAGE_X_SIZE*IMAGE_Y_SIZE; n++) { uint8_t r5 = R[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H]; uint8_t g6 = G[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H]; uint8_t b5 = B[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H]; drawImageBuf[n] = (uint16_t)(((uint16_t)r5 & 0x001F)<<11) | (((uint16_t)g6 & 0x003F)<<5) | ((uint16_t)b5 & 0x001F);
}
drawBox(imgBuf, GRAY_OFFSET_X, GRAY_OFFSET_Y, DNN_WIDTH, DNN_HEIGHT, 3, ILI9341_GREEN);
display.drawRGBBitmap(DISPLAY_WIDTH/2, 0, drawImageBuf, CAM_IMGSIZE_QQVGA_H, CAM_IMGSIZE_QQVGA_V);
}
``` ### ImageProcess_for_PBL.ino 画像処理開発用 ``` #include <Camera.h> #include <Adafruit_ILI9341.h> #include <SDHCI.h> #include <BmpImage.h> #include "PBL_library.h" #define TFT_DC 9 #define TFT_CS 10 Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC);
#define DNN_HEIGHT (28) #define DNN_WIDTH (28)
// 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); img.convertPixFormat(CAM_IMAGE_PIX_FMT_YUV422); uint16_t* imgbuf = (uint16_t*)img.getImgBuff(); for (int n = 0; n < IMAGE_X_SIZE*IMAGE_Y_SIZE; n++) { Y[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)(((imgbuf[n] & 0xf000) >> 8) | ((imgbuf[n] & 0x00f0) >> 4)); U[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)((imgbuf[n] & 0x0f00) >> 8); V[(int)(n%CAM_IMGSIZE_QQVGA_H)][n/CAM_IMGSIZE_QQVGA_H] = (uint8_t)(imgbuf[n] & 0x000f); }
uint8_t Y[RESIZED_LENGTH][RESIZED_LENGTH]; uint8_t U[RESIZED_LENGTH][RESIZED_LENGTH]; uint8_t V[RESIZED_LENGTH][RESIZED_LENGTH];
//////// USER CODE ///////// // Y[X座標(0-159)][Y座標(0-119] 値域[0-255] // U[X座標(0-159)][Y座標(0-119] 値域[0-15] 無色は8 // V[X座標(0-159)][Y座標(0-119] 値域[0-15] 無色は8 // 画像左上が原点で、Xは右方向、Yは下方向
BmpImage bmp; char fname[16]; int take_picture_count = 0; int test;
/////////////// ここから ////////////////
void CamCB(CamImage img) {
// 例:YUVの平均値を算出し、ディスプレイに表示する。 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; picture_eval_score = ave_Y; // char message[32] = {0}; sprintf(message, "%d: %d %d %d", take_picture_count, ave_Y, ave_U, ave_V); putStringOnLCD(message, 5, ILI9341_WHITE);
putStringOnLcd("save jpeg", ILI9341_GREEN); sprintf(fname, "PICT%03d.JPG", take_picture_count); if(take_picture_count++<=10){ test = camTake128(fname, Y,U,V,img); for (int n = 0; n < RESIZED_LENGTH*RESIZED_LENGTH; ++n) { if(n%RESIZED_LENGTH==0 && (n/RESIZED_LENGTH)%32 == 0){ Serial.print(Y[(n/RESIZED_LENGTH)][n%RESIZED_LENGTH]); Serial.print(','); Serial.print(U[(n/RESIZED_LENGTH)][n%RESIZED_LENGTH]); Serial.print(','); Serial.println(V[(n/RESIZED_LENGTH)][n%RESIZED_LENGTH]);
// 例:画像処理した結果を、ディスプレイに表示する。 for(int y = 0; y < 120; y++) { for(int x = 0; x < 160; x++) { if (Y[x][y] > 128) { // Yが128より明るい画素をグリーンにする。 R[x][y] = 0; G[x][y] = 63; B[x][y] = 0;
}
else { // Yが128以下の画素をグレーにする。 R[x][y] = 10; G[x][y] = 10; B[x][y] = 10; }
}
Serial.println();
}
test = camCapRGB128(Y,U,V,img); // RGB565の表示 for (int n = 0; n < RESIZED_LENGTH*RESIZED_LENGTH; ++n) { if(n%RESIZED_LENGTH==0 && (n/RESIZED_LENGTH)%32 == 0){ Serial.print(Y[(n/RESIZED_LENGTH)][n%RESIZED_LENGTH]); Serial.print(','); Serial.print(U[(n/RESIZED_LENGTH)][n%RESIZED_LENGTH]); Serial.print(','); Serial.println(V[(n/RESIZED_LENGTH)][n%RESIZED_LENGTH]); } } Serial.println(); img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); display.drawRGBBitmap(0, 0, (uint16_t *)img.getImgBuff(), LENGTH, LENGTH);
displayImage(); //RGB565の3配列をカラー画像としてディスプレイ右に表示する /////////////// ここまで ////////////////
}
void setup() { Serial.begin(115200);
// Spresenseとっディスプレイを初期化します。Spresenseカメラのプレビュー画像のフレームレートを5fps、160*120画素、YUV422形式に設定します。 // Spresenseカメラの静止画を640*480画素のJPEGに設定します。 // コードを変更しないでください。 void setup() { CamErr err;
display.begin(); display.setRotation(3);
display.fillScreen(ILI9341_BLACK);
if(!camInit()){ putStringOnLcd("Failed to initialize camera", ILI9341_RED);
while (!theSD.begin()) { putStringOnLCD("Insert SD card", 2, ILI9341_GREEN);
}
if (theCamera.startStreaming(true, CamCB) != CAM_ERR_SUCCESS){ putStringOnLcd("Failed to stream camera", ILI9341_RED); }
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);
}
void loop() { } ``` ### PBL_library.h 関連ライブラリ ``` #include <Camera.h> #include <SDHCI.h> #include <stdio.h> #include <Adafruit_ILI9341.h> #define LENGTH (128) #define RESIZED_LENGTH (128) SDClass SD;
// ユーザはこの中に画像の評価値に基づいて静止画を撮影するか否かを判断するコードを記述します。 // takeStillPicture_SD()をコールすると、撮影画像1枚をSDカードに保存します。保存処理に1秒程度要することに留意してください。 void loop() { /////////////// ここから //////////////// sleep(1); // Robot move & stay
bool camInit(){ while (!SD.begin()) { Serial.println("Insert SD card");
// Camera take if (take_picture_count == TOTAL_PICTURE_COUNT) { putStringOnLCD("TOTAL_PICTURE_COUNT END", 2, ILI9341_GREEN); theCamera.end(); exit(0);
}
CamErr err; err = theCamera.begin(1,CAM_VIDEO_FPS_30,LENGTH,LENGTH,CAM_IMAGE_PIX_FMT_YUV422,7); if (err != CAM_ERR_SUCCESS) { return false; } Serial.println("Set Auto white balance parameter"); err = theCamera.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_DAYLIGHT); if (err != CAM_ERR_SUCCESS) { return false; } Serial.println("Set still picture format"); err = theCamera.setStillPictureImageFormat(1024,1024,CAM_IMAGE_PIX_FMT_JPG); if (err != CAM_ERR_SUCCESS) { return false; } return true; }
// Image analysis
// YUV422を出力 bool camCap128(uint8_t Y[RESIZED_LENGTH][RESIZED_LENGTH], uint8_t U[RESIZED_LENGTH][RESIZED_LENGTH], uint8_t V[RESIZED_LENGTH][RESIZED_LENGTH], CamImage img){ CamErr err = img.convertPixFormat(CAM_IMAGE_PIX_FMT_YUV422); uint16_t* imgbuf = (uint16_t*)img.getImgBuff(); for (int n = 0; n < RESIZED_LENGTH*RESIZED_LENGTH; ++n) { Y[(int)(n/RESIZED_LENGTH)][n%RESIZED_LENGTH] = (uint8_t)(((imgbuf[n] & 0xf000) >> 8) | ((imgbuf[n] & 0x00f0) >> 4)); U[(int)(n/RESIZED_LENGTH)][n%RESIZED_LENGTH] = (uint8_t)((imgbuf[n] & 0x0f00) >> 8); V[(int)(n/RESIZED_LENGTH)][n%RESIZED_LENGTH] = (uint8_t)(imgbuf[n] & 0x000f);
// Decision make if(picture_eval_score > 100) { takeStillPicture_SD(); take_picture_count++;
}
return true;
/////////////// ここまで ////////////////
}
// RGB565を出力 bool camCapRGB128(uint8_t R[RESIZED_LENGTH][RESIZED_LENGTH], uint8_t G[RESIZED_LENGTH][RESIZED_LENGTH], uint8_t B[RESIZED_LENGTH][RESIZED_LENGTH], CamImage img){ CamErr err = img.convertPixFormat(CAM_IMAGE_PIX_FMT_RGB565); uint16_t* imgbuf = (uint16_t*)img.getImgBuff(); for (int n = 0; n < RESIZED_LENGTH*RESIZED_LENGTH; ++n) { R[(int)(n/RESIZED_LENGTH)][n%RESIZED_LENGTH] = (uint8_t)((imgbuf[n] & 0xf800) >> 11); G[(int)(n/RESIZED_LENGTH)][n%RESIZED_LENGTH] = (uint8_t)((imgbuf[n] & 0x07e0) >> 5 ); B[(int)(n/RESIZED_LENGTH)][n%RESIZED_LENGTH] = (uint8_t)(imgbuf[n] & 0x001f); } return true; } bool camJPG(char filename[16]){ CamImage img = theCamera.takePicture(); SD.remove(filename); File myFile = SD.open(filename, FILE_WRITE); myFile.write(img.getImgBuff(), img.getImgSize()); myFile.close(); return true; } bool camTake128(char filename[16], uint8_t Y[128][128], uint8_t U[128][128], uint8_t V[128][128],CamImage img){ if(!camCap128(Y,U,V,img)){ return false; }; if(!camJPG(filename)){ return false; }; return true; }
```