WEBカメラで撮影してJPEGファイルにするプログラム(前編)

前回、Armadillo-IoT G3Lの4G-LTE通信性能を評価する過程で、通信よりも画像撮影の所要時間が長すぎるという課題が生じました。

これを、プログラミングで解決したいと思います。

 

プログラミングで前回の問題を解決

前回、既存のアプリを組み合わせて、USBカメラで撮影した画像をサーバにアップするところまで成功はしたものの、撮影処理やファイル保存処理時間が長いため、通信速度の評価には適さない、という結果に終わりました。

そこで、今回は既存のアプリを使うのではなく、自分でプログラミングして撮影からサーバへのアップを一気に実行してしまおう、というわけです。

結果から言うと、以下の様になりました。

条件 : 1280×1024の静止画を連続撮影し、JPEGファイルにしてサーバにアップする。

光回線の場合 : 1枚当たり平均約1秒。

4G-LTE回線の場合 : 1枚当たり平均約2.5秒。(最短1.7秒、最長9秒)

内訳 :

起動からキャプチャ開始までに1.5秒
キャプチャ処理は、1枚当たり200ms
JPEGファイル化は、1枚当たり170ms
残りがサーバへのアップロード時間

前回、アプリの組み合わせで試した結果は、1枚当たり約6秒だったので、とりあえず半分にはなりましたね。
(更なる改善により、この半分くらいにできるかもしれません。)

これなら、工場や農地の監視、観察対象の記録、ちょっとしたTV電話などには使えそうです。

 

