読者です 読者をやめる 読者になる 読者になる

( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

JJUG CCC 2009 FallでProcessingとOpenCVについてLTで話しましたですの

Java 勉強会

JJUG CCC 2009 Fall第十陸回 第3回チキチキ 天下一15分ですべてを見せてやる&ライトニングトーク大会で、「Javaの画像処理で遊んでみようや」というタイトルでProcessingとOpenCVについてLTしてきました。



カメラキャプチャや内蔵Micからの音声信号の波形出力や、OpenCVの顔認識などのデモをやりました。うまく動いてよかったです。インパクトも、あったのではないかと思います。


以下は、デモの解説です。長いです。
id:nagaseyasuhitoやrobaさんらに「エントリ書かかねーなんてありえねーだろクズが」と言われたので頑張って書きました><(嘘です)

Processingについて

Processingは、Javaで実装されている「グラフィックデザインプログラミング言語 & 開発環境」です。
Javaを簡略化した記法で、簡単にアニメーションを作成したり、動画や音声を扱うことができます。

Processing.org


Processingのコードは、バイトコードレベルでJavaと互換性をもっており、Processingのコード中にjava.swingなどをimportしてWindowを表示したり、JavaプログラムからProcessingのクラスを利用できたりします。このへんはシームレスに統合されています。


Processingをダウンロードしてインストールすると、こんな感じのProcessingの開発環境が利用できるようになります。ちょっとしたコードなら、このProcessingのエディタで書いて動かして試すことができます。


f:id:yuroyoro:20091009175958p:image

デモのソース解説

今回のデモでは、こんな風に内蔵カメラからキャプチャした画像に、内蔵マイクから入力された音声信号を波形出力するものを見せました。


f:id:yuroyoro:20091009175959p:image


使用したコードはこれです。簡単ですね。そうですね。

import processing.video.*;
import ddf.minim.*;

Minim minim;
AudioInput in;
Capture cam;
  
void setup() {
  size(640, 480);

  cam = new Capture(this, 640, 480);
  minim = new Minim(this);
  in = minim.getLineIn(Minim.STEREO, 640);
}

void draw() {
  if (cam.available() == true) {
    cam.read();
    image(cam, 0, 0);
  }

  smooth();stroke(#FF3333);  strokeWeight(2);
  
  for(int i = 0; i < in.bufferSize() - 1; i++){
    line(i, 200 + in.right.get(i)*100, i+1, 200 + in.right.get(i+1)*100);
  }
} 

void stop()
{
  in.close();
  minim.stop();
  
  super.stop();
}

カメラからのキャプチャは、processing.video.Captureクラスを、new Capture(this,640,480)のように、キャプチャサイズを指定してインスタンスを作るだけです。実際にキャプチャ画像を取得するのは、draw()メソッドのcam.read()です。で、image(cam,0,0)でキャプチャ画像をウィンドウに表示しています。


Capture \ Language (API) \ Processing 2+


音声信号は、ddf.minim.Minimクラスを利用します。minim.getLineIn(Minim.STEREO, 640)で内蔵マイクからの信号を拾えます。640はサンプリングレートです。このコードでは、ウィンドウのサイズが640pixelですので、640bitでサンプリングして1bit=1pixleで波形を描画してます。


波形を表示しているのはこんなコードです。

  for(int i = 0; i < in.bufferSize() - 1; i++){
    line(i, 200 + in.right.get(i)*100, i+1, 200 + in.right.get(i+1)*100);
  }

forループで音声信号のバッファサイズ(サンプリングレート)毎に、in.right.get(i)でバッファ内の信号レベルを取得、lineメソッドで波形を出力してます。in.rightはRチャンネルの信号で、ほかにもin.mixやin.leftで信号を拾えますよ。その辺APIを参照してくださいね☆


Minim JavaDoc


Processingで特徴的なのは、draw()メソッド内で表示処理を行うだけで簡単にアニメーションや動画が出力できてしまう点だと思います。


Javaのコードでこれらの処理を行おうと思うと、Threadを用意してフレームレート毎に自前で描画処理を呼び出すように実装する必要があります。


そういった面倒な処理をProcssingが引き受けてくれることが、すごくいいなぁと思いました。

OpenCVについて

OpenCVは、インテルが開発しているOSSのコンピュータビジョンライブラリです。


Welcome - OpenCV Wiki


OpenCVのライブラリは、C++で提供されていますが、これをJavaから呼び出すブリッジライブラリが存在します。これはProcessingと統合されています。


OPENCV \ library
OpenCV.jp


このライブラリでは、OpenCVが提供する全てのAPIをラップしているわけでは無いのですが、パターン認識などは出来るので、サンプルを動かすだけでも楽しめます。

環境構築

まず、OpenCVをインストールするところから始める必要があります。Windows環境ならばOpen Computer Vision Library - Browse /opencv-win/2.0 at SourceForge.netこちらからダウンロー丼してインストールします。


俺の環境はMacOSX SnowLeopardなんですが、OpenCVを入れるのは結構苦労しました。
Mac_OS_X_OpenCV_Port - OpenCV Wiki


手順はこんな感じです。

  • cmake入れる
  • OpenCVのソース落とす
  • cmakeでビルドする
  • OpenCV.Frameworkを作って/Library/Frameworkに入れる


こちらの記事に、詳しいやり方が載っています。


次に、OPENCV Processing and Java Libraryをダウンロー丼して適当な場所に解凍しておきます。
OPENCV Processing and Java Library

デモのコード - カメラキャプチャと顔認識

デモのコードをGistに貼っておきます。



yuroyoro's
gist: 205865 — Gist


コンパイルは、クラスパスにOPENCV Processing and Java Libraryのjarを指定しておきます。

$ javac -classpath ../OpenCV/library/OpenCV.jar FaceDetection.java


実行は、-d32を指定しつつ、-Djava.library.pathにOPENCV Processing and Java Libraryを解凍したディレクトリにの/libraryディレクトリを指定します。で、クラスパスにOPENCV Processing and Java Libraryのjarを指定です。

java -d32 -Djava.library.path=../OpenCV/library -classpath ./:../OpenCV/library/OpenCV.jar FaceDetection
    // OpenCV setup
    cv = new OpenCV();
    cv.capture( 640, 480);
    cv.cascade( OpenCV.CASCADE_FRONTALFACE_ALT );


カメラキャプチャは、OpenCVクラスをnewして、cv.capture(640,480)でキャプチャサイズを指定するだけです。


顔認識するための分類器の定義ファイルは、cv.cascade( OpenCV.CASCADE_FRONTALFACE_ALT )で指定します。
OpenCV.CASCADE_FRONTALFACE_ALTは、OpenCVに最初からバンドルされている、フツーの人の顔を認識する定義です。

t.sleep( FRAME_RATE );

// grab image from video stream
cv.read();

// create a new image from cv pixels data
MemoryImageSource mis = new MemoryImageSource( cv.width, cv.height, cv.pixels(), 0, cv.width );
frame = createImage( mis );

// detect faces
squares = cv.detect( 1.2f, 2, OpenCV.HAAR_DO_CANNY_PRUNING, 20, 20 );

// of course, repaint
repaint();


このコードは、Threadのwhileループ内で行ってる処理で、cv.read()でキャプチャ画像を取得し、createImage( mis)でウィンドウにキャプチャ画像を出力しています。


顔認識は、cv.detect()でやってます。こまかいパラメータは、俺もよくわかってないです。誰か教えてください><


cv.detect()で顔と認識された範囲が取得できるので、あとはrepaint()内で四角い枠を描画するだけです。

デモのコード - アニメ顔認識

Perlでのアニメ顔認識は、Imager::AnimeFaceというライブラリがあります。


作者のid:ultraistさんが、OpenCV用の定義ファイルを作成されており、今回はそれを利用させてもらいました。
情報提供してくれた@yasushiaさん感謝です!!
アニメ顔の検出とキャラクターの分類 - デー


デモのコードをGistに貼っておきます。



yuroyoro's
gist: 205867 — Gist


このコードは、id:iad_otomamayさんのhttp://d.hatena.ne.jp/iad_otomamay/20080927/p1をパクらせてもらいました。こんなネタに使ってゴメンナサイ。


コンパイルは、クラスパスにOPENCV Processing and Java Libraryのjarを指定しておきます。

$ javac -classpath ../OpenCV/library/OpenCV.jar AnimeFaceDetection.java


実行は、-d32を指定しつつ、-Djava.library.pathにOPENCV Processing and Java Libraryを解凍したディレクトリにの/libraryディレクトリを指定します。で、クラスパスにOPENCV Processing and Java Libraryのjarを指定です。

java -d32 -Djava.library.path=../OpenCV/library -classpath ./:../OpenCV/library/OpenCV.jar AnimeFaceDetection './nene.jpg'


メインの処理の抜粋です。

    cv = new OpenCV();
    cv.loadImage(imagePath);

    // 顔の検出
    cv.cascade( "./haarcascades/haarcascade_animeface2.xml");
    Rectangle[] squares = cv.detect( 1.2f, 2, OpenCV.HAAR_DO_ROUGH_SEARCH,20, 20 );

    this.setBounds( 0, 0, cv.width, cv.height );
    this.setVisible( true );

    MemoryImageSource mis = new MemoryImageSource( cv.width, cv.height, cv.pixels(), 0, cv.width );
    Image frame = createImage( mis );
    Graphics2D g = (Graphics2D)this.getGraphics();
    g.drawImage( frame, 0, 0, null );

    // 顔の部分を赤四角で囲む。
    g.setColor( Color.RED );
    BasicStroke wideStroke = new BasicStroke(4.0f);
    g.setStroke(wideStroke);
    for( Rectangle rect : squares ){
      g.drawRect( rect.x, rect.y, rect.width, rect.height );
    }


やってることは単純で、cv.cascade()メソッドでアニメ顔認識用の定義ファイルを指定して、あとはcv.detect()で認識範囲を取得してるだけです。


アニメ顔認識用の定義ファイルは、こちらからダウンロー丼しておきます。
http://www.udp.jp/cv/haarcascades/haarcascade_animeface2.xml


認識結果はこんな感じです。

f:id:yuroyoro:20091009180949p:image
f:id:yuroyoro:20091009180941p:image


彼女とキスするときに便利ですね!!

まとめ

業務アプリ書くだけがJavaじゃないですよ。もっと役にも立たない面白プログラムもJavaで書いてみるといいんジャマイカ?と言いたいだけです。


手始めに、美人時計を顔認識とかアニメ顔をmiyagawanizeなどいかがでしょう?