●ESP32-CAMモジュール

ESP32-CAM-MBモジュールを使用しました。
使ったモジュールはこちらの物です。
ESP32-CAM カメラ開発ボード(ESP32-CAM-MB)


届いたモジュールは3個の部品でバラバラになっています。


まずは、カメラを接続します、モジュールのカメラ接続部分にあるコネクタのフタを持ち上げます。


そこに、カメラを上向き状態でコネクタの奥まで差し込みます。


フタを指で閉じたらカメラの接続完了です。


パソコンと通信する基盤に取り付けたら組み立て完了です。


▼パソコンとの接続

パソコンとマイクロUSBで接続しますが、100均などで売っている充電専用ケーブルでは接続できません。
そこで、通信もできるケーブルを使用する必要があります。
もし、パソコンに接続してドライバを要求された場合にはCH340のドライバを探してきてインストールします。
私は、秋月電子のCH340チップのページからドライバーソフトをダウンロードしてインストールしました。
秋月電子のCH340チップのページ
その後、正常に接続されているのかをデバイスマネージャで確認します。
windows10ではスタートメニューボタンの横の検索ボックスに dev と入力するとデバイスマネージャを検索することができ、
デバイスマネージャを起動させて、ポート(COM と LTP) の項目にUSB-SERIAL CH340 が追加されているのを確認できたら接続完了です。


▼ArduinoIDEの設定


ArduinoIDEを起動させ、メニューのファイル→環境設定を開きます。
追加のボードマネージャのURLの項目に https://dl.espressif.com/dl/package_esp32_index.json を追加して画面を閉じます。
コピー&ペーストのメニューが出てこない為、Control + v のショートカットで張り付けるのがお勧めです。


次に、メニューのツール → ボード:"??????" → ボードマネージャ を開きます。
検索ボックスに esp32 と入力して検索して esp32 を探し出してインストールします。


ボードの種類の設定をします。
メニューの ツール → ESP32 Arduino → ESP32 Wrover Module を選択します。


次に、Partition Schemeの設定をします。
メニューの ツール → Partition Scheme:"??????" → HugeAPP(3MB No OTA/1MB SPIFFS) を選択します。
これで設定は完了しました。


▼スケッチ例でのカメラの動作確認

さっそくカメラのサンプルを動作させてみるとします。

メニューを開いて ファイル → スケッチ例から ESP32 → Camera → CameraWebServer を開きます。
そのままでは動作しないので、ソースコードの上の方の10行目付近を編集します。

// Select camera model
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
//#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM

#include "camera_pins.h"

const char* ssid = "あなたのWifiのID";
const char* password = "あなたのWifiのパスワード";
赤文字の部分の、カメラモードのコメントアウトを修正してモードを切り替えます。
自宅のWIFIのIDとパスワードを入力します。


次に、ボードに書き込みます、書き込みボタンを押してしばらくすると書き込み完了のメッセージが表示されます。


シリアルモニタを開いて、速度を115200 bpsを選択し、ボードのリセットボタンを押します。
うまくWifiルーターに接続された場合には、 Camera Ready! Use 'http://192.168.0.119' to connect
のような文字が表示されますので、表示されたアドレスをコピーしてブラウザで開きます。
アドレスは環境によって違いますし毎回変わる可能性もありますので、表示されたアドレスを使ってください。


表示されたアドレスをブラウザで開いた所です。
カメラの画像を表示したりできます。


■画像ファイルをSDカードに保存できるようにする

サンプルをいじって、WIFI機能を無効にし画像ファイルを連番でSDカードに保存できるようにします。
まずは、サンプルの93行目から106行のカメラサーバーの部分をコメントアウトします。

