2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

M5Cameraを極めて融通の利かない写真専門家にする

Posted at

概要

M5Cameraをいとも簡単に顔認識機付きWebカメラにできるCameraWebServer。この恐るべき多機能なサンプルをシェイプアップしまくって、極めて融通の利かない写真専門家にする。顔認識はもちろん、動画ストリーミング、外部からの撮影パラメータ変更などの機能を無効にしまくる。

超初心者ならではの躊躇のなさを発揮するので、やり過ぎ、間違いなどにご注意を。

試した環境

  • M5Camera Version B
  • Arduino IDE 1.8.15
  • Arduino core for the ESP stable 1.0.6
  • CameraWebServer(Arduino core for the ESP stable 1.0.6に同梱)

手順

事前学習

CameraWebServerをシンプルにしたい欲求はいろんな方がお持ちのようで、まずは先人たちの知恵を学んだ方が良い。つまるところ、この記事不要説。

CameraWebServerを開いて別名で保存する

Arduino core for the ESPが入っていれば、スケッチ例から開ける。
[ファイル]-[スケッチ例]-[ESP32]-[Camera]-[CameraWebServer]
これを別名で保存しておく。今回はM5Camera-Imageにした。

顔認識関係と動画ストリーミング関係を削除する

ガツガツと削除。本来は顔認識関係と動画ストリーミング関係を、それぞれ順を追って削除していった方が分かりやすいけど、ここでは一緒くたにやる。顔を認識したとき、ストリーミング用の画像に四角形を埋め込んだりしてて、二人は切っても切れない関係のご様子なので。

対象はapp_httpd.cpp。いきなり削除後のソースだけにすると後で振り返るのが大変なので、関係してるパーツごとに削除した履歴を書いておく。以下は残した部分ではなく、削除した部分を書いているので気を付けて。

  • 顔認識と動画ストリーミング関係の関数を削除する。
app_httpd.cpp
static ra_filter_t * ra_filter_init(){...}
static int ra_filter_run(){...}
static void rgb_print(){...}
static int rgb_printf(){...}
static void draw_face_boxes(){...}
static int run_face_recognition(){...}
static size_t jpg_encode_stream(){...}
  • 静止画キャプチャー以外のハンドラを削除する。
app_httpd.cpp
static esp_err_t stream_handler(){...}
static esp_err_t cmd_handler(){...}
static esp_err_t status_handler(){...}
static esp_err_t index_handler(){...}
  • 削除したハンドラに対応するURIの定義と登録を削除する。
app_httpd.cpp
void startCameraServer(){
...
    httpd_uri_t index_uri = {...};
    httpd_uri_t status_uri = {...};
    httpd_uri_t cmd_uri = {...};
    httpd_uri_t stream_uri = {...};
...
        httpd_register_uri_handler(camera_httpd, &index_uri);
        httpd_register_uri_handler(camera_httpd, &cmd_uri);
        httpd_register_uri_handler(camera_httpd, &status_uri);
...
}
  • 静止画キャプチャーの関数から顔認識関係を削除する。
