はじめに
Wigglegramとは、視差のついた写真を2個以上使用して作られた、立体感のある実写のGIFアニメーションです。
今回は、M5Cameraを3台使用して、Wigglegramを作成する方法を考えて試したのでここで公開します。
ElchikaではアニメーションGIFが表示できないようですので、実際に作成したGIFアニメーションは以下のリンクからチェックしてください。
実施例
https://www.hiroto.tech/moin.cgi/Camera/Wigglegram/results
ユニークなアイデアのポイントは、M5Cameraを使用してWigglegramを作成する前例がなかった点とUDPブロードキャストで同時撮影を実装した点です。
要件
まずはM5Cameraを使用してWigglegramを作成するための要件を以下のように決めました。
- M5Camera 3台を横に並べて固定できること
- M5Cameraに外部から電源供給できること(バッテリーが内蔵されていないため)
- M5Camera 3台が同じタイミングで写真撮影できること
- 撮影した写真をM5Cameraから抜き出してJPEG形式で保存できること
- 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に接続してネットワークを構成します。
M5CameraではUDP ポート番号10000(なんでもよい)で受信待ちにします。
コントロール端末からポート番号10000のUDPパケットをブロードキャストして、M5Cameraで同時にUDPパケットを受け取ったタイミングで撮影します。
撮影したデータのダウンロードを順番に行います。
最後にコントロール端末でアニメーションGIFを生成します。
コントロール用の端末はM5Stackでも作ってみましたが、少し離れると通信が安定しないことがあったり、GIFの生成が難しかったので、最終的にはノートPCで確認しました。
各施策の実施
治具作成
Fusion360を使用して図面を作成し、3Dプリンタで出力しました。
カメラ同士の間隔は 5cmとしました。
M5Cameraと接続するためのパーツを3個作り、直線状に並べ、接着剤で接着します。
GoPro 3wayの治具も接着します。
M5Cameraをセットすると、以下のようになります。
モバイルバッテリーから電源供給
M5Camera のUSB端子に電源供給するために、モバイルバッテリーを3台用意します。
が、実際に試してみるとさすがにこれは扱いづらいです。バッテリーはできれば1台にしたい。
そこで、モバイルバッテリー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を作るカメラを作りました。
やってみてわかったこと、難しかったポイントは以下です。
- M5Cameraに搭載しているOV2640の個体差なのか、得られる画像が位置ずれしており単純にGIFにするとガタガタしていたため、画像補正(回転、位置)を行った。
- GIFを生成する際の回転補正は、カメラの個体差のため一回決めればよかったが、位置補正は毎回調整する必要があった。調整用の用紙を用意して、それを撮影して補正値を決めれば良かったかもしれない。
- M5Camera 3個でWigglegramを作成したが、5個, 10個と数を増やす事は理論的には可能。しかし治具を作るのが大変だったため、あきらめた。
- 3つの写真でアニメーションを作るとカクカクしてしまうため、モーフィングという機能を使い、補間した。
- 撮影時の注意点として、背景が大きく動くと酔うため、背景は遠目にするか、真っ白な壁などシンプルな背景を選んだ方がよい。
投稿者の人気記事
-
Himajin
さんが
2021/02/28
に
編集
をしました。
(メッセージ: 初版)
-
Himajin
さんが
2021/02/28
に
編集
をしました。
-
Himajin
さんが
2021/02/28
に
編集
をしました。
-
Himajin
さんが
2021/02/28
に
編集
をしました。
-
Himajin
さんが
2021/02/28
に
編集
をしました。
-
Himajin
さんが
2021/02/28
に
編集
をしました。
-
Himajin
さんが
2021/02/28
に
編集
をしました。
ログインしてコメントを投稿する