eMoi-pic が 2024年06月15日21時30分38秒 に編集
初版
タイトルの変更
EMOI-PIC カラー画像簡易ベクトルスコープ表示
タグの変更
SPRESENSE
メイン画像の変更
記事種類の変更
セットアップや使用方法
本文の変更
# 簡易ベクトルスコープ表示 Speresenseカメラで撮影したカラー画像の特徴を把握するために、簡易ベクトルスコープを作成してみました。YCbCr(YUV)のYはRGB各値の単純平均で簡略化しています。ヒストグラムのスケールも固定しています。RGB→YUVへの色変換は、Spresenseの内部処理機能(convertPixFormat関数)を使っても実装可能ですが、このサンプルコードでは直に書いています。 ``` #include <stdio.h> #include <Camera.h> #include <Adafruit_ILI9341.h> #define TFT_DC (9) #define TFT_CS (10) Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC); uint8_t R[160][120]; // [0-255] uint8_t G[160][120]; // [0-255] uint8_t B[160][120]; // [0-255] uint8_t Y[160][120]; // [0-255] int8_t U[160][120]; // [-128-0-127] int8_t V[160][120]; // [-128-0-127] uint8_t buf_R[160][120]; // [0-255] uint8_t buf_G[160][120]; // [0-255] uint8_t buf_B[160][120]; // [0-255] uint16_t* image_buffer; uint16_t drawImageBuf[160*120]; void displayImage() { for (int n = 0; n < 160*120; n++) { uint8_t r5 = buf_R[n%160][n/160]; uint8_t g6 = buf_G[n%160][n/160]; uint8_t b5 = buf_B[n%160][n/160]; drawImageBuf[n] = (uint16_t)(((r5 & 0x00f8)<<8) | ((g6 & 0x00fc)<<3) | (b5 & 0x00f8)>>3); } display.drawRGBBitmap(160, 0, drawImageBuf, 160, 120); } void CamCB(CamImage img) { if (img.isAvailable()) { image_buffer = (uint16_t *)img.getImgBuff(); } } void setup() { CamErr err; display.begin(); display.setRotation(3); 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.startStreaming(true, CamCB); } void loop() { int x, y, n, tY, h; delay(200); // 入力画像を描画 display.drawRGBBitmap(0, 0, image_buffer, CAM_IMGSIZE_QQVGA_H, CAM_IMGSIZE_QQVGA_V); // 入力画像をRGB各チャンネル毎に8btにスケーリングして格納 uint16_t* imgbuf_RGB565 = (uint16_t*)image_buffer; for (n = 0; n < 160*120; n++) { x = n%160; y = n/160; R[x][y] = (uint8_t)((imgbuf_RGB565[n] & 0xf800) >> 8); // [0-255] G[x][y] = (uint8_t)((imgbuf_RGB565[n] & 0x07e0) >> 3); // [0-255] B[x][y] = (uint8_t)((imgbuf_RGB565[n] & 0x001f) << 3); // [0-255] } // RGBからYCbCr(YUV)に変換し、各チャンネル毎に8btにスケーリングして格納。YはRGB単純平均で近似 for (n = 0; n < 160*120; n++) { x = n%160; y = n/160; tY = (R[x][y]+G[x][y]+B[x][y]) / 3; Y[x][y] = tY; // [0-255] U[x][y] = G[x][y] - tY; // [-128-0-127] V[x][y] = B[x][y] - tY; // [-128-0-127] } // ベクトルスコープ表示 CbCrカラー平面を描画 // 枠線描画 display.fillRect(160, 120, 160, 120, ILI9341_BLACK); display.drawRect(180, 120, 120, 120, ILI9341_WHITE); display.drawLine(180, 180, 299, 180, ILI9341_DARKGREY); display.drawLine(240, 120, 240, 239, ILI9341_DARKGREY); // CbCr(UV)値に基づき、ピクセル色でドットを描画 for(y = 0; y < 120; y++) { for(x = 0; x < 160; x++) { display.drawPixel(240+U[x][y]/2, 180-V[x][y]/2, display.color565(R[x][y], G[x][y], B[x][y])); } } // ヒストグラム表示 int hR[256], hG[256], hB[256]; // ヒストグラム配列表 // ヒストグラム表の初期化 for(n = 0; n < 256; n++) { hR[n] = 0; hG[n] = 0; hB[n] = 0; } // 頻度のカウント for(y = 0; y < 120; y++) { for(x = 0; x < 160; x++) { hR[R[x][y]]++; hG[G[x][y]]++; hB[B[x][y]]++; } } // ヒストグラム軸描画 display.fillRect(0, 120, 159, 239, ILI9341_BLACK); display.drawLine(20, 119, 20, 159, ILI9341_RED); display.drawLine(20, 159, 148, 159, ILI9341_RED); display.drawLine(20, 160, 20, 199, ILI9341_GREEN); display.drawLine(20, 199, 148, 199, ILI9341_GREEN); display.drawLine(20, 200, 20, 239, ILI9341_BLUE); display.drawLine(20, 239, 148, 239, ILI9341_BLUE); #define BarGain 30 // 頻度バー表示(R, G); RとBの分解能は5bitの32段階なので、4ピクセルおきに4ピクセル幅のおバー表示する。 for(x = 0; x < 256; x+=8) { h = hR[x]/BarGain; if(h >= 39) h = 39; display.fillRect(x/2+20, 159-h, 4, h, ILI9341_RED); h = hG[x]/BarGain; if(h >= 39) h = 39; display.fillRect(x/2+20, 239-h, 4, h, ILI9341_BLUE); } // 頻度バー表示(G); Gの分解能は6bitの64段階なので、2ピクセルおきに2ピクセル幅のおバー表示する。 for(x = 0; x < 256; x+=4) { h = hG[x]/BarGain; if(h >= 39) h = 39; display.fillRect(x/2+20, 199-h, 2, h, ILI9341_GREEN); } } ```