MacのIntelliJ IDEAでJavaからOpenCVを使うイントロダクションをやったときのメモ

MacOpenCV の公式イントロダクション Introduction to Java Development — OpenCV 2.4.12.0 documentation を進めたときのメモです。 IntelliJ IDEA を使って Java から OpenCV したいと思います。

はじめに

バージョンです。

OpenCV のインストール

Homebrew — The missing package manager for OS X 便利です。

$ brew update
$ brew search opencv
$ brew tap homebrew/science
$ brew info opencv
$ brew install opencv --with-java

SBT project for Java and Scala をやる

SBT project for Java and Scala の章をやっていきます。

$ mkdir JavaSample
$ cd JavaSample
$ mkdir -p src/main/java
$ mkdir project

project/JavaSampleBuild.scala を作ります。サンプルから scalaVersion を変更。また Project.defaultSettings が deprecated であるという warn が出ていたので、それを Defaults.coreDefaultSettings に置き換えてやりました。

import sbt._
import Keys._

object JavaSampleBuild extends Build {
  def scalaSettings = Seq(
    scalaVersion := "2.11.8",
    scalacOptions ++= Seq(
      "-optimize",
      "-unchecked",
      "-deprecation"
    )
  )

  def buildSettings =
    Defaults.coreDefaultSettings ++
      scalaSettings

  lazy val root = {
    val settings = buildSettings ++ Seq(name := "JavaSample")
    Project(id = "JavaSample", base = file("."), settings = settings)
  }
}

Hello, OpenCV やります。 src/main/java/HelloOpenCV.java を作って実行してみます。

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

よさそうです。

今回は Eclipse を使わないので Eclipse 用の準備は飛ばしました。

Running SBT samples をやる

Running SBT samples の章をやっていきます。

lib に OpenCV の jar ファイルをコピーします。 Homebrew で入れると /usr/local/share/OpenCV/usr/local/Cellar/opencv/2.4.12_2/share/OpenCV/ へのシンボリックリンクになっているようですね。 xml ファイルも resources にコピーします。

$ mkdir lib
$ cp /usr/local/share/OpenCV/java/opencv-2412.jar lib/
$ mkdir src/main/resources
$ cp /usr/local/share/OpenCV/lbpcascades/lbpcascade_frontalface.xml src/main/resources/

さきほど作った src/main/java/HelloOpenCV.java を拡張します。

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(getClass().getResource("/lbpcascade_frontalface.xml").getPath());
    Mat image = Highgui.imread(getClass().getResource("/lena.png").getPath());

    // 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();
  }
}

IntelliJ IDEA では、 File > Project Structure… ⌘; の Modules から、さっき作った lib ディレクトリを追加してやるとコード的に見えるようになりました。

f:id:yonexyonex:20160321000339p:plain

ガイドの通り lena.png をダウンロードしておきます。

$ curl -o src/main/resources/lena.png http://docs.opencv.org/2.4/_images/lena1.png

run してみますが、ここで次のようなエラー。

$ sbt run
[info] Loading project definition from /Users/yonex/tmp/JavaSample/project
[info] Set current project to JavaSample (in build file:/Users/yonex/tmp/JavaSample/)
[info] Compiling 1 Java source to /Users/yonex/tmp/JavaSample/target/scala-2.11/classes...
[info] Running HelloOpenCV 
Hello, OpenCV
[error] (run-main-0) java.lang.UnsatisfiedLinkError: no opencv_java2412 in java.library.path
java.lang.UnsatisfiedLinkError: no opencv_java2412 in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1864)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)
    at HelloOpenCV.main(HelloOpenCV.java:47)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
[trace] Stack trace suppressed: run last compile:run for the full output.
java.lang.RuntimeException: Nonzero exit code: 1
    at scala.sys.package$.error(package.scala:27)
