影のボールプールの仕組み

      2017/07/11

こんにちは
ShibaLabのdaisukeです。
今回は2017年大宮祭で展示した「影のボールプール」の仕組みについて解説したいと思います。稚拙なコードですが、興味があったら見ていってください。

影のボールプールとは

影のボールプールは2017年大宮祭で展示した、映像に自分の影で触れることができる作品です。

スクリーンの前に立つと、自分の影がスクリーンに映ります。スクリーン上部から降ってくる様々な色の物体に、自らの影で触れ、動かすことができます。

 

影のボールプールの仕組み:概要

この影のボールプールでは、入力は、webカメラで行い、スクリーン全体を読み取っています。処理は、processingを使って行います。最後に、出力は、プロジェクターで行います。

位置関係は、このような感じです。

 

処理の部分は大きく分けて3つの部分に分けることができます。

  1. 入力された映像を使いやすいように補正する、映像補正部分
  2. 補正された映像の中から、影の部分を見つけ出す、影検出部分
  3. 影の形のオブジェクトを物理エンジンに渡す、オブジェクト生成部分

それぞれについて解説していきたいと思います。

 

1. 映像補正部分

webカメラから入力された映像は、以下のようになります。

この映像を、処理しやすいように、スクリーン部分を切り抜き、長方形になるように補正(射影変換)します。イメージ↓

射影変換は、変換前の四隅の4点、変換後の四隅の4点からパラメータを生成し、そのパラメータを用いることで、座標権の変換をすることができます。この射影変換について、参考にしたサイトはこちらです↓

Processingで射影変換(ホモグラフィ)

今回は、スクリーンに出力するため、出力先の4点は(0, 0), ( width, 0), (width, height), (0, height) で固定しています。

//double[] parameter;
//PVector[] corner;
void computeParameter() {
 //変換前の4点はcornerに入っている
 Matrix m=new Matrix(new float[][]{ 
 {corner[0].x, corner[0].y, 1, 0, 0, 0, 0, 0}, 
 {corner[1].x, corner[1].y, 1, 0, 0, 0, -width*corner[1].x, -width*corner[1].y}, 
 {corner[2].x, corner[2].y, 1, 0, 0, 0, -width*corner[2].x, -width*corner[2].y}, 
 {corner[3].x, corner[3].y, 1, 0, 0, 0, 0, 0}, 
 {0, 0, 0, corner[0].x, corner[0].y, 1, 0, 0}, 
 {0, 0, 0, corner[1].x, corner[1].y, 1, 0, 0}, 
 {0, 0, 0, corner[2].x, corner[2].y, 1, -height*corner[2].x, -height*corner[2].y}, 
 {0, 0, 0, corner[3].x, corner[3].y, 1, -height*corner[3].x, -height*corner[3].y}});
 Matrix inv_m=m.inverse();
 parameter=inv_m.mul(new float[]{0, width, width, 0, 0, 0, height, height});
 }

パラメーターを使った座標変換の処理は、以下のようになります。


PVector adapt(PVector input) {//入力に対して変換後の座標を返す関数
  if (parameter==null)return input;
    float x=(float)((parameter[0]*input.x+parameter[1]*input.y+parameter[2])/(parameter[6]*input.x+parameter[7]*input.y+1));
    float y=(float)((parameter[3]*input.x+parameter[4]*input.y+parameter[5])/(parameter[6]*input.x+parameter[7]*input.y+1));
    return new PVector(x, y);
}

 

影検出部分

影の検出には、diewald_CV_kitというライブラリの、BlobDetectionを利用しました。このライブラリは、Library Managerで公開されていないため、リンクを張っておきます。

Processing Library – Computer Vision – diewald_CV_kit

このライブラリが優秀で、画像と、検出の条件を指定すると、画像の中から条件にあった塊を検出してくれます。

検出条件のコード例↓

public final boolean isBLOBable(int pixel_index, int x, int y) {
 if (PixelColor.brighntess(img_.pixels[pixel_index]) < 30){
  return true;
 } else {
  return false;
 }
}

 

この、ライブラリによって抽出された、影の輪郭のデータを使って、物理エンジンに入れるためのオブジェクトを作ります。

オブジェクト生成部分

物理エンジンのオブジェクトは、今回は輪郭部だけ生成しました。複雑な形の図形は当たり判定を行うことができないため、今回は輪郭部だけの当たり判定を物理エンジンに行わせました。内部に物体が入ってしまう対策の処理は今回は省略します。

今回はbox2dという物理エンジンライブラリを使用しました。Processingで手軽に利用できる便利な物理エンジンです。

//BlobDetector bd;
//List<Body> bodies;
void update() {
 killBody();
 bd.update();
 ArrayList<Blob> blob_list = bd.getBlobs();
 for (int blob_idx = 0; blob_idx < blob_list.size(); blob_idx++ ) {
  Blob blob = blob_list.get(blob_idx);
  ArrayList<Contour> contour_list = blob.getContours();
  for (int contour_idx = 0; contour_idx < contour_list.size(); contour_idx++ ) {
   Contour contour = contour_list.get(contour_idx);
   if ( contour_idx == 0) {
    addBody(contour.getPixels()); 
   }
  }
 }
}
void addBody(List<Pixel> points) {
 ChainShape chain = new ChainShape();
 Vec2[] vertices = new Vec2[points.size()];
 for (int i = 0; i < vertices.length; i++) {
  PVector p=pc.adapt(points.get(i).x_, points.get(i).y_);
  Vec2 edge = box2d.coordPixelsToWorld(p.x, p.y);
  vertices[i] = edge;
 }
 chain.createChain(vertices, vertices.length);
 BodyDef bd = new BodyDef();
 bd.position.set(0.0f, 0.0f);
 Body body = box2d.createBody(bd);
 body.createFixture(chain, 1);
 bodies.add(body);
}

ここまでご覧いただきありがとうございます。
質問等ありましたらコメント欄へお願いします。

 - 作品紹介