app_httpd.cpp
static esp_err_t capture_handler(httpd_req_t *req){
...
    size_t out_len, out_width, out_height;
    uint8_t * out_buf;
    bool s;
    bool detected = false;
    int face_id = 0;
    if(!detection_enabled || fb->width > 400){
...
        if(fb->format == PIXFORMAT_JPEG){
...
        } else {
            jpg_chunking_t jchunk = {req, 0};
            res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL;
            httpd_resp_send_chunk(req, NULL, 0);
            fb_len = jchunk.len;
        }
...
    }

    dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3);
    if (!image_matrix) {
        esp_camera_fb_return(fb);
        Serial.println("dl_matrix3du_alloc failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    out_buf = image_matrix->item;
    out_len = fb->width * fb->height * 3;
    out_width = fb->width;
    out_height = fb->height;

    s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf);
    esp_camera_fb_return(fb);
    if(!s){
        dl_matrix3du_free(image_matrix);
        Serial.println("to rgb888 failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    box_array_t *net_boxes = face_detect(image_matrix, &mtmn_config);

    if (net_boxes){
        detected = true;
        if(recognition_enabled){
            face_id = run_face_recognition(image_matrix, net_boxes);
        }
        draw_face_boxes(image_matrix, net_boxes, face_id);
        free(net_boxes->score);
        free(net_boxes->box);
        free(net_boxes->landmark);
        free(net_boxes);
    }

    jpg_chunking_t jchunk = {req, 0};
    s = fmt2jpg_cb(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, jpg_encode_stream, &jchunk);
    dl_matrix3du_free(image_matrix);
    if(!s){
        Serial.println("JPEG compression failed");
        return ESP_FAIL;
    }

    int64_t fr_end = esp_timer_get_time();
    Serial.printf("FACE: %uB %ums %s%d\n", (uint32_t)(jchunk.len), (uint32_t)((fr_end - fr_start)/1000), detected?"DETECTED ":"", face_id);
    return res;
...
  • 動画ストリーミング用HTTPサーバの起動関係を削除する。
app_httpd.cpp
httpd_handle_t stream_httpd = NULL;
....
void startCameraServer(){
...
    config.server_port += 1;
    config.ctrl_port += 1;
    Serial.printf("Starting stream server on port: '%d'\n", config.server_port);
    if (httpd_start(&stream_httpd, &config) == ESP_OK) {
        httpd_register_uri_handler(stream_httpd, &stream_uri);
    }
...
  • 顔認識の初期設定関係を削除する。
app_httpd.cpp
void startCameraServer(){
...
    ra_filter_init(&ra_filter, 20);
    
    mtmn_config.type = FAST;
    mtmn_config.min_face = 80;
    mtmn_config.pyramid = 0.707;
    mtmn_config.pyramid_times = 4;
    mtmn_config.p_threshold.score = 0.6;
    mtmn_config.p_threshold.nms = 0.7;
    mtmn_config.p_threshold.candidate_number = 20;
    mtmn_config.r_threshold.score = 0.7;
    mtmn_config.r_threshold.nms = 0.7;
    mtmn_config.r_threshold.candidate_number = 10;
    mtmn_config.o_threshold.score = 0.7;
    mtmn_config.o_threshold.nms = 0.7;
    mtmn_config.o_threshold.candidate_number = 1;
    
    face_id_init(&id_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES);
...

この辺りのいらない判断は、以下の記事を参考にした。ただし、以下の記事は「顔認識をしよう!」という前向きな記事。こんな使われ方は不本意かも知れない…。

  • 用なしになったヘッダーファイルの読み込みを削除する。
app_httpd.cpp
#include "img_converters.h"
#include "camera_index.h"
...
#include "fb_gfx.h"
#include "fd_forward.h"
#include "fr_forward.h"
  • もはや無意味になった定数や変数、型の定義を削除する。
app_httpd.cpp
#define ENROLL_CONFIRM_TIMES 5
#define FACE_ID_SAVE_NUMBER 7

#define FACE_COLOR_WHITE  0x00FFFFFF
#define FACE_COLOR_BLACK  0x00000000
#define FACE_COLOR_RED    0x000000FF
#define FACE_COLOR_GREEN  0x0000FF00
#define FACE_COLOR_BLUE   0x00FF0000
#define FACE_COLOR_YELLOW (FACE_COLOR_RED | FACE_COLOR_GREEN)
#define FACE_COLOR_CYAN   (FACE_COLOR_BLUE | FACE_COLOR_GREEN)
#define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED)

typedef struct {
        size_t size; //number of values used for filtering
        size_t index; //current value index
        size_t count; //value count
        int sum;
        int * values; //array to be filled with values
} ra_filter_t;

typedef struct {
        httpd_req_t *req;
        size_t len;
} jpg_chunking_t;

#define PART_BOUNDARY "123456789000000000000987654321"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

static ra_filter_t ra_filter;
...
static mtmn_config_t mtmn_config = {0};
static int8_t detection_enabled = 0;
static int8_t recognition_enabled = 0;
static int8_t is_enrolling = 0;
static face_id_list id_list = {0};
  • M5Cameraに写真を撮ってもらうURIを変更する。変えないと何となく気持ち悪い。
app_httpd.cpp
void startCameraServer(){
...
    httpd_uri_t capture_uri = {
//        .uri       = "/capture",
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = capture_handler,
        .user_ctx  = NULL
    };
...

ここまでやると、事前学習で紹介した以下のサイトのapp_httpd.cppとほとんど同じになる。

お好みの撮影パラメータを定義する

対象はM5Camera-Image.ino。これも考えようによってはガツガツ削除できるけど、うっかりカメラの機種が変わることも考えて、今回はガッツかないことにする。なぜか?それはいきなり販売中止になると、頭の中が真っ白になるから。

  • 解像度やJPEG品質とかの設定を追加する。
app_httpd.cpp
  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;

  // My setting
  config.frame_size = FRAMESIZE_QVGA;
  config.jpeg_quality = 10;
  config.fb_count = 1;

追加したのは、// My settingの部分。

  • 画像の向きを調整する。うちのM5Cameraは上下左右が逆に映るので、これを直す。
#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

  // For M5Camera
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);

追加したのは、// For M5Cameraの部分。上の方にあるCAMERA_MODEL_M5STACK_WIDEってきっと、M5Cameraの魚眼版のことだと思うんだけど、これの場合は元々、上下左右を逆にするようになってる。通常版のM5Cameraの場合はそうならない想定なんだろうな…。うちのがおかしいのか?

どんな撮影パラメータが使えるかは、事前学習で紹介した以下の記事にたくさん挙げて下さってる。両手を合わせながらありがたく参考にする。

camera_index.hを削除する

別に削除しなくてもいいけど、使わないので削除する。薄情な世の中。

動かす

あとは、CameraWebServerでお約束のカメラの機種とかWi-Fiの設定をして、M5Cameraに流し込めばOK。起動するとシリアルモニターにアクセス先のIPアドレスが表示されるので、ブラウザでそこにアクセス。無言で注文通りに今の瞬間を捉えた画像が表示される。

最後に

ド級の初心者にはちょっとハードル高かった。でも、先人様たちのおかげ、見様見真似で何とかなった。後は同じM5Cameraに、先の記事に書いた環境センサーもつないで、写真も撮れて温度も教えてくれる偉人にするだけ。

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?