画像処理2

前の画像処理1の内容は、原理的なことに触れているので、結構難しかった人もいるかと思います。
今回は、インタラクティブアート作品に利用するためのより実践的な方法を学びます。

まず、これから扱うプログラムを正常に動かすために、露出固定が可能なカメラを使用してください。MacBookなどに搭載されているカメラやUSBカメラにはこのような機能が付いていないので、Firewire (iLink)が付いているDVカメラなどが一番適しています。
インスタレーションなどに使う場合は、Firewireの長さに限界がある(最長4.5m)ので、長く繋ぎたい場合には、通常のビデオコードをカメラに付け、パソコンには、アナログ-DVコンバータで接続します。

1. フレーム差分

まずは、カメラの前で対象物(人など)が動いた場合に、その変化量を計算するプログラムです。
これを画像処理の用語で「フレーム差分」と言います。前のサンプルよりはかなりコードが長くなっていますが、基本的に前の実習の応用です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//This code has been arranged by Yasushi Noguchi. Last updated on June 21, 2008.
/**
 * Frame Differencing
 * by Golan Levin.
 *
 * Quantify the amount of movement in the video frame using frame-differencing.
 */ 
 
import processing.video.*;
 
int numPixels;    //画像のピクセルの総数
int[] previousFrame;    //一つ前のフレーム
int noiseFilter = 50;    //ノイズを拾わないためのフィルタ(値を増やすとフィルタが強くかかる)
Capture video;    //キャプチャ映像用の変数
 
void setup() {
  size(640, 480);
 
  video = new Capture(this, width, height, 24);
  //キャプチャーするビデオ画像の総ピクセル数
  numPixels = video.width * video.height;
  //現在のキャプチャ画像と比べるために一つ前のフレーム用の配列を作る
  previousFrame = new int[numPixels];
  loadPixels();
}
 
void draw() {
  if (video.available()) {  //もしキャプチャができたら、
    video.read(); //ビデオフレームの読み込み
    video.loadPixels(); //ビデオのピクセルを操作できるようにする
 
    int movementSum = 0; //一つのフレームでの動作量
    for (int i = 0; i < numPixels; i++) { //フレーム内のそれぞれのピクセルを検出
      color currColor = video.pixels[i];
      color prevColor = previousFrame[i];
 
      //現在のピクセルのR, G, Bを抜き出す
      int currR = (currColor >> 16) & 0xFF;
      int currG = (currColor >> 8 ) & 0xFF;
      int currB = currColor & 0xFF;
 
      //一つ前のフレームの色を抜き出す
      int prevR = (prevColor >> 16) & 0xFF;
      int prevG = (prevColor >> 8 ) & 0xFF;
      int prevB = prevColor & 0xFF;
 
      //現在のピクセルから前のピクセルの色を引いた絶対値
      int diffR = abs(currR - prevR);
      int diffG = abs(currG - prevG);
      int diffB = abs(currB - prevB);
 
      //noiseFilterの値よりも大きかったらmovementSumに足していく
      //そして、現在の色に更新
      if(diffR + diffG + diffB > noiseFilter){
        movementSum ++;
        pixels[i] = color(currR, currG, currB);
        //次のコードの方が高速に実行できるが、ちょっと難しい
        //pixels[i] = 0xFF000000 | (currR << 16) | (currG << 8 ) | currB;
      }
      else{
        //そうでない場合は黒
        pixels[i] = color(0);
      }
 
      //現在のフレームの色を前のフレームの色にする。
      previousFrame[i] = currColor;
    }
 
    updatePixels();    //ピクセルを更新
    textSize(12);  //テキストのサイズ
    text(movementSum, 30,30);  //変化したピクセルの総数をプリントを画面表示
  }
}

processingcorepappletscreensnapz004.jpg

