ストアカで「LINEに通知できる防犯カメラを作ろう!」という講座をやっているのですが、時々撮った写真が緑被りすることがあったのです。
緑被り対策としてイメージセンサーの初期化の後にディレイを入れていたのですが、ディレイを長くすると写真を撮るタイミングが遅くなるので都合が悪いんです。
プログラムはM5Cameraを使っていた頃から今のTimer Camera Xまで色々と変わっていますが、カメラモジュールの初期化パラメーターはそのまま使用していました。
その他に、Timer Camera XのOV3660になって写る写真がボヤーとした感じになっていて、M5CameraのOV2640より画質が落ちていたので、初期化パラメーターを調べて色々とテストしてみることにしました。
以前から使用している初期化パラメーター(抜粋)
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10
config.fb_count = 1
OV2640の写真(M5Stack Unit CAMS3を使用)ディレイあり
OV3660の写真(M5Stack Timer Camer Xを使用)ディレイあり
初期化パラメーター
GitHubにESP32 Camera Driverというリポジトリがあり、ここにESP32が対応しているイメージセンサーなどの情報が載っていて、初期化パラメーターについても簡単にコメントが書いてありました。
載っていた初期化パラメーター(抜粋)
.xclk_freq_hz = 20000000,//EXPERIMENTAL: Set to 16MHz on ESP32-S2 or ESP32-S3 to enable EDMA mode .pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG .frame_size = FRAMESIZE_UXGA,//QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. .jpeg_quality = 12, //0-63, for OV series camera sensors, lower number means higher quality .fb_count = 1, //When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode. .grab_mode = CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled
.grab_mode
はこれまで使用していなかったのですが、記述しないとデフォルトがCAMERA_GRAB_WHEN_EMPTY
になるようです。
この初期化パラメーターで、初期化後に全くディレイを入れない状態で写真を撮影したら、OV2640とOV3660で次のような写真が撮れてしまいました。
OV2640の写真(M5Stack Unit CAMS3を使用)ディレイなし
OV3660の写真(M5Stack Timer Camer Xを使用)ディレイなし
しかし、今回のテストのために用意したプログラム(ブラウザでアクセス(リロード)した時に写真を撮って表示する)により、色被りした写真が撮れてしまうことよりも深刻な問題が潜んでいることが発覚しました。
なんと色被りしていた写真は、写真を撮る命令を実行した時のものではなく、どうもイメージセンサーを初期化したときに撮られたものらしかったのです。
そこで、写真を撮る動作に影響を与えていると思われる.fb_count
と.grab_mode
の値の組み合わせで、どのような写真が取れるのか確かめてみました。
なんと、正しく動作する組み合わせは.fb_count
が2
で.grab_mode
がCAMERA_GRAB_LATEST
の1通りしかありませんでした。
画質調整用パラメーター
画質を調整できそうなパラメーターは、ESP32 Camera Driverリポジトリのソースコードを漁っていたら見つけることができました。
載っていた画質調整パラメーター(抜粋)
int8_t brightness;//-2 - 2 int8_t contrast;//-2 - 2 int8_t saturation;//-2 - 2 int8_t sharpness;//-2 - 2 uint8_t denoise;
輝度、コントラスト、彩度、シャープネス、ノイズ除去の調整が行えるようです。
これらのパラメーターでOV3660の画質を向上させることができるか試行錯誤した結果、次のパラメータでOV2640に近い画質に改善することができました。
s->set_contrast(s, 1);
s->set_saturation(s, 2);
s->set_denoise(s, 1);
OV3660の写真(M5Stack Timer Camer Xを使用)画質調整なし
OV3660の写真(M5Stack Timer Camer Xを使用)画質調整あり
新たな問題
OV3660の画質も向上し一件落着っと思ったのですが、シリアルモニタに見慣れないメッセージcam_hal: EV-EOF-OVF
が幾つも出ているのに気づきました。
また、この状態だと、まれに次のような変な写真が撮れてしまうことがありました。
途中から緑被りしている写真
上記の画質調整パラメーターを使用するとこのメッセージが表示されるようで、.fb_count
を1
.grab_mode
をCAMERA_GRAB_WHEN_EMPTY
に戻すと、ほぼメッセージが表示されなくなることがわかりました。
八方塞がりな感じになってしまったため、正攻法で攻めるのをやめて邪道な方法をとることにしました。
写真を撮る命令の実行で前回の写真が得られるなら、1度に2回連続写真を撮ってしまえば今の写真が得られる訳です。
コードは次のようになります。
esp_camera_fb_return(fb);
fb = esp_camera_fb_get();
写真を撮った後にバッファを解放してから2回目の写真を撮るようにしないと、マイコンがダンプを吐いてリブートしてしまうので注意してください。
テストプログラム
CameraTest.ino
#include <WiFi.h> // WiFiライブラリ #include <esp_camera.h> // ESPカメラライブラリ #include <esp_http_server.h> // ESP Webサーバーライブラリ #include <ESPmDNS.h> // マルチキャストDNSライブラリ //#define CAMERA_MODEL_XIAO_ESP32S3 // 使用するボードのコメントを外す //#define CAMERA_MODEL_UNIT_CAMS3 // 使用するボードのコメントを外す #define CAMERA_MODEL_TIMER_CAM // 使用するボードのコメントを外す #include "camera_pins.h" #define WIFI_SSID "********" // *にWiFiのSSIDを書く #define WIFI_PASS "********" // *にWiFiのパスワードを書く #define HOST_NAME "esp32camera" // ホスト名 #define VFLIP 1 // カメラの設置向き 1:正常 0:逆さ // Webサーバー用オブジェクト作成 httpd_handle_t camera_httpd; // マイコンの電源投入後に、最初に呼び出される関数(ディープスリープからの復帰後も) void setup() { Serial.begin(115200); // シリアルモニタの通信速度設定 initCamera(); // カメラの初期化 connectWiFi(); // WiFi接続 MDNS.begin(HOST_NAME); // mDNS初期化 startCameraServer(); // Webサーバーの開始 } // setup関数の後に、繰り返し呼び出される関数 void loop() { delay(1000); } // カメラの初期化(CameraWebServerのサンプルコードを改良) void initCamera() { camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM; config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM; config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM; config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM; config.pin_xclk = XCLK_GPIO_NUM; config.pin_pclk = PCLK_GPIO_NUM; config.pin_vsync = VSYNC_GPIO_NUM; config.pin_href = HREF_GPIO_NUM; config.pin_sscb_sda = SIOD_GPIO_NUM; config.pin_sscb_scl = SIOC_GPIO_NUM; config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 1; config.fb_location = CAMERA_FB_IN_PSRAM; config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; //config.grab_mode = CAMERA_GRAB_LATEST; esp_camera_init(&config); sensor_t *s = esp_camera_sensor_get(); s->set_brightness(s, -1); // 輝度 -2 - 2 s->set_contrast(s, 1); // コントラスト -2 - 2 s->set_saturation(s, 2); // 彩度 -2 - 2 s->set_denoise(s, 1); // ノイズ除去 s->set_vflip(s, VFLIP); #if defined(CAMERA_MODEL_TIMER_CAM) s->set_hmirror(s, !VFLIP); #else s->set_hmirror(s, VFLIP); #endif Serial.println("カメラ初期化完了"); } // WiFi接続 void connectWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASS); // WiFi接続 Serial.print(WIFI_SSID "に接続中"); int i = 0; while (WiFi.status() != WL_CONNECTED) { // 繋がるまでループ if (++i > 5) ESP.restart(); // 5回でリセット Serial.print("."); delay(1000); } Serial.println("接続完了"); } // Webサーバーの開始 void startCameraServer() { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); // ストリーミング用URI httpd_uri_t index_uri = { .uri = "/", .method = HTTP_GET, .handler = capture_handler, .user_ctx = NULL }; // Webサーバーの開始とハンドラー関数の登録 httpd_start(&camera_httpd, &config); httpd_register_uri_handler(camera_httpd, &index_uri); Serial.print("次のURLで表示できます。'http://" HOST_NAME ".local'"); } // ストリーミング用ハンドラー関数 esp_err_t capture_handler(httpd_req_t *req) { camera_fb_t *fb = esp_camera_fb_get(); esp_camera_fb_return(fb); fb = esp_camera_fb_get(); httpd_resp_set_type(req, "image/jpeg"); httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); httpd_resp_send(req, (const char *)fb->buf, fb->len); esp_camera_fb_return(fb); return ESP_OK; }
camera_pins.h
#if defined(CAMERA_MODEL_XIAO_ESP32S3) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 10 #define SIOD_GPIO_NUM 40 #define SIOC_GPIO_NUM 39 #define Y9_GPIO_NUM 48 #define Y8_GPIO_NUM 11 #define Y7_GPIO_NUM 12 #define Y6_GPIO_NUM 14 #define Y5_GPIO_NUM 16 #define Y4_GPIO_NUM 18 #define Y3_GPIO_NUM 17 #define Y2_GPIO_NUM 15 #define VSYNC_GPIO_NUM 38 #define HREF_GPIO_NUM 47 #define PCLK_GPIO_NUM 13 #elif defined(CAMERA_MODEL_UNIT_CAMS3) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 11 #define SIOD_GPIO_NUM 17 #define SIOC_GPIO_NUM 41 #define Y9_GPIO_NUM 13 #define Y8_GPIO_NUM 4 #define Y7_GPIO_NUM 10 #define Y6_GPIO_NUM 5 #define Y5_GPIO_NUM 7 #define Y4_GPIO_NUM 16 #define Y3_GPIO_NUM 15 #define Y2_GPIO_NUM 6 #define VSYNC_GPIO_NUM 42 #define HREF_GPIO_NUM 18 #define PCLK_GPIO_NUM 12 #elif defined(CAMERA_MODEL_TIMER_CAM) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 25 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 32 #define VSYNC_GPIO_NUM 22 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #else #error "Camera model not selected" #endif