Timer Camera Xで画像データを得る



https://qiita.com/katsuyoshi/items/155102eba41701b3a923

の続きで画像データの取得に関してです。  
web_camサンプルでは[startCameraServer()](https://github.com/m5stack/TimerCam-arduino/blob/master/examples/web_cam/web_cam.ino#L82)の一行でWebCameraとしてストリーミングしてくれるので画像データはいったいどこでどう処理されてるんだ???と疑問に思います。

## カメラの初期化

データを取得する前にカメラの初期化が必要になります。  
esp_camera_init()で行いますが、引数として渡す環境設定はTimer Camera Xでハード的に決まっている事なのでweb_camサンプルとほぼ同じになると思います。  


```
void setup() {
    .
    .
  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_QVGA;
  config.jpeg_quality = 10;
  config.fb_count     = 2;

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

  sensor_t *s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  s->set_vflip(s, 1);        // flip it back
  s->set_brightness(s, 1);   // up the blightness just a bit
  s->set_saturation(s, -2);  // lower the saturation
    .
    .
}

```

変更するとすれば config.pixel_format、config.jpeg_quality、config.frame_size辺りではないでしょうか。

config.pixel_formatでは取得する画像データの形式を指定します。jpegの場合はconfig.jpeg_qualityで品質を指定します。config.frame_sizeでは画像サイズを指定します。

今回はjpegデータで欲しいのでこのままにします。config.frame_sizeは後で画像確認する時のためにサイズが小さいFRAMESIZE_QVGAを指定しています。

esp_camera_sensor_get()で得られたsensor_t構造体の関数ポインタを使用して撮像条件を指定できます。ここら辺は前回のweb_camサンプルを使ってGUIで条件を出してそれを適用する様にすればいいのではないでしょうか。とはいいつつ結構大変そうなのでweb_camでの設定から設定するコードを自動生成する様ななんかができればいいのではないかと思いますが、今回の本質ではないのでまたの機会にします。


## 画像データの取得

画像データはいったいどこでどう処理されてるんだ???と疑問に思いましたが、画像データを取得するにはesp_camera_fb_get()関数を使用するだけです。これを呼び出すと撮像したデータがcamera_rb_t構造体を通して得られます。なんと簡単。

```
  camera_fb_t *fb  = NULL;
  fb = esp_camera_fb_get();
```

fb->lenで画像データのサイズが得られ、fb->bufが画像データのポインターとなっています。config.pixel_formatでPIXFORMAT_JPEGを指定しているのでjpegデータとして得られます。

__大事なことは画像データは__ mallocなどで動的に確保されている様なので使い終わって不要になったらesp_camera_fb_return()で __解放してあげないといけない__ ことです。  
これを忘れるとesp_camera_fb_get()の呼び出し2、3回目辺りでメモリ不足で再起動してしまいます。




```
  esp_camera_fb_return(fb);
```

せっかくデータが得られても画像が見れないので、シリアル出力にrubyスクリプトを書き出しそれをコピペして実行すると見れる様にしてみました。  

```
  camera_fb_t *fb  = NULL;
  fb = esp_camera_fb_get();
  
  size_t fb_len = 0;
  fb_len = fb->len;
  size_t blk_size = 60;
  unsigned char inp[blk_size];
  unsigned char out[blk_size * 15 / 10];
  Serial.println("require 'base64'");
  Serial.println("s = <<EOS");
  for (int i = 0; i < fb_len; i += blk_size) {
    encode_base64(&(fb->buf[i]), min(fb_len - i, blk_size), out);
    Serial.printf("%s\n", out);
  }  
  Serial.println("EOS");
  Serial.println("File.write('image.jpg', Base64.decode64(s))");
  Serial.println("system('open image.jpg')");
```

実行するとこんな出力が出るので、適当なrubyスクリプトファイルを作って貼り付けて実行すると画像が見れます。

```
require 'base64'
s = <<EOS
/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAoHCAkIBgoJCAkLCwoMDxkQDw4ODx8WFxIZJCAmJiQgIyIo
.
.
.
S0gFozzSAM0bqAF3Um6lYAzTc0wEopgXYcumak2muZljTHntUfllfu0rjDcy9RUiuDQ0IdijaaQH/9k=
EOS
File.write('image.jpg', Base64.decode64(s))
system('open image.jpg')
```




ソースコードはこちらに公開してます。

https://gist.github.com/katsuyoshi/a34b236f747013df3eda61903f2cd00c