0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ESP32のカメラで静止画を撮影する

Last updated at Posted at 2024-12-29

M5Cameraなど、ESP32とカメラがセットになったデバイスで、静止画を撮影します。
最終的には、以下のESP32で動作するJavascript環境で実行できるようにします。

(参考) ESP32で動作するJavascript実行環境

ESP32で動作するJavascript実行環境を公開しています。

「電子書籍:M5StackとJavascriptではじめるIoTデバイス制御」

サポートサイト

以下のデバイスで動作確認しました。他のモデルでは未確認です。
・M5Camera X https://www.switch-science.com/products/6555
・TTGO Camera Plus https://ja.aliexpress.com/item/32971057846.html
※いずれももう販売はしていませんが、後継機種があるようです。

カメラ制御のための関数

カメラの制御には、標準でついているライブラリを使います。
以下のサンプルを参考にしました。

今回は、スナップショット画像だけ欲しいので、ストリーム生成や顔検出機能は省いてシンプルにしました。

・esp_camera_init
カメラを初期化します。引数に、ピン配置や画面解像度などを指定できます。

・esp_camera_fb_get
現時点のフレームバッファに格納されている画像データを取得します。

・esp_camera_fb_return
取得していたフレームバッファの画像を開放します。

・esp_camera_deinit
カメラをリセットして初期状態にするそうです。

・esp_camera_sensor_get
カメラのキャプチャに関する設定変更するための構造体のインスタンスを取得します。

・esp_camera_sensor_get_info
カメラ本体の情報を取得します。

初期化と終了は以下のようにしました。

module_camera.cpp
static long camera_initialize(uint8_t type, uint8_t framesize)
{
  camera_config_t config;

  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;

  config.pin_d0 = camera_pins[type][Y2_GPIO_INDEX];
  config.pin_d1 = camera_pins[type][Y3_GPIO_INDEX];
  config.pin_d2 = camera_pins[type][Y4_GPIO_INDEX];
  config.pin_d3 = camera_pins[type][Y5_GPIO_INDEX];
  config.pin_d4 = camera_pins[type][Y6_GPIO_INDEX];
  config.pin_d5 = camera_pins[type][Y7_GPIO_INDEX];
  config.pin_d6 = camera_pins[type][Y8_GPIO_INDEX];
  config.pin_d7 = camera_pins[type][Y9_GPIO_INDEX];
  config.pin_xclk = camera_pins[type][XCLK_GPIO_INDEX];
  config.pin_pclk = camera_pins[type][PCLK_GPIO_INDEX];
  config.pin_vsync = camera_pins[type][VSYNC_GPIO_INDEX];
  config.pin_href = camera_pins[type][HREF_GPIO_INDEX];
  config.pin_sccb_sda = camera_pins[type][SIOD_GPIO_INDEX];
  config.pin_sccb_scl = camera_pins[type][SIOC_GPIO_INDEX];
  config.pin_pwdn = camera_pins[type][PWDN_GPIO_INDEX];
  config.pin_reset = camera_pins[type][RESET_GPIO_INDEX];

  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.

//  config.frame_size = FRAMESIZE_QVGA;
  config.frame_size = (framesize_t)framesize;
  config.jpeg_quality = 10;
  config.fb_count = 1;

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x\n", err);
    return -1;
  }

  return 0;
}

static long camera_dispose(void)
{
  esp_camera_deinit();

  return 0;
}

初期化のための関数「camera_initialize」について補足します。

1つ目の引数は、デバイスのモデルの指定です。モデルごとにピン配置が異なるためです。
2つ目の引数は、画像解像度です。最初に指定した解像度以下であれば、起動後も解像度を変更できるため、初期サイズを指定できるようにしています。

これをESP32上のJavascript実行環境から呼び出せるように以下を用意します。
camera_initializeをリトライしています。
起動直後に呼び出すと失敗することがあるようで、1回リトライするようにしました。

module_camera.cpp
static JSValue esp32_camera_start(JSContext *ctx, JSValueConst jsThis, int argc, JSValueConst *argv)
{
  if( isInitialized )
    return JS_EXCEPTION;

  uint32_t type = CAMERA_MODEL_WROVER_KIT;
  if( argc >= 1 )
    JS_ToUint32(ctx, &type, argv[0]);
  if( type >= CAMERA_MODEL_NUM )
    return JS_EXCEPTION;

  uint32_t framesize = FRAMESIZE_QVGA;
  if( argc >= 2 )
    JS_ToUint32(ctx, &framesize, argv[1]);

  long ret = camera_initialize(type, framesize);
  if( ret != 0 ){
    ret = camera_initialize(type, framesize);
    if( ret != 0 )
      return JS_EXCEPTION;
  }
  isInitialized = true;

  return JS_UNDEFINED;
}

