JavaでOpenCVのインストール・eclipseの設定など(Mac OSX)

メモ。
基本的にOpenCVの
Introduction to Java Development の内容です。

・ダウンロード

Sourceforgeからダウンロードできます。
もしくは、Gitコマンドでダウンロード。
%git clone git://github.com/Itseez/opencv.git
%cd opencv
%git checkout

・インストール

[事前準備]
cmakeを入れましょう。僕はhomebrewでいれました。

Sourceforgeからダウンロードした場合、解凍してできたopencvディレクトリに移動。
ビルド用のディレクトリを作ります。
%mkdir build
%cd build

Sourceforgeからダウンロードした場合、解凍してできたopencvディレクトリに移動。
Makefileを作って、コンパイルする。
%cmake -DBUILD_SHARED_LIBS=OFF ..
%make -j8


これで、opencv/build以下にopencv一式がコンパイルされました。


・eclipseの設定

新しいプロジェクトを作成するとする(例に習って、HelloCVとする)。
これにbuild pathを通す。Librariesの設定から、Add Libraryを選択。


eclipse_user_lib


User Libraryを選択。

eclipse_user_lib2

eclipse_user_lib3

eclipse_user_lib4

下の名前はopencv-2.4.4だけど、バージョン違ってたらそれに合わせましょう。

eclipse_user_lib5

次に、JNIライブラリの設定。

eclipse_user_lib6

Jarファイルには、opencv-245.jar(245はバージョンによります) (場所はopencv/build/bin/の中) (245はバージョンによります)を指定。
次に、JNIライブラリの場所を指定。


eclipse_user_lib7

ここは、libopencv_java245.dylib (245はバージョンによります)(場所はopencv/build/lib の中)を指定。
で、この設定したユーザライブラリをプロジェクトのライブラリに指定します。

eclipse_user_lib8


試しに、次のプログラムを作ってコンパイル・実行してみましょう。


import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;

public class Main {
public static void main(String[] args) {
System
.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Mat m
= Mat.eye(3, 3, CvType.CV_8UC1);
System
.out.println("m = " + m.dump());
}
}



eclipse_run


こうなればオッケー。

・顔認識のサンプルプログラム



最後に、顔認識のプログラムを動かしてみましょう。

[事前準備]
opencv/data ディレクトリをeclipseプロジェクトにコピー。
このdataディレクトリには顔認識に必要な学習ファイルが存在していて、プログラムで使います。

lena

この画像をlena.png という名前でダウンロードして、同じくeclipseプロジェクトの適当な場所にコピー(例: YOUR_PROJECT/resources/とか作って、その中とか)。
次のプログラムを作成。



import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.highgui.Highgui;
import org.opencv.objdetect.CascadeClassifier;

//
// Detects faces in an image, draws boxes around them, and writes the results
// to "faceDetection.png".
//
class DetectFaceDemo {
public void run() {
System
.out.println("\nRunning DetectFaceDemo");

// Create a face detector from the cascade file in the resources
// directory.
CascadeClassifier faceDetector
= new CascadeClassifier(“data/lbpcascades/lbpcascade_frontalface.xml”);
Mat image
= Highgui.imread(“resources/lena.png");

// Detect faces in the image.
// MatOfRect is a special container class for Rect.
MatOfRect faceDetections
= new MatOfRect();
faceDetector
.detectMultiScale(image, faceDetections);

System
.out.println(String.format("Detected %s faces", faceDetections.toArray().length));

// Draw a bounding box around each face.
for (Rect rect : faceDetections.toArray()) {
Core
.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0));
}

// Save the visualized detection.
String filename
= "faceDetection.png";
System
.out.println(String.format("Writing %s", filename));
Highgui
.imwrite(filename, image);
}
}

public class HelloOpenCV {
public static void main(String[] args) {
System
.out.println("Hello, OpenCV");

// Load the native library.
System
.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new DetectFaceDemo().run();
}
}




実行したら、次のファイルが生成されているはずです。

faceDetection


・おまけ



BufferedImage から Mat オブジェクトへの変換。
imgという名前でBufferedImageのオブジェクトが作成されている場合:

Pixels pixels = ((DataBufferByte)img.getRaster().getDataBuffer()).getData();
Mat mat = new Mat(img.getHeight(),img.getWidth(),CvType.CV_8UC3);
mat.put(0, 0, pixels);

これでmatに画像データがコピーされた行列ができます。
ちなみに
img.getRaster().getDataBuffer() の返り値がDataBufferIntで、
エラー:
java.awt.image.DataBufferInt cannot be cast to java.awt.image.DataBufferByte
が発生した場合(BufferedImageのタイプによります。
この辺この辺を参考に。)、


public byte[] getImageBytesForBufferedImageOfDataTypeSize1(BufferedImage image) {
int[] pixels = new int[image.getWidth()*image.getHeight()];
byte[] bytes = new byte[image.getWidth()*image.getHeight()*3];
DataBufferInt buffer = (DataBufferInt)(image.getRaster().getDataBuffer());

int j =0;
for(int i = 0; i < image.getWidth()*image.getHeight(); i++){
pixels[i] = buffer.getElem(i);

bytes[j] = (
byte)(0xFF & ((pixel[i] & 0x000000FF) >> 0)); //r
bytes[j+1] = (
byte)(0xFF & ((pixel[i] & 0x0000FF00) >> 8)); //g
bytes[j+2] = (
byte)(0xFF & ((pixel[i] & 0x00FF0000) >> 16)); //b
j+=3;
}
return bytes;
}