Armadillo-IoT G3Lから4G-LTEで本稿と同じサーバ(https://team-ebi.com/test)に cam000.jpg ~ cam199.jpg というファイル名でアップロードしました。

しばらくそのままにしておきますが、後々消してしまうかもしれません。
また、別のファイルに置き換えてしまうかもしれませんので、ここに101枚目と102枚目だけ置いておきます。

1枚目 2枚目

 

 

問題解決への道

それでは、この結果までの道のりを辿ってみましょう。
(ソースはこちら:ソース倉庫

前回の結果から、以下の予想が立ったため、それらを回避すれば余分な処理時間を省略できるのではないか、と考えました。

1.カメラを用いた撮影において、撮影を開始してから画像を取得するまでの時間が予想よりも長すぎる。撮影を開始するための事前処理やピントを合わせるためのフォーカスに時間がかかっているだけではないか?

2.画像を取得してから、ファイルをサーバに転送開始するまでに少し間がある。画像をファイルとして保存してから、そのファイルをサーバに転送しているので、ファイルをディスクに書き出し終わるまでの時間が障害になっているのでは?

3.あるいは、サーバへのログインに時間がかかっているのではないか?

まず、3のログイン時間についてです。

具体的には、回線からインターネットへの接続やサーバへの接続、セキュリティ認証等に要する時間です。

光回線で1秒くらい、4G回線で2秒くらい待たされ、その時々により、数秒~10秒近く待たされる時もあります。

そこで、接続とファイルのアップロードを行った後、切断せずに接続を保持し、次のアップロードをスムーズに実行すれば、改善できそうです。

しかし、ちょっとサーバ側の手間がかかりそうです。
これは次回の課題にしましょう。

次に、2のディスクへの書き出し時間についてです。

後述のプログラミングとデバッグを進める内に明らかになったことですが、全体の所要時間に比べるとたいして大きな時間ではないことが分かりました。

これは、無理に改善する必要は無い様です。

というわけで、3つ目の撮影開始に関する時間の短縮をメインターゲットに据えましょう。

 

課題:カメラ撮影処理時間の短縮

WEB等でライブラリ等を調べてみるとすぐ見つかります。
UVCドライバで動作しているWEBカメラは、「Video for Linux API version 2 (V4L2 API)」というライブラリ(API)で制御できます。

ドキュメントは下記サイトの「 Part I – Video for Linux API 」にあります。
https://linuxtv.org/downloads/v4l-dvb-apis/index.html

英文ですし、これだけで理解するのは難しいので、先人の解説やプログラムを参考にし、情報が足りないと思ったときにサイトを参照する、という方法が良いでしょう。

USB接続すると、ドライバにより /dev/video0 というファイルが作成されます。(すでに0が使用されている等の都合により/dev/video1などになる場合もあります)

アプリ等のプログラムは、このファイルを通して、カメラに対する命令を発したり、カメラからの画像を受け取ったりします。

このあたりの情報は、WEBや経験から得たものですが、これから学ぶ方はWEBだけでも概ねつかめますし、雑誌や本などを読むのも良いでしょう。
そして、以下のプログラムの流れに関する情報は、前述のサイトやWEBから得た知識です。

その為、作成するプログラムの流れは以下のような感じです。

1.デバイスのファイル(/dev/video0)を開く。
2.デバイスやライブラリなどを初期化する。
3.カメラからの画像を受け取るための、受け皿(メモリ領域)を用意する。
4.撮影を開始する。
5.撮影を停止する。
6.メモリを返却(解放)する。
7.デバイスやライブラリなどの終了処理をする。
8.デバイスのファイルを閉じる。

更に、5の前後で、受け取った画像をファイルに書き出したり、サーバにアップロードしたりします。

これらの処理を行うプログラムを作成しながら、要所要所にメッセージを仕込みました。

作成中のプログラムは、ミスや足りない部分が多発するので、いざプログラムを実行した時に、そのプログラムがどこまで正しく動いているのか、外から眺めていても良く分かりません。
そこで、プログラムのいたるところに、「この処理を通過した」とか「この処理を実行した後のこの変数の値はこうだった」といったメッセージを仕込むのです。

こうしておけば、うまく動かなかった時に、「どこまで進んでどこで止まったのか」、うまくすれば「この時の変数の値がおかしいから、この処理がおかしい」とか直ぐにアタリを付けられます。
また、あっさり動作した時も、「本当に完璧か?」「実は途中でまずい動きがあったが、結果的に最後まで動作しただけ」とか、色々と大事な収穫を得ることができます。

実際、このプログラムも、設計中はもっと大量のメッセージを仕込んでいました。
手抜きのメッセージ(後から見ても何のメッセージかわからないものとか)がほとんどですし、かっこ悪いのでほとんど消しましたが。

更に、メッセージに加えてタイムスタンプを仕込みました。

どこの処理で時間がかかり、どこの部分を工夫すれば、課題を解決できるかわからなかったためです。
これについては、WEBやサイトを見てもはっきりしませんでした。

処理時間を知るためには、秒単位ではなく、ms(ミリ秒)単位の仕込みが必要です。
そこで、timestamp()という関数を用意しました。
これは、その関数を実行したその瞬間の時間をms単位で書き出すものです。

そうして、課題解決のポイントが判明しました。

起動からキャプチャ開始までに1.5秒
キャプチャ処理は、1枚当たり200ms
JPEGファイル化は、1枚当たり190ms

ポイントは、撮影を止めないこと(VIDIOC_STREAMOFF しないこと)です。
撮影を止めて、再開(VIDIOC_STREAMON)しようとしてしまうと、また撮影開始までに待たされてしまうのです。
撮影を止めなくても、画像の受け皿であるバッファを撮影処理(キュー)から切り離し、ファイルに落とした後に戻せば、継続して撮影画像を得ることができました。

また、JPEGファイル化の処理を実行している間もキャプチャ処理が進んでいるので、2枚目以降のキャプチャ処理時間は、単純な足し算(200ms+190ms)よりも短い時間(約330ms)で実現します。

JPEGファイル化や転送処理を行っている間もキャプチャを継続する様にしたので、処理中にキャプチャ用のバッファ(buf_count)が一杯になってしまう可能性があります。
そうなるとキャプチャは一時停止し、処理が終わってバッファが解放されると、再びキャプチャが開始され、1枚キャプチャが終わるまで待たされます。
つまり、その際の処理時間は約330msではなく、単純な足し算(200ms+190ms)と同じくらいになります。

このプログラムにより、撮影に約2秒必要としていた所要時間が、約0.3秒になりました。

 

こうして取得した画像データを、JPEGファイルにします。
ちょっと長くなるので、続きは別ページにします。

 

プログラムのオプション

このプログラムの使い方を説明しておきましょう。

適当なディレクトリを作り、そこにソース(ソース倉庫)を置いて gcc -o cam cam.c -ljpeg と入力し、コンパイルします。

すると、camという実行ファイルが生成されますので ./cam /dev/video0 と入力して実行することができます。

実行すると、撮影された画像が 000.jpg ~ 002.jpg に保存されます。

以下のオプションがあります。

-c 撮影枚数を指定(デフォルトは3枚)
-x 撮影およびJPEG画像サイズを指定(画像の幅)
-y 撮影およびJPEG画像サイズを指定(画像の高さ)
-q JPEG圧縮率を指定(デフォルトは90)
-o 出力ファイル名を指定
-s JPEGファイル生成完了時に実行するコマンドやスクリプトを指定

-sは次のように使用します。

send.sh に scp $1 ebi:www/wp/test という様な、サーバに送信するコマンドを書いてから、

./cam /dev/video0 -o /var/www/html/cam -c200 -s send.sh

の様に実行すると、
200枚の撮影画像 cam000.jpg ~ cam199.jpg を /var/www/html/というフォルダに置きながら、サーバにアップロードします。

 

つづき:WEBカメラで撮影してJPEGファイルにするプログラム(後編)