はじめに
Linuxで実時間画像処理を行おうという試みが数年前から欧州を中心に盛り上がっています。いくつかのプロジェクトが進められていますが、今回はその中のひとつ、EffecTVを取り上げます。EffecTVでは様々なイフェクト(映像処理)をプラグイン形式で組み込むことができるようになっています。本稿では、簡単なプラグインを作成し、実時間画像処理の面白さを紹介します。
対象読者
実時間画像処理やデジタルビデオイフェクトに興味を持ち、ビデオアートに関する自らのアイデアを実現してみたい方。
必要な環境
EffecTVはLinuxで動作します(現バージョンのEffecTVは、インテルアーキテクチャ(IA-32)とPlayStation2 Linuxのふたつのプラットフォームをサポートしています)。なお、リアルタイムの画像入力を必要としますので、Video4Linuxをサポートした環境と、対応するカメラも必要です。なお最新のバージョンでは、IEEE1394カメラからの入力を試験的にサポートするためのパッチも添付されています。
プログラムはC言語で書かれており、gccでコンパイルします。画像の表示やイベントハンドリングにSDLを利用しているので、バージョン1.1.7以降のSDLライブラリが必要です。またイフェクトを高速に動作させるには、NASMをインストールしておくとよいでしょう。
なお今回紹介するイフェクトプラグインを検証するには、EffecTV本体が必要です。EffecTVのソースコードは、EffecTVのウェブサイトからダウンロードして下さい。
EffecTVとは
EffecTVは、カメラから入力された画像にリアルタイムで動画像処理を加える「リアルタイム・ビデオ・エフェクタ」のアプリケーションです。入力画像に特殊効果を加え、不思議な映像を作りだすことができます。
様々なイフェクト
本稿執筆時点(2005年10月)での最新版である「effectv-0.3.10」に含まれるイフェクトは、全部で41個です(次表)。
QuarkTV | FireTV | BurningTV | RadioacTV | StreakTV |
BaltanTV | 1DTV | DotTV | MosaicTV | PuzzleTV |
PredatorTV | SpiralTV | SimuraTV | EdgeTV | ShagadelicTV |
NoiseTV | AgingTV | TransformTV | LifeTV | SparkTV |
WarpTV | HolographicTV | CycleTV | RippleTV | DiceTV |
VertigoTV | DeinterlaceTV | NervousTV | RndmTV | RevTV |
RandomDotStereoTV | LensTV | DiffTV | BrokenTV | WarholTV |
MatrixTV | PUPTV | ChameleonTV | OpTV | NervousHalf |
SloFastTV |
代表的なイフェクトをいくつか紹介します。
時間的処理を効果的に利用したイフェクト(QuarkTV)
動いている部分が散り散りに表示されるイフェクトです。過去のフレーム数枚から画素をランダムに選択して表示しているだけで面白い効果を挙げています。
過去のフレームの情報を参照し、時間的処理の効果を強く利用しているイフェクトの例といえます。このような処理は、静止画に対する画像処理では実現できないもので、動画像処理の好例といえるでしょう。
色・座標の変換が中心となっているイフェクト(PuzzleTV)
いわゆる15パズルふうのパズルを、映像ベースで実現します。キーボードでパズルを操作することができますが、対象が動くので、実はなかなか難しい。空間的処理であっても連続的な処理において変換処理のパラメータを変えることで結果が刻々と変わる点が、動画像処理の面白さになっています。
背景差分によるイフェクト(BurningTV)
差分の領域を対象として燃え上がる炎を書き加えています。入力画像に重ねて表示しているので、あたかも映像中の物体が燃え上がるような効果を与えています。なお同じアルゴリズムで炎を描画するだけのFireTVもあります。
背景差分系のイフェクトでは、動作中にスペースキーを押すことで、その瞬間の画像を背景画像として取り込むことができるようになっています。
その他のイフェクト
EffecTVは表現上の面白さを追求しているので、その他にもユニークな効果を表現するイフェクトが数多く揃っています。
プラグインを作ろう
それでは実際にEffecTVのイフェクトプラグインを作ってみましょう。
EffecTVのセットアップ
まずはじめにEffecTVの開発環境を作らなくてはいけません。EffecTVのウェブサイトからダウンロードしたソースコード一式を適当なディレクトリに展開します。展開すると、「effectv-(バージョン番号)」というディレクトリができるので、そこに移動して下さい。
環境に合わせた調整が必要な場合は、そのディレクトリに含まれる「config.mk」ファイルの内容を修正します。修正項目はさほど多くありません。変更箇所は、インストール先のディレクトリをどうするか、MMXを使うか否か、NASMを使うか否かなどで、通常はそのままで構いません。
問題がなければ、make; make install
でEffecTVが作成され、インストールされます。標準では「/usr/local/bin/effectv」としてインストールされるので、起動してみましょう。入力画像のウィンドウが表示され、起動した端末に動作するイフェクトの一覧が出力されれば成功です。
プラグインの追加
新しいイフェクトプラグインの追加について説明します。
新しいイフェクトの名前は、HintDePintTVとします。このイフェクトは、粗いモザイクが徐々に細かくなっていくというサイクルを繰り返すイフェクトです。あるTV番組に因んで私が以前に作成したものですが、日本人にしか分からないという理由でソースツリーへの追加は却下されてしまいました。
HintDePintTVは、ファイル「hintdepint.c」に記述するものとします。このファイル名は既存のものとかぶらないように気をつけましょう。なお、雛型として配布されている「dumb.c」をコピーして、書き変えながら新しいイフェクトを作成していくのがよいでしょう。
$ cp dumb.c gray.c
最低限必要な項目
プラグインに必要な項目は、下記の5つです。
- イフェクト開始関数
- イフェクト停止関数
- イフェクト描画関数
- イベント処理関数
- イフェクト名
サンプルのHintDePintTVでは、それぞれ次の関数と文字列が相当します。
int hintdepintStart(); int hintdepintStop(); int hintdepintDraw(); int hintdepintEvent(); ... static char *effectname = "HintDePintTV";
これらは、イフェクト登録関数hintdepintRegister()
で、その返値であるeffect
型の構造体に、次のように格納されます。
effect *hintdepintRegister() { effect *entry; entry = (effect *)malloc(sizeof(effect)); if(entry == NULL) return NULL; entry->name = effectname; entry->start = hintdepintStart; entry->stop = hintdepintStop; entry->draw = hintdepintDraw; entry->event = hintdepintEvent; return entry; }
既存ファイルの修正
EffecTVでは、動的リンク形式のプラグインは採用していません。そこで、コンパイル時に静的結合を行う必要があります。修正すべき既存のファイルは、「effects/Makefile」、「main.c」、「effects/effects.h」の3つです。
「effects/Makefile」の修正
追加した「hintdepint.c」をコンパイルするように修正します。例えばEFFECTS = ...
に$(HINTDEPINT)
を追加し、次の行を追加します。
HINTDEPINT = hintdepint.o
「main.c」の修正
イフェクトを登録する関数の配列effectRegistFunc
に、新たに作成したイフェクト登録関数の名前であるhintdepintRegister
を追加します。
「effects/effects.h」の修正
hintdepintRegister
を「main.c」から参照できるよう、次の行を追加します。
extern effectRegistFunc hintdepintRegister;
処理の概要
プラグインのイフェクト処理を解説します。
イフェクトプラグインの開始と終了
EffecTVでは、組み込まれている各種のイフェクトをキーボード制御で切り替えて使うことができます。イフェクトの切り替えのタイミングでは、動作しているイフェクトの終了コードが動いてから、切り替えるイフェクトを開始させるコードが動作します。そのタイミングで呼ばれるプラグイン側の関数が、イフェクト開始関数とイフェクト終了関数です。HintDePintTVの場合は、hintdepintStart()
とhintdepintStop()
がそれぞれ相当します。
プラグインの開始と終了にあたり、HintDePintTVではあまり凝った処理はしていません。
プラグインの開始と停止に合わせたビデオキャプチャの制御と、開始時にパラメータの初期化処理をしているくらいです。
初期化処理
HintDePintTVでは、映像のフレーム数を数えており、変数frameCounts
ごとにモザイクの解像度を変化させています。モザイクの大きさは配列cellsizeTable[]
に格納されていて、配列のインデクスにはcellsizeIndex
を使います。
static int cellsizeTable[] = { 1, 5, 10, 20, 40, 80, 0 };
HintDePintTVの初期化処理では、モザイクの大きさを画面サイズを超えない最大の大きさとし、カウンタをその位置にセットしています。
イフェクトの描画
イフェクト描画関数がプラグインの心臓部です。イフェクト描画のための関数は、ビデオ画像の新しいフレームが確保されると毎回呼び出されます。
イフェクト描画関数の構成
イフェクト描画関数の基本的な構成は、次のようになります。
RGB32 *src, *dest; src = (RGB32 *)video_getaddress(); dest = (RGB32 *)screen_getaddress(); ... (srcのデータを画像処理して結果をdestに格納する) ...
画像データはRGB32
型(その実体は、unsigned int
です)の配列として格納されています。元画像と処理画像を格納するメモリのアドレスはそれぞれvideo_getaddress()
とscreen_getaddress()
で取得します。画像データの幅と高さは、グローバル変数のvideo_width
とvideo_height
で参照できます。
実際のコードは、スクリーンのロックや全画面表示への対応などで若干複雑になっています。ただし、本質的なところは画像処理を行う部分だけですので、既存のプラグインのコードを参考にすれば、新しいプラグインのコードを書くのはそれほど難しくないでしょう。
HintDePintTVイフェクトの中心部分
HintDePintTVにおけるイフェクト処理の中心は、モザイク処理を行うmakeMosaic()
関数です。以下のように非常に単純なコードです。
void makeMosaic(RGB32 *dest, RGB32 *src, int cellsize) { int x, y; for (y = 0; y < video_height; y += cellsize) for (x = 0; x < video_width; x += cellsize) { RGB32 v; int yy, xx; int dx = cellsize; int dy = cellsize; if (cellsize > video_width-x) { dx = video_width-x; } if (cellsize > video_height-y) { dy = video_height-y; } v = src[(y+dy/2)*video_width+(x+dx/2)]; for (yy = y; yy < y + dy; yy++) for (xx = x; xx < x + dx; xx++) { dest[yy*video_width+xx] = v; } } }
モザイクのセルの大きさは、配列を参照してcellsize
として与えています。また、この大きさは、イフェクト描画関数hintdepintDraw()
の中で、フレーム数に応じて変化させています。
なお、このコードでは、リアルタイム性を追求するための工夫が入っています。通常のモザイク処理ではセルの中で画素値の平均をとります。ただしこのコードでは、処理を少しでも高速化するために、セルの中央画素の値を平均値の代わりとして使っています。
まとめ
映像へのモザイク処理を動的に変化させるHintDePintTVを題材に、ビデオイフェクトプログラミングの一例を紹介しました。EffecTVプロジェクトでは開発チームへの参加を歓迎します。面白いイフェクトプラグインを開発した方は、ぜひともプロジェクトチームへご連絡下さい(連絡先は、EffecTVプロジェクトのウェブサイトをご参照下さい)。