Javaは本来リアルタイム処理のことをあまり意識したプログラミング環境ではありません.このためJavaからビデオカメラを利用してリアルタイムに画像処理するためには少し留意点があります.まずハードウェアに依存したカメラの処理を記述するか,またリアルタイム処理を記述する上でJavaで注意しなければならないのはガベージコレクションの抑制です.
Javaからビデオカメラ(USBカメラ/IEEE1394カメラ)にアクセスするために用意された手段は大きくふたつにわかれます.
JMFはSun Microsystemsから提供されており,v4l(Video for Linux1)対応のUSBカメラなら利用できます.Javaからカメラを利用する一番正当な方法といえます.しかしながら2003年に2.1.1eがリリースされて以来更新が止まっています.いまもJDKの関連ダウンロードにリンクがあるものの,おそらく今後のメンテナンスも期待できないものと思われます.将来入手可能なUSBカメラはv4l2(Video for Linux2)対応のカメラとなることが予想され,JMFではそれらの新型カメラは利用不能となります.(※カメラのv4l対応,v4l2対応についてはカメラのページを参照してください.)
一方のJNIを用いた方法は,C言語でカメラのインターフェース部分を自作することになります.つまりJMFに代わってハードウェアに依存する部分をC言語で直接v4l/v4l2またはlibdcなどのIEEE1394ドライバのAPIを利用して自作し,カメラから画像を取得してJavaのデータ配列に変換し,Javaプログラムに引き渡す機能を実現します.Javaプログラム上からは,nativeキーワードを使用したnativeメソッドを呼び出す形式です.
田村研では,現実問題として強引であっても自分達だけでどうとでもなるようにJNIを利用した方法でカメラを利用することとし,次に自作したインターフェースを公開します(現在は学内限定).※ここでいうインターフェースとは一般的な意味でのもので,Java言語におけるinterfaceキーワードのことではありません.
ネットワークカメラへのアクセスは,ハードウェアを直接接続するわけではなく,通常のTCP/IPによる通信プログラムを書くだけで済むため,次のネットワークカメラ用のインターフェースを利用するだけで可能です.
カメラにアクセスするためのライブラリを学内限定で公開します.USBカメラ・IEEE1394カメラ(v4l,v4l2,ステレオカメラ)の場合にはJNIライブラリとインタフェースのクラスをダウンロードしてください.ネットワークカメラの場合にはインタフェースだけです.Javaプログラムからの利用方法はサンプルプログラムを参照してください.このサンプルプログラムを実行するためには,まず次のライブラリとインターフェースクラスをダウンロードします.
カメラ用のライブラリです.ライブラリパス(下記参照)が通った場所に格納しておいてください.
インターフェース用のJavaクラスです.プロジェクトの場所に保存しておきます.
上記からダウンロードした後,ライブラリの設定が必要です.Linuxの場合,ライブラリパスに指定されたディレクトリにライブラリに保存しないと実行時にJavaVMがライブラリを見付けてくれません.しかし,初期設定でライブラリパスに指定済みのディレクトリはシステムのライブラリを格納してある場所ですので,通常は,新規ディレクトリを作成してライブラリパスに追加することになります.
よく用いられるディレクトリ名は,次のふたつです.
そのマシンを利用している他のユーザもライブラリを使用する場合には「/usr/local/lib」を用い,もしも自分だけが利用できればよい場合には「~/lib」を利用した方がよいでしょう.「~/lib」は,自分のホームディレクトリの中に「lib」というディレクトリを作成するという意味です.いずれかのディレクトリを新規作成します./usr/local/libの場合にはrootにsuしてからでないと作成する権限がないので注意してください.
su mkdir /usr/local/lib exit
または
mkdir ~/lib
その上で,初期設定ファイルにライブラリパスを追加します.Vineなどウィンドウシステムとしてgnomeが導入されているディストリビューションの場合には次のようにして初期設定ファイルを編集できます.geditはgnomeの標準テキストエディタですので,それ以外の使い慣れているテキストエディタで.bash_profileを編集しても構いません.
gedit ~/.bash_profile (テキストエディタgeditで.bash_profileを編集する)
そして次の行を追加します.それぞれライブラリパスLD_LIBRARY_PATHに/usr/local/libまたは~/libを追加する意味があります.
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/: export LD_LIBRARY_PATH
または
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/lib/: export LD_LIBRARY_PATH
保存したらログインしなおすことでライブラリパスが更新されます.また,ダウンロードしたライブラリをこのディレクトリに移しておくのを忘れないようにしてください.
インターフェースクラスの方は,通常のJavaソースファイルと同じ場所に保存して,同じように利用してください.
Javaのメモリ管理では,メモリを使い捨てにする形が前提です.メモリが足りなくなったときには,ガベージコレクションが発生して捨てられたメモリ領域をJavaVMが拾い集めて再利用します.この割り切った利用方法のおかげで,プログラムする側がメモリの使いまわしを気にしなくてもよくなり,プログラムを単純に書くことができます.
しかし,リアルタイム処理を考えた場合には,ガベージコレクションの発生は致命的です.一度ガベージコレクションが発生すると,最低でも数100ミリ秒から数秒に渡ってプログラムの実行が中断され,ガベージコレクションだけが処理されることになります.このような中断は,リアルタイム処理によってはとても許容できないものです.
そこで,次の対策を徹底してください.
根本的な解決方法ではありませんが,もっともてっとり早く実効性のある方法です.JavaVMが利用できるメモリ量の設定を増やすことで,そもそもガベージコレクションを発生する頻度を下げることができます.
JavaVMには次のようにメモリサイズに関するオプションやそのほか高速化のためのオプションが存在し,適切な値を設定することができます.
-Xss128M -Xms512M -Xmx512M -server -XX:+UseCompilerSafepoints -XX:+UseOnStackReplacement
このオプションはjavaコマンド実行時のオプション指定か,Eclipse上からの実行の場合には
[ウィンドウ]->[設定]->[Java]->[インストール済みのJRE] 選択して[編集] [デフォルトVM引数]へ設定
メモリサイズの指定値は,マシンのメモリ容量次第で調整してください.ただしXmsとXmxは同じサイズを指定しておくと,ヒープエリア管理のオーバヘッドを削減できる可能性があるため,若干高速化に寄与するかもしれません.そのほか高速化のためのオプションでは.VMの起動が遅くなりますが,ループ処理を最適化したりすることができます.自分の環境とプログラムの性格を考えて設定しましょう.詳細は各自調べてください.
Javaではメモリの使い捨てが当たり前なため,素直なJavaプログラマが,一般の動画処理プログラムを書くと次のようになるかもしれない.
for(;;){ //無限ループ(カメラからの映像をずっと処理しつづける) 画像オブジェクト = new 画像オブジェクト カメラから画像オブジェクトに読み込み その画像を処理 結果表示 }
しかし,前述のとおりメモリを使いすてにするとガベージコレクションが発生するために速度低下を招く.そのため,次のようにループ内部ではnew演算子を使用しないように書き換えなければいけない.
/* 必要な分だけ画像オブジェクトを用意 */ 画像オブジェクト= new 画像オブジェクト for(;;){ //無限ループ(カメラからの映像をずっと処理しつづける) カメラから画像オブジェクトに読み込み その画像を処理 結果表示 }
画像オブジェクト以外にもnewはループ内で使用しないように気を付ける必要もある.特に盲点のひとつが文字列処理である.例えば結果表示の所で次のようなプログラムが良くある.
/* 必要な分だけ画像オブジェクトを用意 */ 画像オブジェクト= new 画像オブジェクト for(;;){ //無限ループ(カメラからの映像をずっと処理しつづける) カメラから画像オブジェクトに読み込み その画像を処理 int output = 画像処理による何かの計算結果; System.out.println("処理結果は"+output+"でした."); }
このプログラムにはnewがループ内に存在しないものの,最後の部分で文字列の「足し算」が行われている.実は文字列(String)に対してこのような操作が行われると,実行時に足し算結果の文字列の格納場所として新規文字列がnewされて使用される.このためループ一回分のメモリ使用量は微々たるものではあるが,ループのたびにメモリが消費されることになる.これを避けるためには,次のようにStringBufferを使用すればよい.
/* 必要な分だけ画像オブジェクトを用意 */ 画像オブジェクト= new 画像オブジェクト final int LENGTH = 100; //文字列の最大長 StringBuffer str = new SringBuffer(LENGTH); //文字列のメモリをあらかじめ確保 for(;;){ //無限ループ(カメラからの映像をずっと処理しつづける) カメラから画像オブジェクトに読み込み その画像を処理 int output = 画像処理による何かの計算結果; str.delete(0,LENGTH); //確保してあったメモリを再利用するため,格納されている文字列を空に戻す str.append("処理結果は"); //空のメモリに書き込む str.append(output); str.append("でした"); System.out.println(str); //書き込んだstrを表示する }
その他,Javaが提供しているクラス・メソッドは,内部でメモリを使い捨てにするようなものが多く存在するため,注意すること.その意味では,Javaを使用している以上はループ内部でのnewを根絶できないかもしれないが,可能な限り排除することを意識してプログラムを書く必要がある.
ループに関しては,次のような一般的な注意も存在する.例えば次のようなプログラムがあったとする.
for(int k=0; kの範囲; k++){ kとは関係ない処理 kを使用した処理 }
これは次のように書き換えると大幅に高速化できる.「kとは関係ない処理」がループ内にあると,ループ内で毎回同じ計算することになり,無駄なためである.
kとは関係ない処理 for(int k=0; kの範囲; k++){ kを使用した処理 }