画像処理

このページでは、Processingでカメラ映像を使った画像処理を学びます。画像処理の基本は、映像を読み込み、ピクセルの色を取り出し、別の色や図形に変換して描き直すことです。

Processingのサンプルコードは、映像が歪まないように基本的に 640×480 で記述しています。ブラウザ上のp5.js実行サンプルは、ページ上で扱いやすいように 320×240 で表示します。

カメラはまだ開始されていません。

1. カメラ映像の表示

まずは、カメラからの映像をProcessing上で表示します。Processingではprocessing.videoライブラリのCaptureを使います。

リスト1
import processing.video.*;

Capture video;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();

  // カメラが認識されるまで待つ
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  // 640×480でカメラ映像を取得する
  video = new Capture(this, 640, 480, cameras[0]);
  video.start();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  image(video, 0, 0, width, height);
}
実行結果:カメラ映像をそのまま表示する

2. tint()を使った効果

tint()を使うと、画像や映像に色を重ねることができます。ここでは、カメラ映像に赤っぽい色を重ねます。

リスト2
import processing.video.*;

Capture video;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, 640, 480, cameras[0]);
  video.start();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  // 映像に赤っぽい色を重ねる
  tint(255, 100, 100);
  image(video, 0, 0, width, height);
}
実行結果:カメラ映像に赤い色を重ねる

3. 座標変換を使った反転

scale()を使うと、カメラ映像を左右反転・上下反転できます。左右反転ではscale(-1, 1)を使います。

リスト3
import processing.video.*;

Capture video;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, 640, 480, cameras[0]);
  video.start();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  background(0);

  image(video, 0, 0, width/2, height/2);  // 左上:正像

  pushMatrix();
  scale(-1, 1);                           // 右上:左右反転
  image(video, -width, 0, width/2, height/2);
  popMatrix();

  pushMatrix();
  scale(1, -1);                           // 左下:上下反転
  image(video, 0, -height, width/2, height/2);
  popMatrix();

  pushMatrix();
  scale(-1, -1);                          // 右下:上下左右反転
  image(video, -width, -height, width/2, height/2);
  popMatrix();
}
実行結果:正像・左右反転・上下反転・上下左右反転

4. 回転するカメラ映像

translate()rotate()を使うと、カメラ映像を回転させることができます。画像の基準点を画面中央に移動してから回転させます。

リスト4
import processing.video.*;

Capture video;
float angle = 0;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, 640, 480, cameras[0]);
  video.start();

  imageMode(CENTER);
}

void draw() {
  if (video.available()) {
    video.read();
  }

  background(0);

  translate(width/2, height/2);
  rotate(angle);
  image(video, 0, 0, 320, 240);

  angle += 0.02;
}
実行結果:カメラ映像が画面中央で回転する

5. 1ピクセルずつ色を取り出す

画像処理では、1ピクセルずつ色を調べることが重要です。pixels[y * width + x]で、x,y座標のピクセルを配列番号として指定します。

リスト5
import processing.video.*;

Capture video;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, width, height, cameras[0]);
  video.start();

  loadPixels();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  video.loadPixels();
  loadPixels();

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      int index = y * width + x;
      pixels[index] = video.pixels[index];
    }
  }

  updatePixels();
}
実行結果:1ピクセルずつ読み取って同じ映像を描く

6. 色を変換する

ピクセルの赤・緑・青の値を取り出すと、グレー変換、色の反転、2色化などができます。

リスト6
import processing.video.*;

Capture video;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, width, height, cameras[0]);
  video.start();

  loadPixels();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  video.loadPixels();
  loadPixels();

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      int index = y * width + x;
      color c = video.pixels[index];

      float r = red(c);
      float g = green(c);
      float b = blue(c);

      // グレー変換:RGBの平均を明るさにする
      float gray = (r + g + b) / 3;
      pixels[index] = color(gray);

      // 反転:色を逆にする場合はこちらを使う
      // pixels[index] = color(255 - r, 255 - g, 255 - b);

      // 二値化:明るさで白と黒に分ける場合はこちらを使う
      // if (gray > 127) {
      //   pixels[index] = color(255);
      // } else {
      //   pixels[index] = color(0);
      // }
    }
  }

  updatePixels();
}
実行結果:グレー・反転・2色化を切り替える

7. 時間の遅延を記録する

1行ずつ、または1列ずつ映像を記録していくと、時間のずれが画面に残ります。動いているものが歪んで表示されるのは、行や列ごとに撮影された時間が違うからです。