画像取得は以下の感じです。
先ほど指定された解像度に合わせて画像ファイルがmallocして返されますので、
使い終わったらfreeします。
途中で気づいたのですが、esp_camera_fb_getで取得しても、なぜか最新のスナップショット画像が取得できないことがありました。
そこで、2回取得するようにし、2回目の画像を使うようにしています。
camera_fb_tは、使い終わったら、esp_camera_fb_returnを呼び出して開放するようにします。

module_camera.cpp
static long camera_get_capture(uint8_t **pp_image, size_t *p_image_size)
{
  camera_fb_t *fb = NULL;
  fb = esp_camera_fb_get();
  if( fb )
    esp_camera_fb_return(fb);
  fb = esp_camera_fb_get();
  if (!fb){
      Serial.println("Camera capture failed");
      return -1;
  }

  if (fb->format != PIXFORMAT_JPEG){
    Serial.println("Unknown format");
    esp_camera_fb_return(fb);
    return -1;
  }
  
  *pp_image = (uint8_t*)malloc(fb->len);
  if( *pp_image == NULL ){
    Serial.println("Out of memory");
    esp_camera_fb_return(fb);
    return -1;
  }
  memmove(*pp_image, fb->buf, fb->len);
  *p_image_size = fb->len;
  esp_camera_fb_return(fb);

  return 0;
}

(参考)
ESP32で使えるOV3660とOV2640の画質の比較と初期化パラメーター

カメラのキャプチャに関する設定変更

esp_camera_sensor_getの呼び出しで設定できる構造体「sensor_t」とesp_camera_sensor_get_infoで取得可能な構造体「camera_sensor_info_t」のパラメータについて補足します。
たくさんありすぎて、よくわからないパラメータがたくさんありますが、このライブラリで設定・取得できるものを転記しておきました。

機能 意味 デフォルト
framesize フレームサイズ FRAMESIZE_96X96(96x96)~FRAMESIZE_QSXGA(2560x1920) FRAMESIZE_QVGA
quality JPEG Quality 0~63 10
brightness 明るさ -2~2 0
contrast コントラスト -2~2 0
saturation 彩度 -2~2 0
special_effect 特殊効果 0:No Effect
1:Negative
2:Grayscale
3:Red Tint
4:Green Tint
5:Blue Tint
6:Sepia
0
awb(whitebal) 自動ホワイトバランス(Automatic White Balance) bool true
awb_gain bool true
wb_mode ホワイトバランスモード 0:Auto
1:Sunny
2:Cloudy
3:Office
4:Home
0
aec(exposure_ctrl) 自動露出制御(Automatic Exposure Control) bool true
aec2 bool false
aec_value 0~1200 335
ae_level -2~2 0
agc(gain_ctrl) 自動ゲイン制御(Automatic Gain Control) bool true
agc_gain 0~30 0
gainceiling 0~6 0
bpc bool false
wpc bool true
raw_gma bool true
lenc レンズ補正(Lens Correction) bool true
hmirror 水平方向の反転表示 bool false
vflip 垂直方向の反転表示 bool false
dcw bool true
colorbar カラーバーフレーム出力 bool false
sharpness シャープネス -2~2 0
denoise 0

こちらのサイトを参考にさせていただきました。

(参考)

esp32-cameraライブラリを読み解く ~モジュール接続、動作チェック編~
Change ESP32-CAM OV2640 Camera Settings: Brightness, Resolution, Quality, Contrast, and More
esp32-camera/sensor.h

呼び出し例

デバイスのGrove端子に接続した物理ボタンを押すと、画像をキャプチャする例です。
JPEGデータをBASE64エンコードして表示しています。

main.js
import * as camera from "Camera";
import * as input from "Input";

input.openCustomButton(input.BUTTON_X, 4, true);
camera.start(camera.MODEL_M5STACK_V2_PSRAM);
console.log(JSON.stringify(camera.getParameter()));

function loop(){
	esp32.update();

	if( input.wasPressed(input.BUTTON_X)){
		console.log("wasPressed");
		var buffer = camera.getPicture();
		var base64 = utils.base64Encode(new Uint8Array(buffer));
		console.log(base64);
	}
}

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?