コメントアウトした部分の下に次のプログラムを追加します。
  delay(100);
  SPI.begin(sd_sck, sd_miso, sd_mosi, sd_ss);

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  char filename[10];
  if (SD.begin(sd_ss)) {
    unsigned long i = 0;
    while (1) {
      sprintf(filename, "/%ld.JPG", i++);
      if (!SD.exists(filename)) {
        break;
      }
    }

    File file = SD.open(filename, FILE_WRITE);

    if (file.write(fb->buf, fb->len)) {
      Serial.print(filename);
      Serial.println("  書き込み成功");
    }else{
      Serial.println("  書き込み失敗");
    }
    file.close();

  } else {
    Serial.println("SDカードが開けません");
  }
  esp_camera_fb_return(fb);
次に、先頭部分、#include <WiFi.h> の下に、次のコードを追加します。
#include <SD.h>
#define sd_sck  14
#define sd_mosi 15
#define sd_ss   13
#define sd_miso  2

サンプルの修正後、ESP32-CAMに書き込み、FAT16もしくはFAT32でフォーマットしたSDカードをカードスロットに差してリセットボタンを押すと、カメラの画像がJPEGファイルとして保存されます。
リセットボタンを押すたびにカメラから取得したJPEG画像がSDカードに連番で保存されてゆくため、画質の悪いおもちゃのカメラみたいです。
ファイル数が増えていくと連番を振るのに時間がかかるようになりますので、その辺は改良する必要がありますが....


■画像データに直接アクセスする

JPEGなどお呼びでない!私は画像データに直接アクセスしたいんだ!
と言う事で、なんとかしてアクセスする方法を考えます。
最初に各種ヘッダーファイルなどがパソコンのどこにあるのかわからなかったので、
手動でArduinoIDEにESP32-CAMの開発環境をインストールする時に使うファイルを https://github.com/espressif/arduino-esp32 からダウンロードし、
ダウンロードしたZIPファイルを解凍してtoolsフォルダの中にあるget.exeを動かして、必要なファイルを揃えました。
このファイル群の中を探れば必要な情報が見つかるはずです。

まず、画像サイズとJPEGフォーマットをなんとかしたいということで、秀丸でgrepして定義箇所を探します。
typedef enum {
    PIXFORMAT_RGB565,    // 2BPP/RGB565
    PIXFORMAT_YUV422,    // 2BPP/YUV422
    PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE
    PIXFORMAT_JPEG,      // JPEG/COMPRESSED
    PIXFORMAT_RGB888,    // 3BPP/RGB888
    PIXFORMAT_RAW,       // RAW
    PIXFORMAT_RGB444,    // 3BP2P/RGB444
    PIXFORMAT_RGB555,    // 3BP2P/RGB555
} pixformat_t;

