workshop/recomposition_image_and_video/main

静止画ピクセルの再構成

まずは下記静止画像を読み込み,それを表示するだけのプログラムを記述してみます.静止画像を別名保存し, スケッチファイルのdataファイルの中にtest.pngとして保存してください.

sample 1
PImage img;
img = loadImage("test.png");
size(img.width,img.height,P2D);
image(img,0,0,img.width, img.height);

たった4行ですが,指定した画像を表示するプログラムが書けました.ではこの画像を再構成する にはどのようなことをすればよいでしょうか?デジタル画像において,全ての画像はピクセルから 構成されます.つまりピクセルデータを扱うことで,同じ画像データから様々な表示に再構成できる はずです.上記プログラムではimage関数から直接PImageの中身を描画していますが,これでは 各ピクセル画像の値を操作することができていません.そこで,次のようなプログラムに変えること で,各ピクセルデータを取得し,点描画と同じやり方で静止画像を描画してみます.

sample 2
PImage img;
img = loadImage("test.png");
size(img.width,img.height,P2D);
imageMode(CENTER);
noStroke();
background(255);
for( int i = 0; i < img.height; i++ ){
  for( int j = 0; j < img.width; j++ ){
    color c = img.get(j,i);
    stroke(red(c), green(c), blue(c));
    point(j, i);  
  }
}

実行結果はsample1と変わらないことが確認できたと思います.では再構成の簡単な事例として, 各色データにおけるRGBをBGRの順に入れ替えて表示してみましょう.

sample 3
PImage img;
img = loadImage("test.png");
size(img.width,img.height,P2D);
imageMode(CENTER);
noStroke();
background(255);
for( int i = 0; i < img.height; i++ ){
  for( int j = 0; j < img.width; j++ ){
    color c = img.get(j,i);
    stroke(blue(c), green(c), red(c));
    point(j, i);  
  }
}

実行してみて,一見するとなにも変わっていないように感じるかもしれませんが, 赤色と青色の部分が元画像から入れ替わっているのがわかると思います.では次にグレースケールに変更 してみましょう.RGBに各色を設定していましたが,ここで,RGB画素の平均値を与えることで, グレースケールに変更出来ます.

sample 4
PImage img;
img = loadImage("test.png");
size(img.width,img.height,P2D);
imageMode(CENTER);
noStroke();
background(255);
for( int i = 0; i < img.height; i++ ){
  for( int j = 0; j < img.width; j++ ){
    color c = img.get(j,i);
    float gray = (red(c)+green(c)+blue(c))/3;
    stroke(gray,gray,gray);
    point(j, i);  
  }
}

ここで,イメージをグレースケールにすることができました.では次に,閾値(しきいち)を設けて, 画像を二値化してみましょう.つまりこの画像の明暗を分ける処理になります.すでにグレースケールの 値は各画素において取得できているので,これと適当な値をくれべることで,画像を白と黒の二値に してみます.

sample 5
PImage img;
img = loadImage("test.png");
size(img.width,img.height,P2D);
imageMode(CENTER);
noStroke();
background(255);
for( int i = 0; i < img.height; i++ ){
  for( int j = 0; j < img.width; j++ ){
    color c = img.get(j,i);
    float gray = (red(c)+green(c)+blue(c))/3;
    if( gray < 50 ){
      gray = 0;
    }
    else{
      gray = 255;
    }
    stroke(gray,gray,gray);
    point(j, i);  
  }
}

次に二値化情報を元に,grayが255の箇所は色を復元することにすると,次のような結果になります. 結果として比較的明度の高いラインが残るようになりました.このように画像に対して二値化を行い, 処理を行なう対象領域を限定する手法は画像処理(Computer Vision)において非常に一般的な手法です. もちろん静止画や動画像における再構成要素としても利用価値が高いものです.

sample 6
PImage img;
img = loadImage("test.png");
size(img.width,img.height,P2D);
imageMode(CENTER);
noStroke();
background(255);
for( int i = 0; i < img.height; i++ ){
  for( int j = 0; j < img.width; j++ ){
    color c = img.get(j,i);
    float gray = (red(c)+green(c)+blue(c))/3;
    if( gray < 80 ){
      gray = 0;
    }
    else{
      gray = 255;
    }
    
    if( gray == 255 ){
      stroke(red(c),green(c),blue(c));
    }
    else{
      stroke(255);
    }
    point(j, i);  
  }
}
練習

上記の2値化については,手動でやるのも問題ないですが,実はPImageのメソッドにはfilter()なるものが 用意されているので,それを使えば一発でできます. http://processing.org/reference/PImage_filter_.html を参考にして,同じ事をfilter関数を利用して実装してみましょう.

動画ピクセルの再構成

では次に動画ピクセルを再構成する方法を考えてみましょう.まずは動画ファイルを読み込んで,再生する だけのプログラムを書いてみます.なお再生する動画ファイルですが,PocoPocoのファイルを使ってみましょう. 動画ファイルを ダウンロード なお,ダウンロードしたファイルはdataフォルダに置くことを忘れないようにして下さい.また,Processingのメニューに ある「Sketch」→「Import Library..」からvideoのライブラリを追加することで,import processing.video.*;を自動で プログラムの頭に追加できます.

sample7
import processing.video.*;

