Himajinのアイコン画像
Himajin 2021年02月28日作成 (2021年02月28日更新)
製作品 製作品 閲覧数 3147
Himajin 2021年02月28日作成 (2021年02月28日更新) 製作品 製作品 閲覧数 3147

M5Cameraを3台使用したWigglegramカメラ

M5Cameraを3台使用したWigglegramカメラ

はじめに

Wigglegramとは、視差のついた写真を2個以上使用して作られた、立体感のある実写のGIFアニメーションです。

今回は、M5Cameraを3台使用して、Wigglegramを作成する方法を考えて試したのでここで公開します。

ElchikaではアニメーションGIFが表示できないようですので、実際に作成したGIFアニメーションは以下のリンクからチェックしてください。

実施例
https://www.hiroto.tech/moin.cgi/Camera/Wigglegram/results

ユニークなアイデアのポイントは、M5Cameraを使用してWigglegramを作成する前例がなかった点とUDPブロードキャストで同時撮影を実装した点です。

要件

まずはM5Cameraを使用してWigglegramを作成するための要件を以下のように決めました。

  1. M5Camera 3台を横に並べて固定できること
  2. M5Cameraに外部から電源供給できること(バッテリーが内蔵されていないため)
  3. M5Camera 3台が同じタイミングで写真撮影できること
  4. 撮影した写真をM5Cameraから抜き出してJPEG形式で保存できること
  5. 3枚の写真からGIFに変換できること

要件を満たすための施策

上で定義した要件を満たすための施策は以下のように決めました。

要件 施策
M5Camera 3台を横に並べて固定できること M5CameraのLEGO互換の取付穴を利用して固定する。3Dプリンタで治具を作成する。GoProの3Wayという三脚を台座にする。
M5Cameraに外部から電源供給できること モバイルバッテリーを使用する
M5Camera 3台が同じタイミングで写真撮影できること コントロール用の端末からUDPパケットをブロードキャストで送信してM5Cameraに通知する
撮影した写真をM5Cameraから抜き出してJPEG形式で保存できること コントロール用の端末からhttpでダウンロードする
3枚の写真からGIFに変換できること コントロール用の端末でImagemagickを使用して変換する

全体構成

M5Camera単体では写真撮影のタイミング制御やストレージへの画像保存はできないため、コントロール用の端末を用意して制御するようにします。コントロール端末はノートPCを使用しました。

M5Camera 3台とコントロール端末がWi-FiのステーションとしてモバイルAPに接続してネットワークを構成します。

Wi-Fi接続

M5CameraではUDP ポート番号10000(なんでもよい)で受信待ちにします。
コントロール端末からポート番号10000のUDPパケットをブロードキャストして、M5Cameraで同時にUDPパケットを受け取ったタイミングで撮影します。

写真撮影

撮影したデータのダウンロードを順番に行います。

M5Camera #1からダウンロード

M5Camera #2からダウンロード

M5Camera #3からダウンロード

最後にコントロール端末でアニメーションGIFを生成します。

キャプションを入力できます

コントロール用の端末はM5Stackでも作ってみましたが、少し離れると通信が安定しないことがあったり、GIFの生成が難しかったので、最終的にはノートPCで確認しました。

各施策の実施

治具作成

Fusion360を使用して図面を作成し、3Dプリンタで出力しました。

M5Cameraと接続するためのパーツ

キャプションを入力できます

カメラ同士の間隔は 5cmとしました。
M5Cameraと接続するためのパーツを3個作り、直線状に並べ、接着剤で接着します。

GoPro 3wayと接続するためのパーツ

GoPro 3wayの治具も接着します。

3Dプリンタで作成した治具

M5Cameraをセットすると、以下のようになります。

M5Cameraをセットしたところ

モバイルバッテリーから電源供給

M5Camera のUSB端子に電源供給するために、モバイルバッテリーを3台用意します。

3台のM5Cameraに電源供給

が、実際に試してみるとさすがにこれは扱いづらいです。バッテリーはできれば1台にしたい。

そこで、モバイルバッテリー1台から、M5Camera 3台に電源供給できるように配線をしました。

モバイルバッテリー1台から、M5Camera 3台に電源供給