リスト7
import processing.video.*;

Capture video;
int row = 0;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, width, height, cameras[0]);
  video.start();

  loadPixels();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  video.loadPixels();

  // 1行分だけ現在の映像からコピーする
  for (int x = 0; x < width; x++) {
    int index = row * width + x;
    pixels[index] = video.pixels[index];
  }

  updatePixels();

  stroke(255, 0, 0);
  line(0, row, width, row);

  row++;
  if (row >= height) {
    row = 0;
  }
}
実行結果:横方向のスキャンで時間差を記録する
リスト8
import processing.video.*;

Capture video;
int column = 0;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, width, height, cameras[0]);
  video.start();

  loadPixels();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  video.loadPixels();

  // 1列分だけ現在の映像からコピーする
  for (int y = 0; y < height; y++) {
    int index = y * width + column;
    pixels[index] = video.pixels[index];
  }

  updatePixels();

  stroke(255, 0, 0);
  line(column, 0, column, height);

  column++;
  if (column >= width) {
    column = 0;
  }
}
実行結果:縦方向のスキャンで時間差を記録する

8. カメラ映像を図形で描く

カメラ映像をそのまま表示するのではなく、ピクセルの明るさや色を使って図形を描くと、モザイクや抽象的な映像表現になります。ここでは、低解像度のカメラ映像を読み取り、1つのピクセルを1つの図形として描き直します。

リスト9
import processing.video.*;

Capture video;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, 80, 60, cameras[0]);
  video.start();

  noStroke();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  video.loadPixels();
  background(0);

  float cellW = width / float(video.width);
  float cellH = height / float(video.height);

  for (int y = 0; y < video.height; y++) {
    for (int x = 0; x < video.width; x++) {
      int index = y * video.width + x;
      color c = video.pixels[index];

      float r = red(c);
      float g = green(c);
      float b = blue(c);
      float brightness = (r + g + b) / 3;

      // 明るいほど大きな円になる
      float s = map(brightness, 0, 255, 2, cellW * 2.5);

      fill(c);
      ellipse(x * cellW, y * cellH, s, s);
    }
  }
}
実行結果:明るさに応じたカラフルな円で映像を描く

元の映像を円の集合に変換するだけでも、カメラ映像がポップなドット表現になります。コードは単純ですが、色と大きさの変化が大きいため、見た目の変化がわかりやすいサンプルです。

9. 明るさで文字のような模様を作る

次は、明るさに応じて図形の大きさを変えながら、少しだけ回転させます。短いコードで、光るタイルやデジタルサイネージのような派手な表現になります。

リスト10
import processing.video.*;

Capture video;

void setup() {
  size(640, 480);

  String[] cameras = Capture.list();
  while (cameras.length == 0) {
    cameras = Capture.list();
  }

  video = new Capture(this, 80, 60, cameras[0]);
  video.start();

  rectMode(CENTER);
  noStroke();
}

void draw() {
  if (video.available()) {
    video.read();
  }

  video.loadPixels();
  background(0);

  float cellW = width / float(video.width);
  float cellH = height / float(video.height);

  for (int y = 0; y < video.height; y += 2) {
    for (int x = 0; x < video.width; x += 2) {
      int index = y * video.width + x;
      color c = video.pixels[index];

      float brightness = (red(c) + green(c) + blue(c)) / 3;
      float s = map(brightness, 0, 255, 2, 24);

      pushMatrix();
      translate(x * cellW, y * cellH);
      rotate(frameCount * 0.04);
      fill(c);
      rect(0, 0, s, s);
      popMatrix();
    }
  }
}
実行結果:明るさに応じた四角形が回転する

このサンプルでは、明るさを四角形のサイズに変換しています。さらにrotate(frameCount * 0.04)で全ての四角形を回転させることで、簡単なコードでも動きのある映像になります。

まとめ

命令・考え方意味
Captureカメラ映像を取得する
image()画像や映像を画面に表示する
tint()画像や映像に色を重ねる
scale(-1, 1)左右反転する
loadPixels()ピクセル配列を操作できるようにする
pixels[y * width + x]x,y座標のピクセルを配列番号で指定する
グレー変換RGBの平均を明るさとして使う
時間の遅延行や列ごとに違う時間の映像を記録する
ドット表現明るさを円の大きさに変換する
タイル表現明るさを四角形の大きさや回転に変換する

今日の重要ポイント:画像処理の基本は、映像をそのまま表示することではなく、ピクセルの色を取り出して、別の色や別の図形に変換して描き直すことです。