編集履歴一覧に戻る
eMoi-picのアイコン画像

eMoi-pic が 2024年06月16日15時40分04秒 に編集

コメント無し

メイン画像の変更

メイン画像が変更されました

本文の変更

# 簡易ベクトルスコープ表示

-

Speresenseカメラで撮影したカラー画像の特徴を把握するために、簡易ベクトルスコープを作成してみました。YCbCr(YUV)のYはRGB各値の単純平均で簡略化しています。ヒストグラムのスケールも固定しています。RGB→YUVへの色変換は、Spresenseの内部処理機能(convertPixFormat関数)を使っても実装可能ですが、このサンプルコードでは直に書いています。

+

Speresenseカメラで撮影したカラー画像の特徴を把握するために、簡易ベクトルスコープを作成してみました。YCbCr(YUV)のYはR+2G+Bで簡略化しています。ヒストグラムのスケールも固定しています。RGB→YUVへの色変換は、Spresenseの内部処理機能(convertPixFormat関数)を使っても実装可能ですが、このサンプルコードでは直に書いています。画像処理では、RGBの零や飽和の取り扱いが課題になりますが、ベクトルスコープの表示では対象としない扱いにしています。

```

+

// Spresenseカメラ ベクトルスコープ表示 2024.6.16

#include <stdio.h> #include <Camera.h> #include <Adafruit_ILI9341.h>

-

#define TFT_DC (9) #define TFT_CS (10)

+

#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.setAutoWhiteBalanceMode(CAM_WHITE_BALANCE_AUTO);

err = theCamera.startStreaming(true, CamCB); } void loop() {

-

int x, y, n, tY, h;

+

int x, y, n, tY, h, val;

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]

+

R[x][y] = (imgbuf_RGB565[n] >> 8) & 0x00f8; // [0-255] G[x][y] = (imgbuf_RGB565[n] >> 3) & 0x00fc; // [0-255] B[x][y] = (imgbuf_RGB565[n] << 3) & 0x00f8; // [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]

+

// RGBからYCbCr(YUV)に変換し、各チャンネル毎に8btにスケーリングして格納。YはR+2G+Bで近似 for (x = 0; x < 160; x++) { for(y = 0; y < 120 ; y++) { tY = ((int)R[x][y]+(int)G[x][y]*2+(int)B[x][y]) / 4; Y[x][y] = tY; // [0-255] val = (int)B[x][y] - tY; if(val > 127) { U[x][y] = 127; } else if(val < -128) { U[x][y] = -128; } else U[x][y] = val; val = (int)R[x][y] - tY; if(val > 127) { V[x][y] = 127; } else if(val < -128) { V[x][y] = -128; } else V[x][y] = val; }

} // ベクトルスコープ表示 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]));

+

if(R[x][y]==0 || G[x][y]==0 || B[x][y]==0|| R[x][y]>=240 || G[x][y]>=248 || B[x][y]>=240) break; // RGBの各値が上限下限で飽和していたら描画をスキップ display.drawPixel(240+(int)U[x][y]/2, 180-(int)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

+

#define BarGain 50

// 頻度バー表示(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;

+

h = hB[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); } }

+

```