M5Cameraの動作確認

M5Cameraのデモ用のソフトウェアCameraWebServerをできる限り利用してアプリケーションを作ります。
デモソフトウェアCameraWebServerはHTTPサーバーが動作しており、以下のAPIが使用可能です。

API 説明
/capture カメラで撮影し、jpegファイルをダウンロード
/control カメラの設定を変更
/status json形式で設定を表示
/stream captureの動作をずっと繰り返し、ブラウザ上では Motion JPEGとして表示されます。

例えば、M5CameraのIPアドレスが192.168.179.10のとき、PCから以下のコマンドを実行することで写真撮影してa.jpgをダウンロードできます。

curl http://192.168.179.10/capture -o a.jpg

また、画像サイズをUXGAに変更したい場合以下のようにアクセスします。

curl http://192.168.179.10/control?var=framesize&val=10

valueは以下のenumを参考にします。

https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/esp32-camera/sensor.h

typedef enum {
    FRAMESIZE_QQVGA,    // 160x120
    FRAMESIZE_QQVGA2,   // 128x160
    FRAMESIZE_QCIF,     // 176x144
    FRAMESIZE_HQVGA,    // 240x176
    FRAMESIZE_QVGA,     // 320x240
    FRAMESIZE_CIF,      // 400x296
    FRAMESIZE_VGA,      // 640x480
    FRAMESIZE_SVGA,     // 800x600
    FRAMESIZE_XGA,      // 1024x768
    FRAMESIZE_SXGA,     // 1280x1024
    FRAMESIZE_UXGA,     // 1600x1200
    FRAMESIZE_QXGA,     // 2048*1536
    FRAMESIZE_INVALID
} framesize_t;

3台のM5Cameraで撮影するには、このコマンド順番に実行します。

curl http://192.168.179.10/capture -o a.jpg
curl http://192.168.179.11/capture -o b.jpg
curl http://192.168.179.12/capture -o c.jpg

しかし、3つのコマンドを同時に実行することができず、撮影した写真は時間的にずれができてしまいます。

時間のずれが気にならない場合は、UDPブロードキャストを扱うためのコードの改造は不要です。

どうやったら改造できそうか、WindowsでArduinoをインストールした場合、サンプルコードCameraWebServerのソースコードは以下の場所にあるので少し調べてみます。

C:\Users\<User name>\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32\examples\Camera\CameraWebServer

esp_camera.hを参照するとAPIは5つだけ。

https://github.com/espressif/esp32-camera/blob/master/driver/include/esp_camera.h

esp_err_t esp_camera_init(const camera_config_t* config);
esp_err_t esp_camera_deinit();
camera_fb_t* esp_camera_fb_get();
void esp_camera_fb_return(camera_fb_t * fb);
sensor_t * esp_camera_sensor_get();

実際に、captureが呼ばれた時の処理はapp_httpd.cppの中のcapture_handlerという関数の中に定義されています。

以下の個所で画像取得し、

fb = esp_camera_fb_get();

以下の個所でhttpで画像送信していました。

res = httpd_resp_send(req, (const char *)fb->buf, fb->len);

調べた結果から、UDPパケット受信時にesp_camera_fb_get() を実行するようにし、
capture API では esp_camera_fb_get() の実行をやめればよさそうです。

UDPブロードキャストで同時撮影

コントロール端末からUDPパケットを送信し、宛先を255.255.255.255とすることでブロードキャストします。

コントロール端末側コード

C言語で以下のコードを作成しました。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int
main()
{
 int sock;
 struct sockaddr_in addr;
 int yes = 1;

 sock = socket(AF_INET, SOCK_DGRAM, 0);

 addr.sin_family = AF_INET;
 addr.sin_port = htons(10000);
 addr.sin_addr.s_addr = inet_addr("255.255.255.255");

 setsockopt(sock,
            SOL_SOCKET, SO_BROADCAST, (char *)&yes, sizeof(yes));

 sendto(sock, "x", 1, 0, (struct sockaddr *)&addr, sizeof(addr));

 close(sock);

 return 0;
}

以下のコマンドでビルドします。

gcc send_bcast.c -o send_bcast