といった感じで強引にbytes[]に直す。

pixels
= getImageBytesForBufferedImageOfDataTypeSize1(img);
Mat mat = new Mat(img.getHeight(),img.getWidth(),CvType.CV_8UC3);
mat.put(0, 0, pixels);

で、Matオブジェクトが生成できます。


・おまけその2


ということで、AR.Droneと組み合わせて簡易的な顔認識・追従ができます。

Xuggleダウンロード&インストール

メモ。
Xuggleをダウンロードしてインストール、
ここで書かれているやり方で推奨されてるのはMavenかIvyを利用してやる方法。
実は密かにpre-compileバージョンも提供されている。
ここ

AR.Droneを無線ルータにつないでJava(Processing用ライブラリARDroneForP5)で制御

メモ。AR Droneをすでにある無線ルータにつないでProcessingで制御。

Pasted GraphicPasted Graphic 1

(1)AR.Droneを無線ルータにつなぐ
初期設定だとAR.Droneが提供するアクセスポイントにPC/iPhone/iPadがつなぎにいき、制御する。
これが嫌なので、AR Droneを無線ルータにつなぎたい。
ここをそのまま参考にする。

1. telnet でつなぐ
まずはAR Droneが提供するアクセスポイントにつなぎ、コンピュータからtelnetする。
%telnet 192.168.1.1

2. WiFiの設定スクリプトを書く
例えば環境にある無線ルータのessidが
hogehoge, AR Droneに設定したいIPがhoge.hoge.hoge.hoge, ネットマスクが255.255.254.0 の場合
どこかにmywifi.sh みたいなファイルを生成して中身をこう書く。

gpio 63 -d ho 1
killall udhcpd
ifconfig ath0 down
iwconfig ath0 essid “hogehoge”
iwconfig ath0 mode managed
iwconfig ath0 key off
iwconfig ath0 essid “hogehoge”
ifconfig ath0 hoge.hoge.hoge.hoge netmask 255.255.254.0 up
ifconfig ath0
iwconfig ath0
iwlist ath0 encryption
gpio 63 -d ho 0


上記リンクそのまま。

3. とりあえずテスト
%chmod 755 mywifi.sh
%./mywifi.sh

これでtelnetがきれるので、コンピュータを同じ無線ルータにつなぎ、設定したIPにつなげれるかテスト。
%telnet hoge.hoge.hoge.hoge

つながったら、OK

4. AR DroneのWiFi設定ファイルに書き込む
/bin/wifi_setup.sh の一番最後に、
/スクリプトを保存したディレクトリ/mywifi.sh
と書く。そうすると、AR Droneの初期設定が終わったあと、強引にその初期設定を無視して新しくWiFIにつながるようになる。

(2)Java(Processing用ライブラリARDroneForP5)でWiFIにつながったAR Droneを制御
ここにProcessing用AR Drone制御ライブラリが提供されているので、有り難くダウンロードさせていただく。
eclipseに読み込むと、後々の開発が楽。
とりあえず、サンプルの examples/ARDroneTest.java を動かしたい。
普通に考えるとARDroneTest.java内の、

private void initialize() {
ardrone = new ARDrone(“192.168.1.1”);

ここの部分を設定したIPに書き換えると動きそうだが、
ardrone = new ARDrone(“hoge.hoge.hoge.hoge”);

しかし動かない。Why ?
ardrone.connect(); 部分でこけてるっぽい。

よってライブラリを変更必要があるので、ARDroneForP5.jar は使わないようにする。
で、ライブラリのソースファイルを変更する。

ソースを見ると、ardrone.connect()は内部でARDroneInfo呼び出してそこの中の
connectToDroneThroughFtp() Droneに接続しにいっている。
ただし、その中で指定されているIP
         client.connect(ARDroneConstants.IP_ADDRESS, ARDroneConstants.FTP_PORT);
となっていて、ARDroneConstants.IP_ADDRESS が決め打ちの”192.168.1.1” を参照しているため、examples/ARDroneTest.javaで ardrone = new ARDrone(“hoge.hoge.hoge.hoge”);
として指定してやったIPアドレスが反映されていない。

ARDroneInfo.java のコンストラクタを変えてやる

public ARDroneInfo() {
connectToDroneThroughFtp();
}



を、

private final InetAddress addr;

public ARDroneInfo(final InetAddress addr) {
this.addr = addr;
connectToDroneThroughFtp();
}



としてやり、同じプログラム内の
         client.connect(ARDroneConstants.IP_ADDRESS, ARDroneConstants.FTP_PORT);
これを、
client.connect(this.addr, ARDroneConstants.FTP_PORT);
これに変えてやる。
ARDroneInfoはARDrone.javaからしか参照されてなさそうなので、他に影響はなくて、大丈夫だと思います。

で、ARDroneInfoを呼び出す元の、com.shigeodayo.ardrone.ARDrone.java内の

if (ardroneVersion == null)
ardroneVersion = new ARDroneInfo().getDroneVersion();

この部分を

if (ardroneVersion == null)
ardroneVersion = new ARDroneInfo(inetaddr).getDroneVersion();


これに変えてやる。そうすると、無線ルータにつながったARDroneにきちんとつながりました。

おまけ:自分でソースを直すのがめんどい人は、
com.shigeodayo.ardrone.utils.ARDroneInfo.java を
これ
com.shigeodayo.ardrone.ARDrone.javaを
これ

置き換えると、すぐに動きます。
tomotakaさん、ありがとうございました。