ESP32 WROVER CAMとEdge Impulseで画像識別

# esp_http_server.hでの同期版
ESP32 WROVER CAMで画像データを収集します。EdgeImpulseにてアノテーションを行い画像モデルを作成します。今回は試験的に数十件だけグー、チョキ、パーの画像を撮ります。作成した画像モデルを元にESP32 WROVER CAMにて画像識別を行うテストです。テスト中は撮影画像をリアルタイムでブラウザに映します。
![wrover_edgeimpulse.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806156/c9bca0e1-5081-f2f7-fbca-8fedbbf964f0.png)

ソースコードのポイント部を下記に示します。
# #include
* esp_http_server.hを使い単一接続のみ可(非同期でないので複数のブラウザでは見れない)
* esp32_pachokigu_inferencing.hはEdgeImpulseで学習した画像モデル
```c++
#include <esp32_pachokigu_inferencing.h>
#include "edge-impulse-sdk/dsp/image/image.hpp"
#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"
```
# HTTPのマルチパートストリーム
```c++
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=frame";
static const char* _STREAM_BOUNDARY = "\r\n--frame\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

// HTMLテンプレート
static const char PROGMEM INDEX_HTML[] = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ESP32 Camera</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; }
        img { width: 100%; max-width: 500px; }
    </style>
</head>
<body>
    <h1>ESP32 Camera Stream</h1>
    <img src="/stream" id="camera-stream">
</body>
</html>
)rawliteral";
```


# ストリームを処理するハンドラ
```c++
static esp_err_t stream_handler(httpd_req_t *req) {
    camera_fb_t *fb = NULL;
    esp_err_t res = ESP_OK;
    size_t _jpg_buf_len = 0;
    uint8_t *_jpg_buf = NULL;
    char part_buf[64];

    res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
    if (res != ESP_OK) {
        return res;
    }

    while (true) {
        fb = esp_camera_fb_get();
        if (!fb) {
            Serial.println("Camera capture failed");
            res = ESP_FAIL;
        } else {
            if (fb->format != PIXFORMAT_JPEG) {
                bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
                esp_camera_fb_return(fb);
                fb = NULL;
                if (!jpeg_converted) {
                    Serial.println("JPEG compression failed");
                    res = ESP_FAIL;
                }
            } else {
                _jpg_buf_len = fb->len;
                _jpg_buf = fb->buf;
            }
        }

        if (res == ESP_OK) {
            size_t hlen = snprintf(part_buf, 64, _STREAM_PART, _jpg_buf_len);
            res = httpd_resp_send_chunk(req, part_buf, hlen);
        }
        if (res == ESP_OK) {
            res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
        }
        if (res == ESP_OK) {
            res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
        }

        if (fb) {
            esp_camera_fb_return(fb);
            fb = NULL;
            _jpg_buf = NULL;
        } else if (_jpg_buf) {
            free(_jpg_buf);
            _jpg_buf = NULL;
        }

        if (res != ESP_OK) {
            break;
        }
    }
    return res;
}

```

#  カメラサーバーを開始する関数
```c++
void start_camera_server() {
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    httpd_uri_t index_uri = {
        .uri       = "/",
        .method    = HTTP_GET,
        .handler   = index_handler,
        .user_ctx  = NULL
    };

    httpd_uri_t stream_uri = {
        .uri       = "/stream",
        .method    = HTTP_GET,
        .handler   = stream_handler,
        .user_ctx  = NULL
    };

    if (httpd_start(&server, &config) == ESP_OK) {
        httpd_register_uri_handler(server, &index_uri);
        httpd_register_uri_handler(server, &stream_uri);
    }
}
```

# 結果
シリアルモニターにguと出てx,yの座標が出ている。
![20240816_173126 (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3806156/8cb79d7c-a5c4-3a47-dac0-e19f22173def.gif)

# これの非同期版は下記
https://qiita.com/hassy131/items/180b0f0ea16b5b74a3dc