typedef enum {
    FRAMESIZE_96X96,    // 96x96
    FRAMESIZE_QQVGA,    // 160x120
    FRAMESIZE_QCIF,     // 176x144
    FRAMESIZE_HQVGA,    // 240x176
    FRAMESIZE_240X240,  // 240x240
    FRAMESIZE_QVGA,     // 320x240
    FRAMESIZE_CIF,      // 400x296
sensor.h の中に、解像度と画像フォーマットの定義を見つける事ができました。
画像データに直接アクセスするために、FRAMESIZE_96X96 のサイズで PIXFORMAT_RGB565 の画像フォーマットでプログラムを書き換える事にします。

そこで、サンプルプログラム CameraWebServer を魔改造したのが次です。
#include "esp_camera.h"
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  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_RGB565;
  config.frame_size = FRAMESIZE_96X96;
  config.jpeg_quality = 12;
  config.fb_count = 1;

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

void loop() {
  delay(1000);

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  Serial.println(fb->width);
  Serial.println(fb->height);
  Serial.println(fb->len);

  Serial.println(fb->buf[0]);
  Serial.println("");

  esp_camera_fb_return(fb);
}

とりあえず、取得するデータは最初の1バイト(赤文字部分)にしました。
ESP32-CAMに書き込み、シリアルモニタを開いて動作を見ます。

実行すると幅、高さ、長さ、画像データの順に値が表示されています。
カメラの前で物を動かすと画像データの値が動きます。
まずは、画像データにアクセスすることができたようです。


▼画像の読み込み

取得できる画像フォーマットであるRGB565とは、ビットマップで色を16ビットで表現する方法で、2バイトで一つのピクセルの色を表します。


16進数で表現すると、
赤色のマスクは0xF800
緑色のマスクは0x07E0
青色のマスクは0x001F
になります。
これを使って画像を読み解いて行こうとおもいます。

RGBからグレースケール画像の作成方法については、
簡易的に処理する方法として、色を見た時の目の認識はGの輝度が6割ほどなのでGのみの値を見て、閾値を決めて白黒画像を作成することにします。

気を付けないといけない点として、
ESP32を調べるとx86と同じでリトル・エンディアンでした。
ということは、バイト単位でアクセスすると下位バイト→上位バイトの順に取り出す事になるということです。
typedef struct {
    uint8_t * buf;              /*!< Pointer to the pixel data */
    size_t len;                 /*!< Length of the buffer in bytes */
    size_t width;               /*!< Width of the buffer in pixels */
    size_t height;              /*!< Height of the buffer in pixels */
    pixformat_t format;         /*!< Format of the pixel data */
    struct timeval timestamp;   /*!< Timestamp since boot of the first DMA buffer of the frame */
} camera_fb_t;
画像にアクセスする構造体を見ると、画像バッファであるbufは8ビット変数のポインタであることがわかります。
ということは、16ビット変数のポインタにキャストしてアクセスすれば動作するんじゃないかと思いました。


以上をふまえて画像を読み込んでみます
前回作成したプログラムの void loop() の内容を次のように書き換えて、簡易的に画像をシリアルモニタに表示するようにします。
void loop() {
  delay(1000);
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  Serial.println(fb->width);
  Serial.println(fb->height);
  Serial.println(fb->len);
  Serial.println(fb->buf[0]);
  Serial.println("");
  uint16_t*pos = (uint16_t*)fb->buf;
  bool i = 0;
  for (int x = 0; x < fb->width; x++) {
    for (int y = 0; y < fb->height; y++) {
      if (55<((*pos) & 0x07E0) >> 5) {
        if (i)Serial.print("0");
      } else {
        if (i)Serial.print(" ");
      }
      pos++;
    }
    if (i)Serial.println("");
    i = !i;
  }
  esp_camera_fb_return(fb);
}

このプログラムを走らせる前に、撮影した画像です。


上記プログラムをESP32-CAMに書き込んで、カメラ画像を文字に変換してシリアルモニタに表示させた所です。 とりあえず、画像データを取得できているようです。

プログラムをちょっといじってRGBから白黒の色を作成し、全体の平均を出して平均以上の部分を表示するようにしてみました。
void loop() {
  delay(500);
  static long average;
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  Serial.println(fb->width);
  Serial.println(fb->height);
  Serial.println(fb->len);
  Serial.println(average);
  Serial.println("");
  uint16_t*pos = (uint16_t*)fb->buf;
  bool i = 0;
  long average_pos = 0;

  for (int x = 0; x < fb->width; x++) {
    for (int y = 0; y < fb->height; y++) {
      unsigned int c = ((float)(((*pos) & 0xF800) >> 11) * 0.3)
                       + ((float)(((*pos) & 0x07E0) >> 5) * 0.6)
                       + ((float)(((*pos) & 0x001F)) * 0.1);
      average_pos += c;
      if (average < c) {
        if (i)Serial.print("0");
      } else {
        if (i)Serial.print(" ");
      }

      pos++;
    }
    if (i)Serial.println("");
    i = !i;
  }
  average = (average_pos / (fb->width * fb->height));
  esp_camera_fb_return(fb);
}
プログラムを実行して画像を表示してみました。
こちらがカメラが見ているはずの画像です。


シリアルモニタの出力結果です。。


とりあえず、形は識別できそうですが、なかなかうまくいきません。
まだまだ、ちょっと厳しいです。

現在作成中です。
時間ができたら更新します。


▲トップページ > マイコンなど