ここでは,画像(静止画・動画・カメラ)の扱い方を,実例をとおして解説していきます.下のリンクから画像素材 ofSozai1.zip をダウンロードし,必要に応じて,プロジェクトごとの bin/data フォルダにファイルをコピーして使ってください.
- このページで利用するおもな画像素材:ofSozai1.zip
静止画について,ファイルからの読み込み,画面への表示,ファイルへの保存などを可能にするのが ofImage クラスです.ここでは,この ofImage クラスの使い方を説明します.
画像ファイル(png や jpg など)をそのまま画面に表示します.いつものように,emptyExample をコピーし,dojoExample7 に名前を変更します.その中にある bin > data フォルダに,ofSozai1.zip から earth.jpg をコピーしてください.この JPEG ファイルは 750x750 の大きさをもつ地球の画像です.(bin/data フォルダがない場合は,bin フォルダとその中に data フォルダを新規作成してください.)
画像ファイルを読み込み,ウィンドウに表示するプログラム(ofApp.cpp, ofApp.h)は,およそ以下のようになります.image は ofImage クラスのインスタンス(ゴム印で押したメモリ領域=オブジェクト)です.
class ofApp : public ofBaseApp {
private:
ofImage image;
public:
(... 変更なし ...)
};
void ofApp::setup () { // 600x400, 30 frames/sec ofSetWindowShape(600, 400); ofSetFrameRate(30); // set background ofSetBackgroundColor(0, 80, 0); // load image image.load("earth.jpg"); } void ofApp::update () { } void ofApp::draw () { image.draw(0, 0); } (... 以下変更なし ...)
オリジナルの画像は地球全体が正方形(750x750 ピクセル)の領域に収まっているはずですが,600x400 のウィンドウにはその左上の部分だけが表示されています.そこで,つぎのように ofApp.cpp を変更すれば,600x400 のウィンドウ領域に,元画像の全体が表示されます.image.draw(x, y) の場合は,画像の左上を,現在の座標系の (x, y) に合わせた位置に,画像をその大きさを変えずに表示させますが,image.draw(x, y, w, h) とした場合は,画像の大きさを横幅 w,縦高 h に調整して表示させるわけです.
void ofApp::setup () {
(... 変更なし ...)
}
void ofApp::update () {
}
void ofApp::draw () {
image.draw(0, 0, 600, 400);
}
(... 以下変更なし ...)
全体が表示されましたが,横に伸びた地球になってしまいました.これを補正しつつ,全体を表示するにはどうすればよいでしょうか.たとえば,以下のように変更すれば解決できます.
void ofApp::setup () {
(... 変更なし ...)
}
void ofApp::update () {
}
void ofApp::draw () {
image.draw(100, 0, 400, 400);
}
(... 以下変更なし ...)
また,以下のように,image.setAnchorPercent(px, py) を与えることで,描画するときの「アンカー位置」(デフォルトでは画像左上端)を認知の位置に移動させることができます.image.setAnchorPercent(0.5, 0.5) とすれば画像の中央がアンカー位置となります.image.draw(x, y) は,このアンカー位置が現在の座標系の (x, y) に来るように描画します.image.draw(x, y, w, h) も同様です.(この image.setAnchorPercent() 呼び出しは ofApp::setup() の中に移しても構いません.)
void ofApp::draw () {
image.setAnchorPercent(0.5, 0.5);
image.draw(300, 200, 400, 400);
}
(... 以下変更なし ...)
発展課題1: 単に,左右反転・上下反転だけであれば座標変換は不要です.上の例であれば,image.draw(300, 200, -400, 400) とすれば左右反転となります.
発展課題2: 画像も ofRect() や ofCircle() と同じように,それを描画する座標系を変換することで,平行移動や回転,拡大縮小などが自由にできます.たとえば,つぎのようにすれば,地球を回転できます.image.draw() の縦高・横幅には (400, -400) を与えて,上下反転させた描画としている点に注意してください.
class ofApp : public ofBaseApp {
private:
ofImage image;
float deg;
public:
(... 変更なし ...)
};
void ofApp::setup () { (... 変更なし ...) image.load("earth.jpg"); image.setAnchorPercent(0.5, 0.5); deg = 0.0; } void ofApp::update () { deg += 0.1; } void ofApp::draw () { // setup standard coordinate system ofTranslate(300, 200); ofScale(1, -1); // rotate around the origin ofRotateZ(deg); image.draw(0, 0, 400, -400); } (... 以下変更なし ...)
発展課題3: ウィンドウがリサイズされても追従できるように改良しましょう.プログラムの動作がわかりやすいように背景を深緑色にしてあります.ofGetWindowWidth() や image.getHeight() などの意味は自明ですね.
void ofApp::setup () { (... 変更なし ...) image.load("earth.jpg"); image.setAnchorPercent(0.5, 0.5); } void ofApp::update () { } void ofApp::draw () { // get size float winW = ofGetWindowWidth(), winH = ofGetWindowHeight(), imgW = image.getWidth(), imgH = image.getHeight(); // fit it float ratioW = winW / imgW, ratioH = winH / imgH, ratioMin; if (ratioW < ratioH) ratioMin = ratioW; else ratioMin = ratioH; // draw it image.draw(winW/2, winH/2, ratioMin*imgW, ratioMin*imgH); } (... 以下変更なし ...)
なお,パソコンの画面サイズを知りたい場合は,ofGetScreenWidth(), ofGetScreenHeight() が使えます。(ofGetScreenWidth/Height() と ofGetWindowWidth/Height() は整数を返します。一方,ofImage クラスのメソッドである image.getWidth/Height() は実数 (float) を返します。)
発展課題4: 画像サイズは読み込み時に調べれば,その後は不変です.また,比率(ratioMin)の計算は,ウィンドウをリサイズしたときのみ実施すれば十分ですから,対応するイベントハンドラである ofApp::windowResized() に入れます.このあたりを整理すると,つぎのようなプログラムになります.
class ofApp : public ofBaseApp {
private:
ofImage image;
float winW, winH, imgW, imgH, ratioMin;
public:
(... 変更なし ...)
};
void ofApp::setup () { winW = 600; winH = 400; // 600x400, 30 frames/sec ofSetWindowShape(winW, winH); ofSetFrameRate(30); // set background ofSetBackgroundColor(0, 80, 0); // load image image.load("earth.jpg"); image.setAnchorPercent(0.5, 0.5); imgW = image.getWidth(); imgH = image.getHeight(); } void ofApp::update () { } void ofApp::draw () { // draw it image.draw(winW/2, winH/2, ratioMin*imgW, ratioMin*imgH); } void ofApp::windowResize (int w, int h) { // get size winW = w; winH = h; // fit it float ratioW = winW / imgW, ratioH = winH / imgH; if (ratioW < ratioH) ratioMin = ratioW; else ratioMin = ratioH; } (... 以下変更なし ...)
夜空の画像(back)に星の画像を重ねて描画します.単純に考えると,まず夜空を描画し,その上(次)に,星を描画すればよいでしょう.プログラムを書くと,つぎのようになります.新たに dojoExample8 をつくり,bin/data に画像ファイル sky1.jpg と star.png をコピーしてから動かしてください.
class ofApp : public ofBaseApp {
private:
ofImage sky1, star;
public:
(... 変更なし ...)
};
void ofApp::setup () { // 600x400, 30 frames/sec ofSetWindowShape(600, 400); ofSetFrameRate(30); // black background ofSetBackgroundColor(0, 0, 0); // load image sky1.load("sky1.jpg"); star.load("star.png"); } void ofApp::update () { } void ofApp::draw () { // draw sky sky1.draw(0, 0, 600, 400); // draw star star.draw(300, 100, 100, 100); } (... 以下変更なし ...)
たしかに夜空に星が描かれましたが,残念なことに,星の画像の黒い背景部分まで描かれてしまいました.これを回避するには,アルファブレンディングを行ないます.まず,背景を透明にした星の画像を作ります.これには Gimp や Inkscape といったフリーソフトや,Illustrator のような商用ソフトを使うとよいでしょう.左下は黒背景の star.png,右下は透明背景の starAlpha.png です.
この真上に starAlpha.png を描画すると,こうなります.
上のプログラムで,ofApp::setup() に ofEnableAlphaBlending() を加えて,アルファブレンディング(透明度をもった画像の描画)を可能にし,star.png を starAlpha.png に置き換えます.画像ファイル starAlpha.png を bin/data に入れて,動作を確かめてください.つぎのような美しい画像が得られるはずです.
void ofApp::setup () { // 600x400, 30 frames/sec ofSetWindowShape(600, 400); ofSetFrameRate(30); ofEnableAlphaBlending(); // black background ofSetBackgroundColor(0, 0, 0); // load image sky1.load("sky1.jpg"); star.load("starAlpha.png"); }
発展課題1: この星をゆっくりと明滅させます.いつものように,内部変数 rad と sin() を使います.
class ofApp : public ofBaseApp {
private:
ofImage sky1, star;
float rad;
public:
(... 変更なし ...)
};
void ofApp::setup () { // 600x400, 30 frames/sec ofSetWindowShape(600, 400); ofSetFrameRate(30); ofEnableAlphaBlending(); // black background ofSetBackgroundColor(0, 0, 0); // load image sky1.load("sky1.jpg"); star.load("starAlpha.png"); // init vars rad = 0; } void ofApp::update () { rad += 0.1; } void ofApp::draw () { // draw sky ofSetColor(255, 255, 255, 255); sky1.draw(0, 0, 600, 400); // draw star float alpha = 255 * (0.25 * sin(rad) + 0.75); ofSetColor(255, 255, 255, alpha); star.draw(300, 100, 100, 100); } (... 以下変更なし ...)
ofSetColor(r, g, b) は,画像に対しては照明光を設定します.画像の各ピクセルの色は,この照明光と「乗算」された色として表示されます.ofSetColor(r, g, b, a) のように第4引数としてアルファ値を与えた場合は,さらに不透明度を表わすアルファ値(アルファチャネルのない画像は 255 を想定)についても同様に「乗算」した値によって表示されます.ここでは,アルファを 0.5〜1.0 に変化させて星を描画しています.また背景については,照明光を標準(r, g, b, a) = (255, 255, 255, 255) にリセットして描画するようにしています.
発展課題2: 「流れ星」をつくります.マウスをクリックすると流れるようにしましょう.
class ofApp : public ofBaseApp {
private:
ofImage sky1, star;
float posX, posY;
public:
(... 変更なし ...)
};
void ofApp::setup () { // 600x400, 30 frames/sec ofSetWindowShape(600, 400); ofSetFrameRate(30); // black background ofSetBackgroundColor(0, 0, 0); // load image sky1.load("sky1.jpg"); star.load("starAlpha.png"); // init vars posX = -200; posY = 0; } void ofApp::update () { posX -= 10; posY += 2; } void ofApp::draw () { // draw sky sky1.draw(0, 0, 600, 400); // draw star star.draw(posX, posY, 50, 50); } void mousePressed (int x, int y, int botton) { posX = 600; posY = 50; } (... 以下変更なし ...)
発展課題3: 星は置いといて,夜空を変化させてみます.sky1.jpg は藍色の夜空,sky2.jpg は茜色の夜空です.これを sin(), ofSetColor(r, g, b, a) で実現します.画像ファイル sky2.jpg も bin/data に入れておきます.
class ofApp : public ofBaseApp {
private:
ofImage sky1, sky2, star;
float rad;
public:
(... 変更なし ...)
};
void ofApp::setup () { // 600x400, 30 frames/sec ofSetWindowShape(600, 400); ofSetFrameRate(30); ofEnableAlphaBlending(); // black background ofSetBackgroundColor(0, 0, 0); // load image sky1.load("sky1.jpg"); sky2.load("sky2.jpg"); star.load("starAlpha.png"); // init vars rad = 0; } void ofApp::update () { rad += 0.1; } void ofApp::draw () { // draw sky float val, alpha1, alpha2; val = 0.5 * sin(rad * 0.1) + 0.5; alpha1 = 255 * val alpha2 = 255 * (1.0 - val); ofSetColor(255, 255, 255, alpha1); sky1.draw(0, 0, 600, 400); ofSetColor(255, 255, 255, alpha2); sky2.draw(0, 0, 600, 400); // draw star float alpha = 255 * (0.25 * sin(rad) + 0.75); ofSetColor(255, 255, 255, alpha); star.draw(300, 100, 100, 100); } (... 以下変更なし ...)
画面は2次元ですが,画像データは,最上段の水平ライン(左から右へ)から各段ずつピクセルを連結した1次元配列となります.各ピクセルは,通常のカラー画像であれば,1つのピクセルは R, G, B, に対応する unsigned char 型 (1 Byte = 8 bit : 値の範囲は 0〜255) のデータ3つからなり,画像データは,この3つ組がピクセルの数だけ繰り返された1次元配列となります.アルファチャネル付きのカラー画像は,R, G, B, A の4つ組からなる1次元配列となります.また,この道場では扱いませんが,グレースケール画像であれば unsinged char 型の1次元配列となります.0 は黒,255 は白です.
たとえば 4×3 ピクセルの画像では,グレースケール画像であれば,12 要素(12 Byte)のデータ,カラー画像であれば 36 要素(36 Byte)のデータ,アルファチャネル付きカラー画像であれば 48 要素(48 Byte)のデータとなります.
画像タイプを調べるには,画像オブジェクト image のメソッド image.getImageType() の値を調べればよいです.その値から,グレースケール画像 (OF_IMAGE_GRAYSCALE),カラー画像 (OF_IMAGE_COLOR, RGB 形式),アルファチャネル付きカラー画像 (OF_IMAGE_COLOR_ALPHA, RGBA 形式) のいずれであるかを知ることができます.
つぎのプログラムは,画像タイプをコンソールに表示するものです.dojoExample9 をつくり,bin/data に画像ファイル(star.png, starAlpha.png, sky1.jpg, color.jpg)をコピーして,それぞれの画像ファイルについて,その画像タイプを調べてください.出力はコンソールにでます.Windows の VC++ であれば,プログラムを起動すると,黒い横長長方形の画面が一緒に出ていました.これがコンソールで,プログラムから printf() などで出力した文字列は,ここに表示されます.Mac OS の Xcode の場合は,メニュー > Run > Console を開いてください.
class ofApp : public ofBaseApp {
private:
ofImage image;
int imgW, imgH;
int type;
public:
(... 変更なし ...)
};
void ofApp::setup() { image.load("star.png"); imgW = image.getWidth(); imgH = image.getHeight(); type = image.getImageType(); // setup window ofSetWindowShape(imgW, imgH); ofSetFrameRate(30); ofEnableAlphaBlending(); ofSetBackgroundColor(0, 80, 0); // init var switch (type) { case OF_IMAGE_GRAYSCALE: printf("GRAY %dx%d\n", imgW, imgH); break; case OF_IMAGE_COLOR: printf("RGB %dx%d\n", imgW, imgH); break; case OF_IMAGE_COLOR_ALPHA: printf("RGBA %dx%d\n", imgW, imgH); break; } } void ofApp::update() { } void ofApp::draw() { // draw image image.draw(0, 0); }
コンソール出力は,デバッギング(プログラムが正しく動作するように修正すること)に利用できます.プログラムの動作をトレースするために,適宜 printf() を入れて,メッセージを出力させます.
画像(ofImage クラスのインスタンス)から画像データを取り出すには,getPixels().getData() を使います.まず,getPixels() で ofPixels 型のデータ(幅・高さ・種類・ピクセルデータ)を取り出し,さらにその内部にあるピクセルデータを getData() を取り出しています.getData() が返す値のは,ピクセルの1次元配列(unsigned char の配列)です.あるいは,この配列の先頭要素へのポインタと言い換えてもよいです.
つぎのプログラムは,画像をクリックすることで,その位置の色情報を画面表示するものです.画面に文字を描画するために,ofDrawBitmapString(buf, x, y) を利用しています.buf は char の配列(またはその先頭要素へのポインタ)です.
class ofApp : public ofBaseApp {
private:
ofImage image;
int imgW, imgH;
int type;
char buf[100];
public:
(... 変更なし ...)
};
void ofApp::setup () { image.load("color.png"); imgW = image.getWidth(); imgH = image.getHeight(); type = image.getImageType(); // setup window ofSetWindowShape(imgW, imgH + 40); ofSetFrameRate(30); ofEnableAlphaBlending(); ofSetBackgroundColor(0, 80, 0); // init var switch (type) { case OF_IMAGE_GRAYSCALE: printf("GRAY %dx%d\n", imgW, imgH); break; case OF_IMAGE_COLOR: printf("RGB %dx%d\n", imgW, imgH); break; case OF_IMAGE_COLOR_ALPHA: printf("RGBA %dx%d\n", imgW, imgH); break; } strcpy(buf, "click to check"); } void ofApp::update () { } void ofApp::draw () { // draw image image.draw(0, 0); // print data ofDrawBitmapString(buf, 20, imgH + 20); } void ofApp::mousePressed (int x, int y, int button) { if (y >= imgH) return; int index = y * imgW + x; int valR, valG, valB, valA; switch (type) { case OF_IMAGE_GRAYSCALE: valA = image.getPixels().getData()[index]; sprintf(buf, "GRAY %3d", valA); break; case OF_IMAGE_COLOR: valR = image.getPixels().getData()[index * 3]; valG = image.getPixels().getData()[index * 3 + 1]; valB = image.getPixels().getData()[index * 3 + 2]; sprintf(buf, "RGB %3d %3d %3d", valR, valG, valB); break; case OF_IMAGE_COLOR_ALPHA: valR = image.getPixels().getData()[index * 4]; valG = image.getPixels().getData()[index * 4 + 1]; valB = image.getPixels().getData()[index * 4 + 2]; valA = image.getPixels().getData()[index * 4 + 3]; sprintf(buf, "RGBA %3d %3d %3d %3d", valR, valG, valB, valA); break; } }
発展課題: 上の例では,ofApp::mousePressed() の中で,getPixels().getData() メソッドの呼び出しを何度も繰り返していて,あまり効率的とは言えません.得られるのは配列ですが,これをどこか変数に代入して,繰り返し使えるようにするとよいでしょう.これを可能にするのがポインタです.これを反映したプログラムは,つぎのようになります.
void ofApp::mousePressed (int x, int y, int button) { if (y >= imgH) return; int index = y * imgW + x; int valR, valG, valB, valA; unsigned char *data; data = image.getPixels().getData(); switch (type) { case OF_IMAGE_GRAYSCALE: valA = data[index]; sprintf(buf, "GRAY %3d", valA); break; case OF_IMAGE_COLOR: valR = data[index * 3]; valG = data[index * 3 + 1]; valB = data[index * 3 + 2]; sprintf(buf, "RGB %3d %3d %3d", valR, valG, valB); break; case OF_IMAGE_COLOR_ALPHA: valR = data[index * 4]; valG = data[index * 4 + 1]; valB = data[index * 4 + 2]; valA = data[index * 4 + 3]; sprintf(buf, "RGBA %3d %3d %3d %3d", valR, valG, valB, valA); break; } }
getPixels().getData() メソッドは,unsigned char へのポインタを返します.したがって,unsigned char *data と宣言したポインタ変数 data に代入することができます.ポインタ変数 data は,配列のように添字を使ってその要素にアクセスすることができます.ポインタと配列は互換性があると考えて構いません.
画像データを読み出すことができるなら,もちろん書き換えることも可能です.たとえば,上の例で,何かキーを押したとき,画像の中央部分の色(ピクセル値)を反転させるなら,つぎのようなプログラムになります.画像データを書き換えたときは,update() メソッドを呼び出すことで,その変更が有効になります.
void ofApp::keyPressed (int key) {
unsigned char *data = image.getPixels().getData();
for (int x = imgW / 4; x < imgW * 3 / 4; x++) {
for (int y = imgH / 4; y < imgH * 3 / 4; y++) {
int index = y * imgW + x;
switch (type) {
case OF_IMAGE_GRAYSCALE:
data[index] = 255 - data[index];
break;
case OF_IMAGE_COLOR:
data[index * 3 ] = 255 - data[index * 3 ];
data[index * 3 + 1] = 255 - data[index * 3 + 1];
data[index * 3 + 2] = 255 - data[index * 3 + 2];
break;
case OF_IMAGE_COLOR_ALPHA:
data[index * 4 ] = 255 - data[index * 4 ];
data[index * 4 + 1] = 255 - data[index * 4 + 1];
data[index * 4 + 2] = 255 - data[index * 4 + 2];
// alpha stays unchanged
break;
}
}
}
// you must call update(), when you have changed the data
image.update();
}
ゼロから画像データを作り出すことも可能です.新しく dojoExample10 として以下のプログラムを動かしてみてください.image.allocate(w, h, type) は,横幅 w,縦高 h で画像タイプ type の画像データを新しく生成します.画像データの初期値は不定です.このプログラムでは,画像中央からの距離に応じて sin() 関数を使ったターゲット模様を生成しています.
class ofApp : public BaseApp {
private:
ofImage image;
int imgW, imgH;
public:
(... 変更なし ...)
};
void ofApp::setup () { imgW = 200; imgH = 200; image.allocate(imgW, imgH, OF_IMAGE_GRAYSCALE); // setup window ofSetWindowShape(imgW, imgH); ofSetFrameRate(30); } void ofApp::update () { // generate image unsigned char *data = image.getPixels().getData(); float cx = imgW / 2.0, cy = imgH / 2.0; for (int y = 0; y < imgH; y++) { for (int x = 0; x < imgW; x++) { float dx = x - cx, dy = y - cy; float r = sqrt(dx * dx + dy * dy); int val = 255.0 * (0.5 + 0.5 * sin(r / 10.0)); data[y * imgW + x] = val; } } // you must call update(), when you have changed the data image.update(); } void ofApp::draw(){ image.draw(0, 0); }
発展課題: このターゲット模様を動かしてみましょう.内部変数 rad を用意し,update() の中で少しずつ増加(あるいは減少)させます.この rad を sin() に与える値に加えることで,時間とともに位相をずらしていきます.プログラムを動かして,その視覚的効果を確かめてください.
class ofApp : public ofBaseApp {
private:
ofImage image;
int imgW, imgH;
float rad;
public:
(... 変更なし ...)
};
void ofApp::setup () { (... 変更なし ...) rad = 0.0; } void ofApp::update () { // move forward/backward rad += 0.1; // generate image unsigned char *data = image.getPixels().getData(); float cx = imgW / 2.0, cy = imgH / 2.0; for (int y = 0; y < imgH; y++) { for (int x = 0; x < imgW; x++) { float dx = x - cx, dy = y - cy; float r = sqrt(dx * dx + dy * dy); int val = 255.0 * (0.5 + 0.5 * sin(r / 10.0 + rad)); data[y * imgW + x] = val; } } // you must call update(), when you have changed the data image.update(); } (... 以下変更なし ...)
Windows 上の openFrameworks は動画再生に QuickTime ライブラリを利用しています.Window 上で動作を確認するには,あらかじめ QuickTime をインストール(無料)しておく必要があります.Mac では特別な準備は必要ありません.
動画ファイルの再生には ofVideoPlayer クラスを使います.基本的には静止画と同じ扱いかたでOKです.内部での画像データは OF_IMAGE_COLOR (RGB) 形式となります.注意すべき点は,まず load() メソッドで動画ファイルを読み込み,少し間を置いてから play() メソッドで再生を開始すると,動作が安定するようです.また,ofApp::update() の中では update() メソッドによって新しいフレームを取り込み,ofApp::draw() の中で draw() メソッドを呼び出すようにします.
つぎのプログラムは,動画ファイル keepon.mov を再生するものです.新しく dojoExample11 をつくり,動作を確認してください.動画ファイルは,ofSozai1.zip に入っていますので,bin/data フォルダにコピーしてください.
class ofApp : public ofBaseApp {
private:
ofVideoPlayer video;
public:
(... 変更なし ...)
};
void ofApp::setup () { // setup window ofSetWindowShape(320, 240); ofSetFrameRate(30); // start player video.load("keepon.mov"); video.play(); } void ofApp::update () { video.update(); } void ofApp::draw () { video.draw(0, 0, 320, 240); }
Windows で動画再生がうまく行かないとき:
Windows 上の openFrameworks では,コーデック(動画ファイルから画像に変換するプログラム部品)が足らない場合があるようです.推奨されている「K-Lite コーデック」をインストールしてください.
少しだけ改造して,マウスボタンを押し下げている間だけポーズするようにします.ポーズするには,setPaused(true) メソッドを呼び出します.false を与えればポーズは解除されます.
class ofApp : public ofBaseApp {
private:
ofVideoPlayer video;
bool isStarted;
public:
(... 変更なし ...)
};
void ofApp::setup () { // setup window ofSetWindowShape(320, 240); ofSetFrameRate(30); // start player video.load("keepon.mov"); isStarted = false; } void ofApp::update () { if (isStarted) { video.update(); } } void ofApp::draw () { if (isStarted) { video.draw(0, 0, 320, 240); } } void ofApp::mousePressed (int x, int y, int button) { if (! isStarted) { isStarted = true; video.play(); } else { video.setPaused(false); } } void ofApp::mouseReleased (int x, int y, int button) { if (isStarted) { video.setPaused(true); } }
発展課題: さらに改造して,動画のフレームごとに getPixels().getData() によって画像データを取り出し,画像が尾を引く(エコーのような)効果を与えてみます.つぎのようなプログラムに改変してください.
class ofApp : public ofBaseApp {
private:
ofVideoPlayer video;
bool isStarted;
ofImage image;
int width, height;
public:
(... 変更なし ...)
};
void ofApp::setup () { (... 変更なし ...) isStarted = false; // get video size width = video.getWidth(); height = video.getHeight(); // image buffer image.allocate(width, height, OF_IMAGE_COLOR); unsigned char *data = image.getPixels().getData(); // black it out for (int k = 0; k < width * height * 3; k++) data[k] = 0; image.update(); } void ofApp::update () { if (isStarted) { video.update(); unsigned char *vdata = video.getPixels().getData(), *idata = image.getPixels().getData(); // image = 0.9 * image + 0.1 * video; for (int k = 0; k < width * height * 3; k++) idata[k] = 0.9 * idata[k] + 0.1 * vdata[k]; image.update(); } } void ofApp::draw () { image.draw(0, 0, 320, 240); } (... 以下変更なし ...)
video.getPixels().getData() によって現在の動画フレームを画像データとして取り出すことができます.上の例では,image の画像データと video の画像データを「演算」し,image の画像データを更新しています.
カメラから動画像を取得するには ofVideoGrabber クラスを使います.基本的には静止画や動画ファイルと同じ使いかたです.内部での画像データは OF_IMAGE_COLOR (RGB) 形式となります.PCに複数のカメラが接続されている場合,setDeviceID() メソッドによって選択することができます.カメラを切り替えるには,いちど close() してから setDeviceID() でカメラを指定し,再び initGrabber() を呼び出してください.
PCにカメラが接続されている人は,新しく dojoExample12 として,以下のプログラムを試してみてください.基本的にカメラから得られた画像も,ビデオ(ファイル)から得られた画像も,また静止画も,扱い方はほぼ同じです.
class ofApp : public ofBaseApp {
private:
ofVideoGrabber camera;
public:
(... 変更なし ...)
};
void ofApp::setup () { // setup window ofSetWindowShape(320, 240); ofSetFrameRate(30); // setup grabber camera.setDeviceID(0); camera.initGrabber(320, 240); } void ofApp::update() { camera.update(); } void ofApp::draw() { camera.draw(0, 0); } (... 以下変更なし ...)
発展課題1: カメラ映像が表示されるだけではおもしろくないので,ホラー映画風にピクセル値を反転させてみましょう.
class ofApp : public ofBaseApp {
private:
ofVideoGrabber camera;
ofImage image;
public:
(... 変更なし ...)
};
void ofApp::setup () { // setup window ofSetWindowShape(320, 240); ofSetFrameRate(30); // setup grabber camera.setDeviceID(0); camera.initGrabber(320, 240); // setup image image.allocate(320, 240, OF_IMAGE_COLOR); } void ofApp::update() { camera.update(); unsigned char *cdata = camera.getPixels().getData(), *idata = image.getPixels().getData(); // image = -camera; for (int k = 0; k < 320 * 240 * 3; k++) { idata[k] = 255 - cdata[k]; } image.update(); } void ofApp::draw() { image.draw(0, 0); }
発展課題2: 画像で出力させる以外にも,つぎのような表示のしかたも考えられます.いろいろ試してみてください
class ofApp : public ofBaseApp {
private:
ofVideoGrabber camera;
ofImage image;
int px[24][32], py[24][32];
public:
...
};
void ofApp::setup () { // setup window ofSetWindowShape(320, 240); ofSetFrameRate(30); ofSetBackgroundColor(0, 0, 0); // setup grabber camera.setDeviceID(0); camera.initGrabber(320, 240); // setup px,py for (int y = 0; y < 24; y++) { for (int x = 0; x < 32; x++) { px[y][x] = x * 10 + 5; py[y][x] = y * 10 + 5; } } } void ofApp::update () { camera.update(); } void ofApp::draw () { unsigned char *data = camera.getPixels().getData(); for (int y = 0; y < 24; y++) { for (int x = 0; x < 32; x++) { int index = (py[y][x] * 320 + px[y][x]) * 3; ofSetColor(data[index], data[index + 1], data[index + 2]); ofCircle(px[y][x], py[y][x], 4); } } }