movementSumに動いたピクセルの総数が代入されます。
この場合は640 x 480の画面なので、フレームごとのピクセルは307200ピクセルとなります。
ですから、動作量は0 ~ 307200の値を取ります。
これは単純にint型の変数なので、少し動いた、かなり動いた、激しく動いたなどという動作量を扱うことができますね。

2. 背景画像の黒抜き

次は、フレーム差分の応用で、「背景画像の黒抜き」です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//This code has been arranged by Yasushi Noguchi. Last updated on June 21, 2008.
/**
 * Background Subtraction
 * by Golan Levin.
 *
 * Detect the presence of people and objects in the frame using a simple
 * background-subtraction technique. To initialize the background, press a key.
 */
 
import processing.video.*;
 
int numPixels;    //画像のピクセルの総数
int[] backgroundPixels;    //背景のピクセル
int noiseFilter = 50;    //ノイズを拾わないためのフィルタ(値を増やすとフィルタが強くかかる)
Capture video;    //キャプチャ映像用の変数
 
void setup() {
  size(640, 480); 
 
  video = new Capture(this, width, height, 24);
  //キャプチャーするビデオ画像の総ピクセル数
  numPixels = video.width * video.height;
  //現在のキャプチャ画像と比べるために背景画像用の配列を作る
  backgroundPixels = new int[numPixels];
  loadPixels();
}
 
void draw() {
  if (video.available()) {  //もしキャプチャができたら、
    video.read(); //ビデオフレームの読み込み
    video.loadPixels(); //ビデオのピクセルを操作できるようにする
 
    //現在のフレームと背景画像の差
    int presenceSum = 0;
    for (int i = 0; i < numPixels; i++) { //フレーム内のそれぞれのピクセルを検出
 
      //現在のピクセルと背景のピクセルの値を変数に代入
      color currColor = video.pixels[i];
      color bkgdColor = backgroundPixels[i];
 
      //現在のピクセルのR, G, Bを抜き出す
      int currR = (currColor >> 16) & 0xFF;
      int currG = (currColor >> 8 ) & 0xFF;
      int currB = currColor & 0xFF;
 
      //背景画像のR, G, Bを抜き出す
      int bkgdR = (bkgdColor >> 16) & 0xFF;
      int bkgdG = (bkgdColor >> 8 ) & 0xFF;
      int bkgdB = bkgdColor & 0xFF;
 
      //現在のピクセルから背景画像のピクセルの色を引いた絶対値
      int diffR = abs(currR - bkgdR);
      int diffG = abs(currG - bkgdG);
      int diffB = abs(currB - bkgdB);
 
      //noiseFilterの値よりも大きかったらpresenceSumに足していく
      //そして、現在の色に更新
      if(diffR + diffG + diffB > noiseFilter){
        presenceSum ++;
        pixels[i] = color(currR, currG, currB);
        //次のコードの方が高速に実行できるが、ちょっと難しい
        //pixels[i] = 0xFF000000 | (currR << 16) | (currG << 8 ) | currB;
      }
      else{
        //そうでない場合は黒
        pixels[i] = color(0);
      }
    }
    updatePixels();    //ピクセルを更新
    textSize(12);  //テキストのサイズ
    text(presenceSum, 30,30);  //変化したピクセルの総数をプリントを画面表示
  }
}
 
//マウスを押したときに、現在のフレームの画像をbackgroudPixelsにコピーする
void mousePressed() {
  video.loadPixels();
  arraycopy(video.pixels, backgroundPixels);
}

processingcorepappletscreensnapz005.jpg

画面上のマウスクリックによって、backgroundPixelsにその時点の画像が保存され、その保存された画像と現在の映像を比較して変化した部分だけに映像が表示されます。

参考:人の動きに反応する円

このサンプルは、人が動いたところにだんだん拡大しながら消えていく円が生成されるプログラムです。
基本的に、今回の実習の範囲内の知識で作ることができます。
多少コードが長くなっていますが、興味のある人は参考にしてみてください。
difference_circle