[trace] Stack trace suppressed: run last compile:run for the full output.
[error] (compile:run) Nonzero exit code: 1
[error] Total time: 2 s, completed 2016/03/20 19:50:15

java.library.path を設定してやる必要がありそうです。こちら IntelliJ IDEA で Scala + OpenCVの環境構築メモ (Mac) - Qiita が参考になりました。 Run > Edit Configurations… から VM options を設定します。

f:id:yonexyonex:20160321000416p:plain

これで、 IntelliJ IDEA 上では Run できるようになりました。 sbt run のオプションでも同じように指定してやると動きます。

$ sbt run -Djava.library.path=/usr/local/share/OpenCV/java/
[info] Loading project definition from /Users/yonex/tmp/JavaSample/project
[info] Set current project to JavaSample (in build file:/Users/yonex/tmp/JavaSample/)
[info] Running HelloOpenCV 
Hello, OpenCV

Running DetectFaceDemo
Detected 1 faces
Writing faceDetection.png
[success] Total time: 0 s, completed 2016/03/20 19:56:34

faceDetection.png が生成されているので、

$ open faceDetection.png

f:id:yonexyonex:20160321000458p:plain

きました。めでたい。以上です。

オプションですが、上の方法以外にも環境変数 SBT_OPTS を設定してやることで動かせます。

$ SBT_OPTS="-Djava.library.path=/usr/local/share/OpenCV/java/" sbt run

あるいは、 build.sbtjavaOptions を設定してやることでも動きはしました(sbt Reference Manual — Forking

fork in run := true

javaOptions in run += "-Djava.library.path=/usr/local/share/OpenCV/java/"

おまけ:単体で配布可能な jar を作りたかった

サンプルプロジェクトをそのまま jar にして実行できないか試しましたがダメでした。

sbt/sbt-assembly: Deploy fat JARs. Restart processes. (port of codahale/assembly-sbt) を使って jar を作ってみます。 README を読みつつ project/assembly.sbt を追加します。

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.2")
$ sbt assembly

target/scala-2.11/JavaSample-assembly-0.1-SNAPSHOT.jar ができたので実行してみます、が

$ java -Djava.library.path=/usr/local/share/OpenCV/java/ -jar target/scala-2.11/JavaSample-assembly-0.1-SNAPSHOT.jar
Hello, OpenCV

Running DetectFaceDemo
Detected 0 faces
Writing faceDetection.png
libpng warning: Image width is zero in IHDR
libpng warning: Image height is zero in IHDR
libpng error: Invalid IHDR data

画像ファイルを上手く読めてなさそうなエラー。 jar を unzip して中身を確認しても画像はちゃんと存在しているのでなんでかと思ったら、 jar の中の resource は opencv から読めないとか。

java - How to get resource files from the runnable jar file which are in buildpath - Stack Overflow

なるほど、そうなんですか…。ということは、読み込ませたい画像ファイルと、あと xml ファイルもありましたが、それらは jar に含められないですね。画像はともかく、この xml を入れられないのはちょっと不便な気がします。

あとがき

OpenCV デビューできました。これから作るプログラムはたとえば CentOS のサーバーにデプロイして動かしておこうと思っているのですが環境構築は JavaOpenCV だけ用意しておけばよさそうなので簡単です(その OpenCV がけっこうデカくはあるんですが)。画像の URL を返す API を別途用意して、それをもとに画像ファイルをダウンロードして OpenCV に食わせていろいろやっていくプログラムになる予定です。

ほんとは OpenCV も全部 jar に含めて、 jar さえあれば OK という状態にしたかったので、誰か教えてください。 Homebrew で入れずにちゃんとソースから make すればできるのかも。

って思ったけど、これ How to package opencv +java in a jar - Stack Overflow を読んで、 .dylib やら .dll やら .so やらというファイルが環境によって違うんだから、ぜんぶ同梱してもいいけどまあプログラムを実行するところそれぞれでそれは用意しといてやるのが無難だという気がしました。なるほど。