OpenCV 2.4.4から正式にJava APIをAndroidプラットフォーム以外でも使えるようになりました。つまりJVM(Java Virtual Machine)上で動く言語ならどんな言語からでもOpenCVが使えるということですね。
* OpenCV now supports desktop Java
、ということで今回はJVM上で動作するScalaからOpenCVを使ってみます。
環境
* CentOS 5.8 (x86_64, 仮想2コア, 1GB RAM)
* Scala 2.10.0 (sbt 0.12)
* Java 1.6.0 (java-1.6.0-openjdk.x86_64)
* OpenCV 2.4.4
Java APIを使う際にAndroidプラットフォームを意識する必要はもうなくなりましたので、ここではアプリケーションサーバから使えるようにLinux機に環境構築して試しました。OpenCVのLinuxへのインストール手順については技術Wikiにメモを残していますのでそちらを参照してください。
* OpenCV – 2.x Tech Note
公式サイトではeclipseやsbtなどでのプロジェクト構築手順もスクリーンショット付きで載っているのでこちらも参考に。
* Introduction to Java Development
ちなみにMac OSXではHomebrewのOpenCVが2.4.4にアップデートされていたので、
1 |
$ brew install opencv |
でインストールできます。簡単ですけどcmakeのオプションを自由にいじれないのでオススメできないかも。
sbt(Simple Build Tool)
sbt(Simple Build Tool)はScala/Java用のビルドツール。ライブラリの依存関係を自動で解決してくれるし、Scalaで書かれたDSLを使ってビルド設定を簡潔に記述できます。今回はEclipseのようなIDEは使わずにemacs + sbtでストレスなく開発ができました。
参考: 始める sbt
OpenCVのインストールに成功したら、ライブラリファイル(opencv-244.jar, libopencv_java244.so)をsbtプロジェクトのlibディレクトリにコピーしておきます。sbtが自動でクラスパスを追加してくれます。
Hello OpenCV in Scala
Javadocを参考にしながら進めます。
* OpenCV Javadoc
core
まずは基本から。JNI(Java Native Interface)経由でMatの機能を使えるか試します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import org.opencv.core.Core import org.opencv.core.Mat import org.opencv.core.CvType import org.opencv.core.Scalar object HelloOpenCV { def main(args:Array[String]) { println("Welcome to OpenCV " + Core.VERSION) // 共有ライブラリ(libopencv_java244.so)をロード System.loadLibrary(Core.NATIVE_LIBRARY_NAME) println("Welcome to OpenCV " + Core.VERSION) // 5行10列1チャンネルの行列(要素の型は符号無し8ビット整数)を生成 val m1 = new Mat(5, 10, CvType.CV_8UC1, new Scalar(0)) println("OpenCV Mat: " + m1) // 2行目(インデックスは0から)を取得 val m1r1 = m1.row(1) // 2行目の要素の値を全て1にセット m1r1.setTo(new Scalar(1)) // 6列目(インデックスは0から)を取得 val m1c5 = m1.col(5) // 6列目の要素の値を全て5にセット m1c5.setTo(new Scalar(5)) println("OpenCV Mat data:\n" + m1.dump()) // 3行3列1チャンネルの行列(要素の型は32ビット浮動小数点数)を生成 val m2 = new Mat(3, 3, CvType.CV_32FC1) // 一様分布乱数(0~25)を使って行列の要素に値をセット Core.randu(m2, 0, 25) println(m2.dump()) val (v1, v2, v3, v4) = (new Mat, new Mat, new Mat, new Mat) // 行列を1行に縮小 (要素の値は合計/平均/最小/最大) Core.reduce(m2, v1, 0, Core.REDUCE_SUM) Core.reduce(m2, v2, 0, Core.REDUCE_AVG) Core.reduce(m2, v3, 0, Core.REDUCE_MIN) Core.reduce(m2, v4, 0, Core.REDUCE_MAX) println("reduce sum: " + v1.dump()) println("reduce avg: " + v2.dump()) println("reduce min: " + v3.dump()) println("reduce max: " + v4.dump()) } } |
* 実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ sbt run [info] Loading project definition from /home/ryo/workspace/src/opencv/samples/project [info] Set current project to HelloOpenCVScala (in build file:/home/ryo/workspace/src/opencv/samples/) [info] Compiling 1 Scala source to /home/ryo/workspace/src/opencv/samples/target/scala-2.10/classes... [info] Running Main Welcome to OpenCV 2.4.4.0 Mat: Mat [ 5*10*CV_8UC1, isCont=true, isSubmat=false, nativeObj=0xfe61740, dataAddr=0xfe61800 ] Mat data: [0, 0, 0, 0, 0, 5, 0, 0, 0, 0; 1, 1, 1, 1, 1, 5, 1, 1, 1, 1; 0, 0, 0, 0, 0, 5, 0, 0, 0, 0; 0, 0, 0, 0, 0, 5, 0, 0, 0, 0; 0, 0, 0, 0, 0, 5, 0, 0, 0, 0] [13.25707, 4.9814796, 10.026485; 20.359627, 10.928325, 6.2197423; 19.327625, 19.052343, 7.6948619] reduce sum: [52.944321, 34.962147, 23.94109] reduce avg: [17.648108, 11.654049, 7.9803634] reduce min: [13.25707, 4.9814796, 6.2197423] reduce max: [20.359627, 19.052343, 10.026485] [success] Total time: 11 s, completed 2013/03/17 17:43:34 |
行列演算はネイティブ側で行われるので高速に処理できます。
highgui, imgproc
ファイルI/Oや画像処理も試してみます。sbtプロジェクトの src/main/resources ディレクトリに画像ファイルを置いておきます。
1 2 3 4 5 6 7 8 9 10 11 |
// 画像ファイルへのパスを取得 val filePath = getClass.getResource("/src.png").getPath // 画像ファイルの読み込み val src = Highgui.imread(filePath) // Cannyフィルタでエッジ検出 val edge = new Mat Imgproc.Canny(src, edge, 80, 100) // 半分の大きさにリサイズ Imgproc.resize(edge, edge, new Size(), 0.5, 0.5, Imgproc.INTER_AREA) // 画像ファイルの書き込み Highgui.imwrite("edge.png", edge) |
Highgui周りはちゃんと動くか心配してたのですが、I/O処理は特に問題ないようでした。
features2d
特徴点検出/特徴量記述、マッチング処理も試してみました。特徴量はパテントフリーで使いやすいORB特徴を使っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// ORB特徴点検出 val detect = (mat:Mat) => { val keypoints = new MatOfKeyPoint FeatureDetector.create(FeatureDetector.ORB).detect(mat, keypoints) (mat, keypoints) } // ORB特徴量記述 val extract = (t:Tuple2[Mat, MatOfKeyPoint]) => { val descriptors = new Mat DescriptorExtractor.create(DescriptorExtractor.ORB).compute(t._1, t._2, descriptors) (t._2, descriptors) } // 関数合成 val detectAndExtract = extract compose detect // 画像ファイル読み込み val img1 = Highgui.imread(getClass.getResource("/img1.png").getPath) val img2 = Highgui.imread(getClass.getResource("/img2.png").getPath) // 合成関数を適用して特徴点(キーポイント)検出 & 特徴量記述 val (keyPoints1, descriptors1) = detectAndExtract(img1) val (keyPoints2, descriptors2) = detectAndExtract(img2) // マッチング (ハミング距離) val matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMINGLUT) val matches = new MatOfDMatch // Scalaでは match は予約語のためバッククォートで括る matcher.`match`(descriptors1, descriptors2, matches) // 距離が近い上位100点を選択 val filtered = matches.toArray.sortBy(_.distance).reverse.take(100) val filteredMatches = new MatOfDMatch(filtered:_*) // マッチング結果を画像ファイルに描画 val resultImg = new Mat Features2d.drawMatches(img1, keyPoints1, img2, keyPoints2, filteredMatches, resultImg) Highgui.imwrite("result.png", resultImg) |
多少は関数型っぽくしてみましたけど、Javaの道具を使ってるので普通のJavaプログラムと変わらないように見えてしまうのは仕方ないですかね。
C++ APIで提供されている FeatureDetector/DescriptorExtractor クラスの機能はJava APIでもほぼそのまま利用できるようです。また、上記のサンプルコード内で MatOfKeyPoint や MatOfDMatch というクラスが登場していますが、どうやらJava側では std::vector を Mat のサブクラスとして扱い、JNIで渡すときに内部でコンテナ変換しているようです。ちなみにその変換部分の実装はOpenCVソースツリーの modules/java/generator/src/cpp/generators.cpp にあります。
C++コンテナ | Javaクラス |
---|---|
std::vector<{型名}> |
org.opencv.core.MatOf{型名} |
それぞれのクラスには toArray や toList メソッドが用意されていて、配列やリスト(java.util.List)のインスタンスに変換できるので運用はしやすいと思います。
以上、簡単にですがOpenCVのJava APIをScalaから使ってみました。他のJVM言語からでも簡単に使えそうなので興味ある方は是非試してみてください。
Scalaを使ってみた感想
ScalaにはREPL(Read-Eval-Print-Loop: 対話型評価環境)があるので思いついた処理を手軽に試すことができて良いですね。それに最近のJVMは速いのでパフォーマンス的にもあまり心配することはなさそうです。
一方、Scalaがどういう風に型推論するのか見切れないから結局は型を明示してしまうという、僕のようなScala初心者は多いのではないかと思います。ScalaでRubyのようにスラスラ書き下すのは難しいです。。また、ScalaをBetter Javaとして使うだけなら学習コストは低いですが、関数型言語としてしっかり使っていくにはかなり学習が必要だと感じました。
また、ScalaはTwitterでの利用実績があることは有名だと思いますが、中の人がEffective Scala(邦訳)というドキュメントを残しています。Scalaを勉強するなら一度は読んでみると良いと思います。
Am aflat aceasta pagina, dupa ce am cautat despre
ScalaでOpenCVを使って画像処理 Rest Term
pe Google. Se pare ca informatia dvs e foarte valoroasa, mai ales ca am mai gasit aici si despre ora, ora exacta,
lucruri interesante si folositoare. Mult succes in continuare!