初出:日本ロボット学会誌,2013 年 31 巻 3 号 p. 244-248
本ページでは,これまでに日本ロボット学会誌に掲載された解説記事の中から注目度の高い記事をHTML形式で紹介しています.HTMLへの変換時に著者紹介,キーワードなど一部の情報は省略しています.また,レイアウトはオリジナル記事とは異なります.
PDF形式のオリジナル記事はこちらでご覧になれます.引用する際は,本Webページではなくオリジナル記事を引用してください.
【事例紹介】
高速ビジョンのための OpenCV の活用
鏡慎吾
1 はじめに
画像処理・コンピュータビジョンの研究において OpenCV [1] はなくてはならない標準ライブラリとしての地位を確立している. 複数のプラットフォームに対応し, 基礎的なものから最新のものまで多くのアルゴリズムが網羅的に部品化されていることが人気の理由であるが, それと同時に, カメラ入力, ファイル入出力, 簡素ではあるが手軽に使えるグラフィカルユーザインタフェース(GUI)が一通り揃っていることも普及の大きな要因になっていると思われる.
一方, ロボット制御などのへ応用には, 数百~数千フレーム毎秒(frames per second, fps)のフレームレートで視覚情報を得る高速ビジョンが有用であることが知られている[2]. 初期の研究においては特殊な専用ハードウェアの開発が必要であったが[3, 4], 最近は高フレームレートのカメラが安価で入手できるようになるとともに PC の性能も大幅に向上していることから, 容易に利用できるようになった.
高速ビジョンのソフトウェア開発は, 画像処理を長くても数ミリ秒, 場合によってはサブミリ秒で完了させる必要がある点で難易度が高いといえるが, ここでも OpenCV は大いに役立つ. OpenCV の画像処理の実装は十分に最適化されているものが多く, うまく組み合わせることで高速処理を実現できる. 一方, OpenCV の GUI 機能を活用するのは難しい. 例えばウィンドウを開いて画像を表示する処理は, 環境にも依存するが数ミリ秒から十数ミリ秒程度の時間を要し, 単純に用いると高フレームレート化の妨げとなる. そのため, GUI をまったく用いずに開発を行ったり, デバッグ時にのみ GUI を用いるといった運用がされることが多いが, 開発・研究の効率の面では望ましくない.
本稿では, OpenCV の使い方に多少の工夫を加えることで, GUI 機能を利用しながら高フレームレート処理を維持する方法を紹介する.4章で述べる実装例の動作の様子を 図1に示す. 平均フレームレート 500 [fps]弱での追跡処理を, 結果をディスプレイに表示しながら実行している. 簡単なアプリケーションであるが, これがノート PC とその内蔵 USB ポートに接続した 7万円台のカメラで実現されていることに注目されたい. 高速ビジョンはもはや一部の研究者のみの道具ではないことがご理解頂けると思う.
本稿で紹介するソースコードは, ウェブサイト[5] にて配布している.
(a) []
(b)[]
(c)[]
(d)[]
図1: 実装例の動作. 約 4 [Hz] で円運動する封筒上の模様を平均 476 [fps] で追跡している. 白い矩形が追跡領域を表し,その内側の白い曲線は最近 32 フレームの追跡領域中心の軌跡である.動画ファイルをウェブサイト[5] で配布している
2 典型的な OpenCV プログラム
議論の出発点とするために, OpenCV を用いた典型的なプログラム例を 図2に示す. 3~5行めでは, カメラから画像キャプチャを行うオブジェクト cap と, 画像オブジェクト image を定義し, “disp” と名づけられた画像表示ウィンドウを準備している. 6~13行めが画像処理のメインループであり, cap から image へ画像を 1 枚読み出した後(何らかの画像処理を行い), ウィンドウ “disp” へのその画像の表示を指示する.
10行めの cv::waitKey() は, 第一義的には引数で指定された時間 [ms] 以上の間キー入力を待つ関数であるが, 実はウィンドウへの画像表示やキー・マウス等のイベントのハンドリングがすべてこの関数により行われる. その意味で, これが OpenCV の GUI 機能の本体であるとすらいえる. 待機時間内にキー入力があればその ASCII コードが, なければ が戻り値となる. これにより, 何らかのキーが押されれば画像処理ループを抜ける.
図2: 典型的な OpenCV プログラム. ヘッダファイルのインクルード部は省略している. 以降のプログラム例でも同様
カメラ画像の読み出しと処理結果の表示が単一のループで直列的に行われているため, 画像取得や画像処理が十分に高速でも, 結果表示により全体が律速される. 前述のように表示機能はタイマ待ちと一体となっているため通常はプロセス切り替えを伴い, 仮に待ち時間として最小値 1 [ms] を指定したとしても, OS のタイマ割り込み周期程度の時間をしばしば要してしまう.
ここで何とか工夫を凝らして cv::waitKey() にかかる時間を削減しようとするのは賢明な方向ではない. たとえ数百 [fps] 以上での画像表示が可能になったとしても, 人間の目には見えないからである. また, 表示内容をリッチにしようとすると描画処理自体に時間がかかることも珍しくない.
3 要求分析と設計
画像取得と画像処理に要求されるレート(例えば数百[fps])と, その結果の表示に要求されるレート(たかだか数十[fps])が異なる以上, それらを別スレッドに分けて独立して走らせ, メッセージパッシングによってデータを受け渡すのが自然な戦略である. 最近は多くの PC がマルチコア CPU を搭載しているため, 効率的な実行が期待できる.
スレッド間のメッセージ交換に際して, 適切な排他制御は必須である. 例えば, 結果表示側のスレッド(以下, 表示スレッド)が画像を読み出している最中に画像取得・処理側のスレッド(以下, 処理スレッド)が破壊することは避けなくてはならない. また, 以下の点を考慮することが重要であり, 一般的な First-In First-Out キュー(FIFO)によるメッセージ伝達は適切でない.
• メッセージコピーや排他制御のオーバヘッドを最小化する.
• 処理スレッドが表示スレッドの都合で妨げられるのを可能な限り避ける.
• 処理スレッドが生成するメッセージは, 表示スレッドがすべて受け取れなくてもよい(受け取れたとしても表示することに意味がない).
スレッド間で受け渡すべきものでサイズの大きいものの代表は画像データであろう.4章の評価環境では, VGA サイズ(画素)のモノクロ 8 ビット画像をコピーするのに 0.1 [ms] 前後を要する. 高フレームレート処理を考える際は馬鹿にならないオーバヘッドである. このオーバヘッドは, データそのものをコピーする代わりに, データ構造を指すポインタを受け渡すことで除去できる.
ただし, 両スレッド間で直接受け渡すのは避けるべきである. 表示スレッドがメッセージを受け取って描画している間は処理スレッドが待機する必要が生じるし, それを防ぐために描画処理を中断する仕組みを用意するのは煩雑である. したがって, 両者の中間にバッファを設け, 三者間でポインタを受け渡すことにするのがよい.
このように, ポインタの受け渡しによりメッセージ伝達を行う場合, メッセージは送信ごとに毎回ゼロから作り直す必要がある. 一方, 例えば対象追跡結果を軌跡として画像上に表示したい場合のように, 前回送信したメッセージに追記する形でメッセージを作成したい場合も多い. このような場合は, 一般的な FIFO キューを用いるのが本来であるが, 複数のメッセージ伝達機構が乱立すると, それらの間の同時性を確保するのが煩雑になる. これを簡易に代替するには, メッセージのうち追記型で用いたいデータのみ, 送信側と中間バッファの間でコピーする機構を設ければよい. コピーするデータは最小限に留めるべきである.
4 実装例
前章で述べた機構の実装例を示す. あくまで例として示すものであり, 汎用ライブラリ/フレームワークとしての使用に耐える品質を持つものではないことをご理解頂きたい.
4.1 スレッド間メッセージパッシング機構
スレッド間のメッセージパッシング機構を C++ のクラスとして実装した. スレッディングおよび排他制御には Boost ライブラリ[6] を用いることでプラットフォームへの依存を避けている.
二つのスレッド間を接続する際には, MsgLink クラスのオブジェクトを生成し, それを指すポインタを両スレッドに渡す. MsgLink オブジェクトは, 受け渡されるメッセージオブジェクトを送信準備用, バッファ用, 受信側処理用に三つ生成し, それらへのポインタを保持する. メッセージは, MsgData クラスを継承するサブクラスとしてユーザ (プログラマ)が定義し, 必要なデータメンバを含める.
図3: マルチスレッド化された画像表示プログラム例. 省略されている MsgLink, MsgData の定義についてはウェブサイト [5] を参照されたい
最も簡単なプログラム例として, 図2と同様に画像を表示するだけのプログラムを 図3に示す. MsgData を継承して DispMsg クラスを定義しており, メンバとして画像 1 枚を持たせている.
main() 関数の処理が送信側スレッドである. MsgLink オブジェクトの prepareMsg() メソッドを呼ぶと送信用メッセージへのポインタが返り, その中の各メンバに送信すべきデータをセットできるようになる. 次いで send() メソッドを呼ぶことで, 送信準備用メッセージとバッファ用メッセージそれぞれへのポインタが互いと交換される.
dispThread() が受信側スレッドである. MsgLink オブジェクトの receive() メソッドを呼ぶと, 新しいメッセージがバッファに届いている場合は, バッファ用メッセージと受信側処理用メッセージそれぞれへのポインタが互いと交換され, 新しい受信側処理用メッセージへのポインタが返る. 受信側スレッドはこのメッセージ内のデータをゆっくり処理すればよい.
ポインタを一方向に受け渡す代わりに交換することにより, メッセージオブジェクトは再利用されるため, 再生成のコストを避けることができる.
図 3 では用いていないが, メッセージ内に追記型データが必要な場合は MsgData クラスを継承する際にcopyTo() メソッドを上書きし, 必要なメンバのみコピーする処理として定義する. これは send() メソッド内で呼び出される.
4.2 高速対象追跡プログラムとその動作
高フレームレートカメラを用いた動作の確認と評価のため, 対象追跡プログラムを実装した. プログラムの一部を 図4に示す. Point Grey Research 社の USB 3.0 モノクロームカメラ Flea3(FL3-U3-13Y3M)を Panasonic 社 Let’snote CF-S10(Core i7-2640M,2.8 [GHz] 4 コア, 主記憶 8 [GB])の内蔵 USB 3.0 ポートに接続して用いた. データシート上の公称フレームレートは, 画素で 492 [fps] とされている[7].
ソフトウェア環境は, Microsoft Windows 7 Professional SP1(64 [bit]), Microsoft Visual Studio 2008, OpenCV 2.3.1, Boost 1.47.0 である. カメラの制御には Point Grey Research 社が提供するライブラリ FlyCapture 2.3.3.19 を用いたが, その API 呼び出しは OpenCV の VideoCapture クラスを継承したサブクラス内にラップして, VideoCapture と同様に使えるようにした.
図4: 対象追跡プログラムの例. マウス操作等の GUI イベントに関する部分を削除したほか, 一部のクラス・関数・マクロ定義を省略している. 完全なコードについてはウェブサイト[5] を参照されたい
図5: 高速追跡処理の実行周期
対象追跡は単純なテンプレートマッチングにより行う. OpenCV の cv::matchTemplate() 関数により, テンプレート画像との正規化差分二乗和が最小になる位置を探索領域内から探すことにより追跡を実行する.
処理スレッドから表示スレッドに受け渡すメッセージには, 画像のほかに, 最近 32 フレームの追跡結果(二次元座標)を格納するリングバッファを含めている. リングバッファは追記型で使用するため, copyTo() メソッドでコピーするよう定義している. メッセージを受け取った表示スレッドは, 最新の追跡領域を四角形で表示するとともに, 最近 32 フレームの追跡位置の中心を小円の軌跡で表示する.
また, 表示スレッドから処理スレッドへメッセージを受け渡す MsgLink も設け, 画像上でマウスをクリックするとその位置が処理スレッドに渡されて, そこを中心としてテンプレート画像を設定し直すようにしている.
追跡領域を 画素, 探索領域を前時刻の追跡位置の周囲 画素とした際の, 処理スレッドのループ実行周期を 図5に示す. カメラ制御ライブラリが最新のフレームを確定できた瞬間(51行めの演算子の内部でflycaptureLockLatest() の呼び出しから戻った直後)に cv::getTickCount() により時刻を計測し, 前時刻との差分を取ることにより求めている. 平均実行周期は 2.1 [ms](476 [fps]相当)を実現できているが, [ms] 程度のばらつきがあり, また毎秒 1 回程度のフレーム落ちが発生していることも見て取れる.
5 議論
5.1 リアルタイム性
図5 からも分かるとおり, 本稿で述べた方法では, 処理時間の上限を厳密に保証するハードリアルタイム性は考慮していない. リアルタイム OS 等を用いていないため, そのような保証は不可能だからである.
処理スレッド内の所要時間の内訳を見ると, テンプレートマッチング処理におよそ 1 [ms], カメラ用フレームバッファからOpenCV 用画像変数へのデータのコピーに 0.1 [ms] を要しており, これら以外はほとんど処理時間を費やしていない. すなわち純粋に処理スレッドが消費する時間は, カメラの公称フレーム時間である 2 [ms] に比べて十分に短い. したがって, 実行周期のばらつきは処理時間がばらつくことによるものではなく, OS のスケジューリングの影響と考えるのが妥当である. なお, 表示スレッドを停止しても同様に毎秒 1 回程度のフレーム落ちが観測されており, マルチスレッド化の悪影響ではないと考えられる.
このように実行時間の保証がない枠組みでも, 以下の理由により一定の利用価値はあると考えられる. まず, 高速ビジョンは必ずしもハードリアルタイムなアプリケーションにのみ有用というわけではない. 例えば筆者らは, 高フレームレートの画像入力に基づいて生成・加工されたビデオレートの視覚刺激をユーザに与えるシステムをいくつか開発している[8, 9].
次に, ロボット制御のようなハードリアルタイム制約を持つシステムでも, 視覚処理部はソフトリアルタイムとすることが許容される場合, あるいはむしろ妥当な場合が存在する. 視覚を用いるハードリアルタイムタスクの代表例であるビジュアルサーボにおいても, 古くからそのような指摘がなされている [10].
誤解を恐れずに述べると, そもそも視覚処理は照明条件や遮蔽, 見えの変化などの影響を受けやすい本質的に不確実なタスクである. 特に構造化されていない環境においては, 一定時間おきに必ず対象追跡結果等が返ってくるなどと期待するのは危険である. その不確実性を前提としてシステム全体が設計されていることが望ましい.
5.2 オーバヘッド再考
画像データのコピーに要するオーバヘッドは完全になくせたわけではなく, カメラ用フレームバッファからのコピーが 1 回行われている. 今回使用したカメラでは, フレームバッファ内のデータはすでに OpenCV で扱える形式になっているため, そのままコピーをせずに探索領域のみ処理し, 表示スレッドにフレームバッファへのポインタを直接渡すといったことが一応は可能なはずである. ただし, カメラの動作や制御 API に依存する慎重な手順が必要となる.
フレームバッファ内の画像がそのまま扱える形式でない場合 はもう少し深刻である. 例えば単板式カラーカメラの場合は色変換処理が必要であり, 単なるコピーよりも長い時間がかかる. カメラのハードウェア側が変換機能を持つ場合もあるが, 例えば RGB 画像であれば転送は 3 倍の帯域を要するため, フレームレートは低下する. 根本的な解決策は, 変換処理の高速化を進めることであると思われるが, 簡単にできるとは限らない. 注目領域以外はカラー画像表示をあきらめるなどの割り切りも必要かも知れない.
5.3 その他の利用法
本稿では, OpenCV の GUI 機能を別スレッドに分離する際のメッセージ交換を扱ったが, ほかにも様々な利用が可能である.
例えば三次元視覚への応用では OpenCV と OpenGL を組み合わせて使いたくなる. プラットフォームに依存せずに簡単に OpenGL を扱えるウィンドウツールキットとして GLUT(あるいはGLUI)は便利だが, イベントループに入るとプログラムに制御が戻ってこなくなる(これは, 手軽に使える GUI ツールキットではよくある設計である)ため, OpenCV のカメラ入力や GUI 機能との併用は一筋縄ではいかない. 入門書等ではしばしば, ウィンドウ描画コールバック関数の中でカメラから画像を読み出す方法が紹介されているが, 安定したフレームレートを得るのは難しい. GLUT の初期化とイベントループ開始を別スレッドとし, OpenCV による画像処理スレッドからのメッセージを GLUT コールバック関数が受け取るようにすることで, 簡単さとフレームレートを保ったまま併用が可能となる.
その他の用途への転用を考える際に, 本稿で紹介した方法がそもそも画像処理スレッド と表示スレッドの非対称性に着目して設計されている点には留意されたい. 特に,メッセージが失われてもよいことを前提としている点には要注意である.4章では, 表示スレッドから処理スレッドへの GUI イベント通知も同じ仕組みで行っているが, 便宜上のものである. 今回の例では実際上問題にならないが, 通知した GUI イベントが失われる可能性がある. 本来は, 一般的な FIFO キューによる通知を行うべきである.
6 おわりに
本稿では, 高フレームレートカメラを用いたソフトリアルタイム処理の開発において OpenCV を活用するための工夫について述べた.「高速ビジョンのための」と銘打ってはいるが, 通常のビデオレートのカメラを用いる際にも時間のかかる描画処理を別スレッドに追い出すことは性能向上につながると期待できる.
OpenCV はコンピュータビジョン分野の事実上の標準ライブラリであるとともに, 現在進行形で進化し続けるソフトウェアでもある. 本稿で紹介した実装例はかなりゆるめの出来であるが, OpenCV 自体の変化に柔軟に対応する余地を残すには, この程度がよいのではないかと考えている.
References
[1] http://opencv.org/
[2] 石川:“超高速ビジョンの展望”, 日本ロボット学会誌, vol.23, no.3, pp.274–277, 2005.
[3] 中坊, 石井, 石川:“超並列・超高速ビジョンを用いた1 [ms]ターゲットトラッキングシステム”, 日本ロボット学会誌, vol.18, no.3, pp.417–421, 1997.
[4] 鏡, 小室, 渡辺, 石川:“ビジョンチップを用いた実時間視覚処理システム VCS-IV”, 電子情報通信学会論文誌(D-I), vol.J88-D-I, no.2, pp.134–142, 2005.
[5] http://code.google.com/p/highspeed-cam-opencv/
[6] http://www.boost.org/
[7] Point Grey Research Inc.: Flea3 FL3-U3 USB 3.0 Digital Camera Technical Reference Manual, version 3.2, 2012.
[8] T. Orikasa, S. Kagami and K. Hashimoto: “Time-Domain Augmented Reality Based on Locally Adaptive Video Sampling,” Science and Technology Proc. IEEE Int’l Symp. on Mixed and Augmented Reality (ISMAR2010), pp.261–262, 2010.
[9] 荒井, 鏡, 橋本:“高速ビジョンシステムを用いたリアルタイム運動予測表示—予測軌道を表示するエアホッケーシステム—”, 日本機械学会ロボティクスメカトロニクス講演会(ROBOMEC’12)講演論文集, 2012.
[10] 橋本, 則次:“RT-Linuxに基づく視覚サーボシステムの開発”, 日本ロボット学会誌, vol.17, no.5, pp.685–688, 1999.
鏡慎吾(Shingo Kagami)
1998年東京大学工学部計数工学科卒業. 2003年同大学大学院工学系研究科博士課程修了. 博士(工学). 科学技術振興事業団研究員, 東京大学助手, 東北大学講師を経て, 2007年より東北大学大学院情報科学研究科准教授. 高速ビジョンを中心とする実時間センサ情報処理の研究に従事.