send_bcastと実行すればポート番号10000にブロードキャストすることができます。

M5Camera側コード

10000ポートでUDP待ち受けするコードを追加します。
何かデータが届いたらesp_camera_fb_get()を呼び出して撮影し、global_fbに保存します。

Arduinoのコードにグローバル変数として以下を追加します。

/* Start */
const int localPort = 10000;      // ポート番号
WiFiUDP udp;
camera_fb_t * global_fb;

setup()関数の末尾に以下を追加します。

/* Start */
udp.begin(localPort);  // UDP通信の開始(引数はポート番号)

loop()関数の内容を以下のように変更します。

// put your main code here, to run repeatedly:
  //delay(10000);
  /* Start */
  if (udp.parsePacket()) {
    Serial.println(udp.read()); // UDP通信で来た値を表示
    //capture a frame
    global_fb = esp_camera_fb_get();
    if (!global_fb) {
      ESP_LOGE(TAG, "Frame buffer could not be acquired");
      return;
    }
  /* End */

写真のダウンロード

capture APIを作り直し、global_fbのデータをHTTPで返却するように修正します。
capture2というAPIを作ります。

app_httpd.cpp を修正します。
startCameraServer()関数に以下の変数を追加。

httpd_uri_t capture2_uri = {
        .uri       = "/capture2",
        .method    = HTTP_GET,
        .handler   = capture2_handler,
        .user_ctx  = NULL
    };

httpd_register_uri_handlerの呼び出し箇所で、capture2_uriを追加で登録します。

if (httpd_start(&camera_httpd, &config) == ESP_OK) {
        httpd_register_uri_handler(camera_httpd, &index_uri);
        httpd_register_uri_handler(camera_httpd, &cmd_uri);
        httpd_register_uri_handler(camera_httpd, &status_uri);
        httpd_register_uri_handler(camera_httpd, &capture_uri);
        httpd_register_uri_handler(camera_httpd, &capture2_uri);  // 追加
    }

capture_handler()をコピーして、capture2_handler()関数を作ります。変更箇所は以下の個所です。
esp_camera_fb_get()の呼び出しをやめ、global_fbを渡します。

キャプションを入力できます

コントロール端末側の撮影スクリプト

以下のスクリプトを実行します。
M5Camera #1のIPアドレスが192.168.179.10、M5Camera #2のIPアドレスが192.168.179.11、M5Camera #3のIPアドレスが192.168.179.12 と仮定しています。

#!/bin/sh

send_bcast
sleep 1

curl http://192.168.179.10/capture2 -o a.jpg
curl http://192.168.179.11/capture2 -o b.jpg
curl http://192.168.179.12/capture2 -o c.jpg

GIFの生成

a.jpg, b.jpg, c.jpg をダウンロードできたら、回転補正、位置補正を実施してからGIFを生成します。

M5Cameraのカメラモジュールの取り付け精度が悪いのか、回転補正してやる必要がありました。

位置補正は、注目する地点の座標をa.jpg, b.jpg, c.jpgで見つけて、設定します。

アニメーションは以下の順で作成します。

表示される順番 ファイル名
1 a.jpg
2 b.jpg
3 c.jpg
4 b.jpg
... 以下、繰り返し

以下のスクリプト(make_wiggle.sh)を作成します。

DELAY=10

IMAGE1=$1
IMAGE2=$2
IMAGE3=$3
TMP_1=tmp.$1
TMP_2=tmp.$2
TMP_3=tmp.$3
GIF=$4
GIF_SMALL=small.$4

X1=$8
Y1=$9
X2=${10}
Y2=${11}
X3=${12}
Y3=${13}

ROT1=$5
ROT2=$6
ROT3=$7

if [ ${X1} -lt ${X2} ] ; then
  X_MIN=${X1}
else
  X_MIN=${X2}
fi
if [ ${X_MIN} -lt ${X3} ] ; then
  X_MIN=${X_MIN}
else
  X_MIN=${X3}
fi
if [ ${Y1} -lt ${Y2} ] ; then
  Y_MIN=${Y1}
else
  Y_MIN=${Y2}
fi
if [ ${Y_MIN} -lt ${Y3} ] ; then
  Y_MIN=${Y_MIN}
else
  Y_MIN=${Y3}
fi

if [ ${X1} -lt ${X2} ] ; then
  X_MAX=${X2}
else
  X_MAX=${X1}
fi
if [ ${X_MAX} -lt ${X3} ] ; then
  X_MAX=${X3}
else
  X_MAX=${X_MAX}
fi
if [ ${Y1} -lt ${Y2} ] ; then
  Y_MAX=${Y2}
else
  Y_MAX=${Y1}
fi
if [ ${Y_MAX} -lt ${Y3} ] ; then
  Y_MAX=${Y3}
else
  Y_MAX=${Y_MAX}
fi


WIDTH=`identify -format "%W" ${IMAGE1}`
HEIGHT=`identify -format "%H" ${IMAGE1}`

echo ${WIDTH}x${HEIGHT}

WIDTH=`expr ${WIDTH}  - ${X_MAX} + ${X_MIN}`
HEIGHT=`expr ${HEIGHT} - ${Y_MAX} + ${Y_MIN}`

# Rotation
convert $1 -rotate ${ROT1} rot.$1
convert $2 -rotate ${ROT2} rot.$2
convert $3 -rotate ${ROT3} rot.$3

# Move
convert rot.$1 -crop ${WIDTH}x${HEIGHT}+`expr ${X1} - ${X_MIN}`+`expr ${Y1} - ${Y_MIN}` ${TMP_1}
convert rot.$2 -crop ${WIDTH}x${HEIGHT}+`expr ${X2} - ${X_MIN}`+`expr ${Y2} - ${Y_MIN}` ${TMP_2}
convert rot.$3 -crop ${WIDTH}x${HEIGHT}+`expr ${X3} - ${X_MIN}`+`expr ${Y3} - ${Y_MIN}` ${TMP_3}

# Animation
convert -delay ${DELAY} -morph 1 -loop 0 ${TMP_1} ${TMP_2} ${TMP_3} ${TMP_2} ${GIF}

# Resize
convert ${GIF} -coalesce -scale 50% -deconstruct ${GIF_SMALL}

コマンドの実行例は以下のようになります。

./make_wiggle.sh a.jpg b.jpg c.jpg out.gif 0 1.3 -1.3 493 198 462 189 420 188

引数の意味は以下の通りです。

引数 意味
第1引数 1つ目の画像ファイルのパス
第2引数 2つ目の画像ファイルのパス
第3引数 3つ目の画像ファイルのパス
第4引数 出力するGIFファイルのパス
第5引数 1つ目の画像に対する回転補正の角度
第6引数 2つ目の画像に対する回転補正の角度
第7引数 3つ目の画像に対する回転補正の角度
第8引数 1つ目の画像に対する位置補正 (x軸)
第9引数 1つ目の画像に対する位置補正 (y軸)
第10引数 2つ目の画像に対する位置補正 (x軸)
第11引数 2つ目の画像に対する位置補正 (y軸)
第12引数 3つ目の画像に対する位置補正 (x軸)
第13引数 3つ目の画像に対する位置補正 (y軸)

回転補正、位置補正のパラメータ出しがかなり大変でした。

さいごに

M5Camera 3台を使用してWigglegramを作るカメラを作りました。
やってみてわかったこと、難しかったポイントは以下です。

  1. M5Cameraに搭載しているOV2640の個体差なのか、得られる画像が位置ずれしており単純にGIFにするとガタガタしていたため、画像補正(回転、位置)を行った。
  2. GIFを生成する際の回転補正は、カメラの個体差のため一回決めればよかったが、位置補正は毎回調整する必要があった。調整用の用紙を用意して、それを撮影して補正値を決めれば良かったかもしれない。
  3. M5Camera 3個でWigglegramを作成したが、5個, 10個と数を増やす事は理論的には可能。しかし治具を作るのが大変だったため、あきらめた。
  4. 3つの写真でアニメーションを作るとカクカクしてしまうため、モーフィングという機能を使い、補間した。
  5. 撮影時の注意点として、背景が大きく動くと酔うため、背景は遠目にするか、真っ白な壁などシンプルな背景を選んだ方がよい。
ログインしてコメントを投稿する