MacのIntelliJ IDEAでJavaからOpenCVを使うイントロダクションをやったときのメモ
Mac で OpenCV の公式イントロダクション 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 ディレクトリを追加してやるとコード的に見えるようになりました。
ガイドの通り 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 を設定します。
これで、 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
きました。めでたい。以上です。
オプションですが、上の方法以外にも環境変数 SBT_OPTS
を設定してやることで動かせます。
$ SBT_OPTS="-Djava.library.path=/usr/local/share/OpenCV/java/" sbt run
あるいは、 build.sbt
で javaOptions
を設定してやることでも動きはしました(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 のサーバーにデプロイして動かしておこうと思っているのですが環境構築は Java と OpenCV だけ用意しておけばよさそうなので簡単です(その OpenCV がけっこうデカくはあるんですが)。画像の URL を返す API を別途用意して、それをもとに画像ファイルをダウンロードして OpenCV に食わせていろいろやっていくプログラムになる予定です。
ほんとは OpenCV も全部 jar に含めて、 jar さえあれば OK という状態にしたかったので、誰か教えてください。 Homebrew で入れずにちゃんとソースから make すればできるのかも。
って思ったけど、これ How to package opencv +java in a jar - Stack Overflow を読んで、 .dylib やら .dll やら .so やらというファイルが環境によって違うんだから、ぜんぶ同梱してもいいけどまあプログラムを実行するところそれぞれでそれは用意しといてやるのが無難だという気がしました。なるほど。