編集履歴一覧に戻る
Himajinのアイコン画像

Himajin が 2021年02月28日18時58分19秒 に編集

初版

タイトルの変更

+

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

タグの変更

+

M5Camera

+

カメラ

+

wigglegram

+

Gif

+

秋葉原2021

メイン画像の変更

メイン画像が設定されました

本文の変更

+

# はじめに Wigglegramとは、視差のついた写真を2個以上の使用して作られた、立体感のある実写のGIFアニメーションです。 今回は、M5Cameraを3台使用して、Wigglegramを作成する方法を考えて試したのでここで公開します。 ElchikaではアニメーションGIFが表示できないようですので、実際に作成したGIFアニメーションは以下のリンクからチェックしてください。 実施例 https://www.hiroto.tech/moin.cgi/Camera/Wigglegram/results # 要件 まずはM5Cameraを使用してWigglegramを作成するための要件とその要件を以下のように決めました。 1. M5Camera 3台を横に並べて固定できること 1. M5Cameraに外部から電源供給できること(バッテリーが内蔵されていないため) 1. M5Camera 3台が同じタイミングで写真撮影できること 1. 撮影した写真をM5Cameraから抜き出してJPEG形式で保存できること 1. 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接続](https://camo.elchika.com/49f4a0036535cfd828583f7785020cfe3c5875d9/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f33643564613065632d323164632d346463302d623237312d333132663635666562313530/) M5CameraではUDP ポート番号10000(なんでもよい)で受信待ちにします。 コントロール端末からポート番号10000のUDPパケットをブロードキャストして、M5Cameraで同時にUDPパケットを受け取ったタイミングで撮影します。 ![写真撮影](https://camo.elchika.com/23ce23fd35c53cfdd09d97b4afe3536592bda4b9/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f33653535626137322d373834342d346562302d623561662d343235383333636263316235/) 撮影したデータのダウンロードはを順番に行います。 ![M5Camera #1からダウンロード](https://camo.elchika.com/0d6fa2383e8b314c04c46f95d12f1923fdae3186/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f38343333363061632d343062612d343930322d626164332d626238326333613663373665/) ![M5Camera #2からダウンロード](https://camo.elchika.com/024a0705fde8d5e3f11cbabf2bd2bdc030e4e58c/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f66363064346432342d346665662d343035642d623361642d366166373461303163643939/) ![M5Camera #3からダウンロード](https://camo.elchika.com/352ccd79501b10ac2cad97e0babf941d8e9c7719/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f66353739346338342d363035362d343633662d613937302d393364353837363263623232/) 最後にコントロール端末でアニメーションGIFを生成します。 ![キャプションを入力できます](https://camo.elchika.com/b9d095e65e6a8848d07d7896c356e22d93be3338/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f34366433323834322d393631382d346237632d393332302d363934383366376235643332/) ==コントロール用の端末はM5Stackでも作ってみましたが、少し離れると通信が安定しないことがあったり、GIFの生成が難しかったので、最終的にはノートPCで確認しました。== # 各施策の実施 ## 治具作成 Fusion360を使用して図面を作成し、3Dプリンタで出力しました。 ![M5Cameraと接続するためのパーツ](https://camo.elchika.com/1227cb18a795f336d989426cf31ac7a26954de7f/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f39643832383734632d613637382d346537382d383539642d633730346431376664323338/) ![キャプションを入力できます](https://camo.elchika.com/3d668a55e50cb798bb920a02058143d818fc9842/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f33396338663362332d666262652d346163392d386561622d343130663536373863373337/) カメラ同士の間隔は 5cmとしました。 M5Cameraと接続するためのパーツを3個作り、直線状に並べ、接着剤で接着します。 ![GoPro 3wayと接続するためのパーツ](https://camo.elchika.com/e8beb2ca5cb546d16994ff8572df2e86a0c17e30/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f63336631393133332d303837632d346539652d613439612d653562366264333265316132/) GoPro 3wayの治具も接着します。 ![3Dプリンタで作成した治具](https://camo.elchika.com/4b4922888c4798195989d8cdd5cc439854d90d17/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f30343231366333332d616533332d346233662d383730632d336265313030366566393334/) M5Cameraをセットすると、以下のようになります。 ![M5Cameraをセットしたところ](https://camo.elchika.com/45cb68cd041943ed8f12aa73b5f8de83633d30dc/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f61313534313537662d306231642d346138362d383665612d643231626232336231323034/) ## モバイルバッテリーから電源供給 M5Camera のUSB端子に電源供給するために、モバイルバッテリーを3台用意します。 ![3台のM5Cameraに電源供給](https://camo.elchika.com/dc942d84a93d778a45644da215f30308519568d5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f38363162376663322d633635642d343333302d623565362d616630626630343230616431/) が、実際に試してみるとさすがにこれは扱いづらいです。バッテリーはできれば1台にしたい。 そこで、モバイルバッテリー1台から、M5Camera 3台に電源供給できるように配線をしました。 ![モバイルバッテリー1台から、M5Camera 3台に電源供給](https://camo.elchika.com/850e8b5431ec0d257637abd84872c3d0b647d9c1/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f38376130353831372d366432342d346664632d383965662d386431626635363761666364/) ## 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ブロードキャストで同時撮影 コントロール端末からUDPパケットを送信し、宛先を255.255.255.255とすることでブロードキャストします。 ### コントロール端末側コード C言語で以下のコードを作成しました。 ```send_bcast.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 ``` ### 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を渡します。 ![キャプションを入力できます](https://camo.elchika.com/f877e62ce0300e6f8de38c5f4c7e732c6ab2fdcb/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39366135666561352d623934662d343738642d613965622d6534666637656338393331312f65393562626661372d303765332d343936372d616466642d383164396135336165626531/) ## コントロール端末側の撮影スクリプト 以下のスクリプトを実行します。 ==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 | | ... | 以下、繰り返し | 以下のスクリプトを実行します。 ``` 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にするとガタガタしていたため、画像補正(回転、位置)を行った。 1. GIFを生成する際の回転補正は、カメラの個体差のため一回決めればよかったが、位置補正は毎回調整する必要があった。調整用の用紙を用意して、それを撮影して補正値を決めれば良かったかもしれない。 1. M5Camera 3個でWigglegramを作成したが、5個, 10個と数を増やす事は理論的には可能。しかし治具を作るのが大変だったため、あきらめた。 1. 3つの写真でアニメーションを作るとカクカクしてしまうため、モーフィングという機能を使い、補間した。 1. 背景が大きく動くと酔うため、背景は遠目にするか、真っ白な壁などシンプルな背景を選んだ方がよい。