Movie mov;
void setup()
{
  size(640, 360);
  noStroke();
  mov = new Movie(this, "pocopoco.mp4");
  mov.noLoop();
  mov.play();
}
void draw()
{
  background(0);
  if( mov.available() == true ){
    mov.read();
    mov.loadPixels();
  }
  image(mov, 0,0, width, height);
}

では次に各画素の色情報を取得してみましょう. 色情報は静止画像と同じように get(int x, int y)メソッドを用いて参照することができます.

sample 8
import processing.video.*;

Movie mov;
void setup()
{
  size(640, 360);
  noStroke();
  mov = new Movie(this, "pocopoco.mp4");
  mov.noLoop();
  mov.play();
}
void draw()
{
  background(0);
  if( mov.available() == true ){
    mov.read();
    mov.loadPixels();
  }
  
  for( int i = 0; i < mov.height; i++ ){
    for( int j = 0; j < mov.width; j++ ){
      stroke(mov.get(j,i));
      point(j,i);
    }
  }
}

どうでしょう?...動きはしますが,めちゃめちゃカクカク(フレームレートが小さい)していると思います. ちなみにProcessingのサイトでは,

You want your sketch to run faster! P2D and P3D make use of OpenGL-compatible graphics hardware. In other words, some of the work required to draw all the pixels in the window can happen on your computer's graphics card which is often more effecient. Keep in mind that OpenGL is not magic pixie dust that makes any sketch faster (though it's close), you will also need to carefully consider the techniques you are using to do the drawing as well. In particular, using the new "shape recording" functionality available in PShape (see PShape tutorial) can greatly increase speed in P3D mode.
と記載されており,ハードウェアレンダリングするようなのですが,とりあえず馬場の環境では1fpsでないくらいでした.なので, 実際に描画する際はimage等の関数を利用したり,ピクセル数を減らして描画していきましょう.動画ファイルの場合, 一般的には一秒間に30コマの静止画がある為,静止画と同じように扱うと,Processingでは処理落ちしてしまいます. なので,そこで,point関数を使わず,直接movクラス内のピクセルを変更することで,動画像ピクセルを操作していきます.

静止画と同様にget関数でcolorクラスを取り出し,ネガポジ反転させたイメージを描画してみます.

sample 9
import processing.video.*;

Movie mov;
void setup()
{
  size(640, 360);
  noStroke();
  mov = new Movie(this, "pocopoco.mp4");
  mov.noLoop();
  mov.play();
}
void draw()
{
 background(0);  
 if( mov.available() == true ){
   mov.read();
   mov.loadPixels();
   for( int i = 0; i < mov.height; i++ ){
     for( int j = 0; j < mov.width; j++ ){
       color c1 = mov.get(j,i);
        color c2 = color(255-red(c1), 255-green(c1), 255-blue(c1));
        mov.set(j,i,c2);
     }
   } 
   }
  image(mov, 0,0, mov.width, mov.height);
}

練習問題

得られた画像ピクセルをネガ・ポジ反転し,静止画と同様に二値化を行い,それを表示するプログラムを記述して下さい.下に記述しますが,filterメソッドを使うと簡単でした.filterメソッドはPImageのメソッドになっているので,https://processing.org/reference/PImage_filter_.htmlを参照して,いくつかのfilterの種類を試してみましょう.

sample 10
import processing.video.*;

Movie mov;
void setup()
{
  size(640, 360);
  noStroke();
  mov = new Movie(this, "pocopoco.mp4");
  mov.noLoop();
  mov.play();
}
void draw()
{
 background(0);  
 if( mov.available() == true ){
   mov.read();
   mov.loadPixels();
   for( int i = 0; i < mov.height; i++ ){
     for( int j = 0; j < mov.width; j++ ){
       color c1 = mov.get(j,i);
        color c2 = color(255-red(c1), 255-green(c1), 255-blue(c1));
        mov.set(j,i,c2);
     }
   }
 }
  mov.filter(THRESHOLD, 0.5);
  image(mov, 0,0, mov.width, mov.height);

}

課題

ここまでやってきた内容を元に,自分だけのオリジナル動画エフェクトを作成してみましょう.例えば下記サンプルは10ピクセルごとに線を引き,各頂点の明るさに応じてy座標を少しずらしています.描いてるのはたった48本の線ですが,私たちはこれをみて人間が演奏することを視認できます.このように線を描く,四角や三角,丸などの基本図形を用いて,動画データを再構成してみましょう.表示結果に失敗はありません.どのようなプログラムがどのような表現に結びつくのかを楽しみならが学んでください.

sample11
import processing.video.*;

Movie mov;
void setup()
{
  size(640, 360);
  noStroke();
  mov = new Movie(this, "pocopoco.mp4");
  mov.noLoop();
  mov.play();
}
void draw()
{

  noFill();
  if ( mov.available() == true ) {
    background(0);  
    mov.read();
    mov.loadPixels();
    for ( int i = 0; i < mov.height; i=i+10 ) {
      beginShape();
      for ( int j = 0; j < mov.width; j++ ) {
        color c1 = mov.get(j, i);
        stroke(255);
        strokeWeight(1);
        vertex(j, i+(red(c1)+green(c1)+blue(c1))/25);
      }
      endShape();
    }
  }
}

Copyright (c) 2015 Tetsuaki BABA all rights reserved.