前のチュートリアル: EclipseでOpenCV Javaを使う
次のチュートリアル: Android開発入門
| |
| 原著者 | Mimmo Cosenza |
| 互換性 | OpenCV >= 3.0 |
- 警告
- このチュートリアルには古い情報が含まれている可能性がある。
OpenCV 2.4.4以降、OpenCVはAndroid開発とほぼ同じインターフェースを用いてデスクトップ向けのJava開発をサポートしている。
ClojureはJava仮想マシン上で動作する現代的なLISP方言であり、基盤となるJVMとの完全な相互運用性を提供する。つまり、ClojureのREPL (Read Eval Print Loop) を、基盤となるOpenCVエンジンに対するインタラクティブでプログラム可能なインターフェースとして利用できるということである。
このチュートリアルで行うこと
このチュートリアルは、完全にプログラム可能なClojure REPLの中でOpenCVをインタラクティブに学習するための基本的なClojure環境をセットアップする手助けとなる。
チュートリアルのソースコード
サンプルの実行可能なソースコードは、OpenCVリポジトリのsamples/java/clojure/simple-sampleフォルダにある。チュートリアルで説明されているとおりにOpenCVとClojureをインストールした後、次のコマンドを実行するとコマンドラインからサンプルを実行できる。
cd path/to/samples/java/clojure/simple-sample
lein run
はじめに
デスクトップJavaサポート付きでOpenCVをインストールする詳しい手順については、対応するチュートリアルを参照のこと。
急いでいる場合は、Mac OS XにOpenCVをインストールするための最小限のクイックスタートガイドを以下に示す。
- 覚え書き
- ここでは、xcode、jdk、Cmakeがすでにインストールされていることを前提とする。
cd ~/
mkdir opt
git clone https://github.com/opencv/opencv.git
cd opencv
git checkout 2.4
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=OFF ..
...
...
make -j8
# optional
# make install
Leiningen のインストール
デスクトップjavaサポート付きでOpenCVをインストールしたら、あと必要なのはLeiningengをインストールすることだけである。これによりCLJプロジェクトのライフサイクル全体を管理できる。
提供されているインストールガイドはとても簡単に従える。
- スクリプトをダウンロードする
- $PATHの通った場所に配置する(パスが通っていれば cf./bin が良い選択である)。
- スクリプトに実行権限を付与する(つまり chmod 755/bin/lein)。
Windowsで作業している場合は、この手順に従うこと
これでOpenCVライブラリと、完全にインストールされた基本的なClojure環境の両方が揃った。次に必要なのは、OpenCVライブラリと連携するようにClojure環境を構成することである。
localrepo Leiningen プラグインのインストール
Leiningenがネイティブにサポートするコマンド群(Leiningenの用語ではタスク)は、さまざまなプラグインによってとても簡単に拡張できる。その一つがlein-localrepoプラグインであり、任意のjarライブラリをマシンのローカルmavenリポジトリ(通常はユーザー名の/.m2/repositoryディレクトリ)にアーティファクトとしてインストールできる。
このleinプラグインを使って、JavaとClojureがopencvライブラリを使うために必要なopencvコンポーネントをローカルmavenリポジトリに追加する。
一般的に言って、あるプラグインをプロジェクト単位でのみ使いたい場合は、leinで作成したCLJプロジェクトに直接追加できる。
一方、あるプラグインをユーザー名空間内のすべてのCLJプロジェクトで利用できるようにしたい場合は、/.lein/ディレクトリ内のprofiles.cljに追加できる。
lein-localrepoプラグインは、Javaインターフェースでラップされたネイティブライブラリを呼び出す必要がある他のCLJプロジェクトでも私にとって役立つ。そこで、すべてのCLJプロジェクトで利用できるようにすることにした。
/.leinディレクトリにprofiles.cljという名前のファイルを作成し、次の内容をその中にコピーする。
{:user {:plugins [[lein-localrepo "0.5.2"]]}}
ここでは、lein-localrepoプラグインのバージョンリリース「0.5.2」が、leinで作成された任意のCLJプロジェクトの:userプロファイルで利用可能になることを指定している。
プラグインをインストールするために他に何かする必要はない。最初に何らかのleinタスクを実行したときに、リモートリポジトリから自動的にダウンロードされるからである。
Java 固有のライブラリをローカルリポジトリとしてインストールする
コンピュータにOpenCVをインストールする標準的なドキュメントに従った場合、OpenCVをビルドしたディレクトリの下に次の2つのライブラリがあるはずである。
- build/bin/opencv-247.jar javaライブラリ
- build/lib/libopencv_java247.dylib ネイティブライブラリ(GNU/Linux OSでOpenCVをビルドした場合は.so)
これらはJVMがOpenCVと連携するために必要な唯一のopencvライブラリである。
必要な opencv ライブラリの分解
上記の2つのライブラリを格納する新しいディレクトリを作成する。まずopencv-247.jarライブラリをその中にコピーするところから始める。
cd ~/opt
mkdir clj-opencv
cd clj-opencv
cp ~/opt/opencv/build/bin/opencv-247.jar .
1つ目のライブラリは完了である。
次に、libopencv_java247.dylib共有ネイティブライブラリをローカルmavenリポジトリに追加できるようにするには、まずそれをjarファイルとしてパッケージ化する必要がある。
ネイティブライブラリは、オペレーティングシステムとアーキテクチャの名前を模したディレクトリ構成の中にコピーする必要がある。私はX86 64ビットアーキテクチャのMac OS Xを使っている。したがって、私の構成は次のようになる。
mkdir -p native/macosx/x86_64
x86_64ディレクトリにlibopencv_java247.dylibライブラリをコピーする。
cp ~/opt/opencv/build/lib/libopencv_java247.dylib native/macosx/x86_64/
別のOS/アーキテクチャの組み合わせでOpenCVを実行している場合は、選択できるマッピングの一覧を以下に示す。
OS
Mac OS X -> macosx
Windows -> windows
Linux -> linux
SunOS -> solaris
Architectures
amd64 -> x86_64
x86_64 -> x86_64
x86 -> x86
i386 -> x86
arm -> arm
sparc -> sparc
ネイティブライブラリを jar としてパッケージ化する
次に、jarコマンドを使ってディレクトリから新しいjarファイルを作成し、ネイティブライブラリをjarファイルにパッケージ化する必要がある。
jar -cMf opencv-native-247.jar native
Mオプションは、アーティファクトのMANIFESTファイルを作成しないようjarコマンドに指示することに注意する。
ディレクトリ構成は次のようになっているはずである。
tree
.
|__ native
| |__ macosx
| |__ x86_64
| |__ libopencv_java247.dylib
|
|__ opencv-247.jar
|__ opencv-native-247.jar
3 directories, 3 files
jar をローカルにインストールする
これで、lein-localrepoプラグインの助けを借りて、2つのjarをアーティファクトとしてローカルmavenリポジトリに追加する準備が整った。
lein localrepo install opencv-247.jar opencv/opencv 2.4.7
ここでlocalrepo installタスクは、opencv-247.jarライブラリからopencv/opencv mavenアーティファクトの2.4.7.リリースを作成し、それをローカルmavenリポジトリにインストールする。これによりopencv/opencvアーティファクトは、maven準拠のあらゆるプロジェクトで利用可能になる(Leiningenは内部的にmavenをベースにしている)。
新しいjarファイルにラップしておいたネイティブライブラリについても、同じことを行う。
lein localrepo install opencv-native-247.jar opencv/opencv-native 2.4.7
2つのアーティファクトのgroupIdであるopencvが同じであることに注意する。これで、OpenCVと連携を始めるための新しいCLJプロジェクトを作成する準備が整った。
プロジェクトの作成
ターミナルからlein newタスクを使って新しいCLJプロジェクトを作成する。
# cd in the directory where you work with your development projects (e.g. ~/devel)
lein new simple-sample
Generating a project called simple-sample based on the 'default' template.
To see other templates (app, lein plugin, etc), try `lein help new`.
上記のタスクは、次のようなsimple-sampleディレクトリ構成を作成する。
tree simple-sample/
simple-sample/
|__ LICENSE
|__ README.md
|__ doc
| |__ intro.md
|
|__ project.clj
|__ resources
|__ src
| |__ simple_sample
| |__ core.clj
|__ test
|__ simple_sample
|__ core_test.clj
6 directories, 6 files
新しく作成したプロジェクトの依存関係として、2つのopencvアーティファクトを追加する必要がある。project.cljを開き、その依存関係セクションを次のように変更する。
(defproject simple-sample "0.1.0-SNAPSHOT"
description "FIXME: write description"
url "http://example.com/FIXME"
license {:name "Eclipse Public License"
url "http://www.eclipse.org/legal/epl-v10.html"}
dependencies [[org.clojure/clojure "1.5.1"]
[opencv/opencv "2.4.7"] ; added line
[opencv/opencv-native "2.4.7"]]) ;added line
Clojureプログラミング言語自体もjarアーティファクトであることに注意する。これがClojureがホスト言語と呼ばれる理由である。
すべてが正しく行われたか確認するには、lein depsタスクを実行する。最初にleinタスクを実行するときは、タスク自体を実行する前に必要な依存関係をすべてダウンロードするため、しばらく時間がかかる。
cd simple-sample
lein deps
...
depsタスクは、project.cljと/.lein/profiles.cljのファイルからsimple-sampleプロジェクトのすべての依存関係を読み込んでマージし、それらがすでにローカルmavenリポジトリにキャッシュされているかを検証する。2つの新しいアーティファクトを取得できないというメッセージなしにタスクが終了すれば、インストールは正しい。そうでなければ、戻ってすべてを正しく行ったか再確認すること。
OpenCV で REPL を使う
次にsimple-sampleディレクトリにcdし、次のleinタスクを実行する。
cd simple-sample
lein repl
...
...
nREPL server started on port 50907 on host 127.0.0.1
REPL-y 0.3.0
Clojure 1.5.1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=>
任意のCLJ式を評価のために入力することで、REPLとすぐにインタラクティブにやり取りできる。
user=> (+ 41 1)
42
user=> (println "Hello, OpenCV!")
Hello, OpenCV!
nil
user=> (defn foo [] (str "bar"))
#'user/foo
user=> (foo)
"bar"
leinベースのプロジェクトのホームディレクトリから実行した場合、lein replタスクはすべてのプロジェクト依存関係を自動的に読み込むものの、OpenCVと連携できるようにするにはopencvネイティブライブラリを読み込む必要がある。
user=> (clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)
nil
その後、クラスの完全修飾名を参照するだけでOpenCVとの連携を始められる。
- 覚え書き
- こちら で OpenCV Java API の全体を確認できる。
user=> (org.opencv.core.Point. 0 0)
#<Point {0.0, 0.0}>
ここでは2次元のopencv Pointインスタンスを作成した。OpenCVへのjavaインターフェースに含まれるすべてのjavaパッケージはCLJ REPLから即座に利用できるが、Point.インスタンスのコンストラクタに完全修飾パッケージ名を付けるのはとても煩わしい。
幸い、CLJはPointクラスを直接インポートすることで、この煩わしさを解消するとても簡単な方法を提供している。
user=> (import 'org.opencv.core.Point)
org.opencv.core.Point
user=> (def p1 (Point. 0 0))
#'user/p1
user=> p1
#<Point {0.0, 0.0}>
user=> (def p2 (Point. 100 100))
#'user/p2
あるインスタンスのクラスを調べ、シンボルの値がPoint javaクラスのインスタンスかどうかを検証することさえできる。
user=> (class p1)
org.opencv.core.Point
user=> (instance? org.opencv.core.Point p1)
true
次にopencv Rectクラスを使って矩形を作成したい場合、たとえそれがPointクラスと同じorg.opencv.coreパッケージにあっても、再びそのコンストラクタを完全修飾しなければならない。
user=> (org.opencv.core.Rect. p1 p2)
#<Rect {0, 0, 100x100}>
ここでもCLJのインポート機能はとても便利で、一度に複数のシンボルをマッピングできる。
user=> (import '[org.opencv.core Point Rect Size])
org.opencv.core.Size
user=> (def r1 (Rect. p1 p2))
#'user/r1
user=> r1
#<Rect {0, 0, 100x100}>
user=> (class r1)
org.opencv.core.Rect
user=> (instance? org.opencv.core.Rect r1)
true
user=> (Size. 100 100)
#<Size 100x100>
user=> (def sq-100 (Size. 100 100))
#'user/sq-100
user=> (class sq-100)
org.opencv.core.Size
user=> (instance? org.opencv.core.Size sq-100)
true
もちろんインスタンスに対してメソッドを呼び出すこともできる。
user=> (.area r1)
10000.0
user=> (.area sq-100)
10000.0
あるいはメンバフィールドの値を変更することもできる。
user=> (set! (.x p1) 10)
10
user=> p1
#<Point {10.0, 0.0}>
user=> (set! (.width sq-100) 10)
10
user=> (set! (.height sq-100) 10)
10
user=> (.area sq-100)
100.0
OpenCVクラスの振る舞いを思い出せないときは、REPLが対応するjavadocドキュメントを簡単に検索する手段を提供してくれる。
user=> (javadoc Rect)
"http://www.google.com/search?btnI=I%27m%20Feeling%20Lucky&q=allinurl:org/opencv/core/Rect.html"
REPL で OpenCV Java チュートリアルのサンプルを再現する
ここではOpenCV Java tutorial sampleをClojureに移植してみよう。ソースファイルに書く代わりに、REPL上で評価していく。
以下は引用したサンプルの元のJavaソースコードである。
import org.opencv.core.Mat;
import org.opencv.core.CvType;
import org.opencv.core.Scalar;
class SimpleSample {
static{ System.loadLibrary("opencv_java244"); }
public static void main(String[] args) {
Mat m = new Mat(5, 10, CvType.CV_8UC1, new Scalar(0));
System.out.println("OpenCV Mat: " + m);
Mat mr1 = m.row(1);
mr1.setTo(new Scalar(1));
Mat mc5 = m.col(5);
mc5.setTo(new Scalar(5));
System.out.println("OpenCV Mat data:\n" + m.dump());
}
}
int main(int argc, char *argv[])
Definition highgui_qt.cpp:3
プロジェクトにインジェクションを追加する
コーディングを始める前に、対話するために新しいREPLを起動するたびにネイティブのopencvライブラリを対話的にロードするという退屈な手間をなくしておきたい。
まず、REPLプロンプトで (exit) 式を評価してREPLを停止する。
user=> (exit)
Bye for now!
次に project.clj ファイルを開き、以下のように編集する。
(defproject simple-sample "0.1.0-SNAPSHOT"
...
injections [(clojure.lang.RT/loadLibrary org.opencv.core.Core/NATIVE_LIBRARY_NAME)])
ここでは、REPLを実行するたびにopencvネイティブライブラリをロードするよう指定しており、手動でロードすることを覚えておく必要がなくなる。
lein repl タスクを再実行する
lein repl
nREPL server started on port 51645 on host 127.0.0.1
REPL-y 0.3.0
Clojure 1.5.1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=>
対象となるOpenCVのjavaインタフェースをインポートする。
user=> (import '[org.opencv.core Mat CvType Scalar])
org.opencv.core.Scalar
ここでは元のOpenCV javaチュートリアルをほぼそのまま再現し、次のことを行う。
- 全要素を0で初期化した5x10の行列を作成する
- 2行目の各要素の値を1に変更する
- 6列目の各要素の値を5に変更する
- 得られた行列の内容を表示する
user=> (def m (Mat. 5 10 CvType/CV_8UC1 (Scalar. 0 0)))
#'user/m
user=> (def mr1 (.row m 1))
#'user/mr1
user=> (.setTo mr1 (Scalar. 1 0))
#<Mat Mat [ 1*10*CV_8UC1, isCont=true, isSubmat=true, nativeObj=0x7fc9dac49880, dataAddr=0x7fc9d9c98d5a ]>
user=> (def mc5 (.col m 5))
#'user/mc5
user=> (.setTo mc5 (Scalar. 5 0))
#<Mat Mat [ 5*1*CV_8UC1, isCont=false, isSubmat=true, nativeObj=0x7fc9d9c995a0, dataAddr=0x7fc9d9c98d55 ]>
user=> (println (.dump m))
[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]
nil
関数型言語に慣れている人なら、これら濫用され変化する名詞の数々は、動詞を好むあなたの嗜好を苛立たせるだろう。CLJのinterop構文は非常に扱いやすく完全であるとはいえ、あらゆるOOP言語とあらゆるFP言語の間にはなおインピーダンスミスマッチが存在する(Scalaは複数のパラダイムを併せ持つプログラミング言語である)。
REPLを終了するには、REPLプロンプトで (exit)、ctr-D、または (quit) と入力する。
user=> (exit)
Bye for now!
画像をインタラクティブに読み込み、平滑化する
次のサンプルでは、以下のOpenCVメソッドを使って、REPLから画像を対話的に読み込んで平滑化する方法を学ぶ。
- ファイルから画像を読み込むための Highgui クラスの imread 静的メソッド
- 画像をファイルに書き込むための Highgui クラスの imwrite 静的メソッド
- 元の画像を平滑化するための Imgproc クラスの GaussianBlur 静的メソッド
また、imread メソッドから返され、GaussianBlur と imwrite の両メソッドの主要な引数として受け取られる Mat クラスも使用する。
画像をプロジェクトに追加する
まず、プロジェクトの静的リソースを格納するために新しく作成したディレクトリに画像ファイルを追加したい。
mkdir -p resources/images
cp ~/opt/opencv/doc/tutorials/introduction/desktop_java/images/lena.png resource/images/
画像を読み込む
いつものようにREPLを起動し、まず使用するすべてのOpenCVクラスをインポートする。
lein repl
nREPL server started on port 50624 on host 127.0.0.1
REPL-y 0.3.0
Clojure 1.5.1
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=> (import '[org.opencv.core Mat Size CvType]
'[org.opencv.imgcodecs Imgcodecs]
'[org.opencv.imgproc Imgproc])
org.opencv.imgproc.Imgproc
次に resources/images/lena.png ファイルから画像を読み込む。
user=> (def lena (Highgui/imread "resources/images/lena.png"))
#'user/lena
user=> lena
#<Mat Mat [ 512*512*CV_8UC3, isCont=true, isSubmat=false, nativeObj=0x7f9ab3054c40, dataAddr=0x19fea9010 ]>
見ての通り、lena シンボルを評価するだけで、lena.png が CV_8UC3 要素型の512x512の行列であることがわかる。同じ次元・要素型の新しい Mat インスタンスを作成しよう。
user=> (def blurred (Mat. 512 512 CvType/CV_8UC3))
#'user/blurred
user=>
次に、lena をソース行列、blurred を出力先行列として GaussianBlur フィルタを適用する。
user=> (Imgproc/GaussianBlur lena blurred (Size. 5 5) 3 3)
nil
最後の手順として、平滑化した行列を新しい画像ファイルに保存するだけである。
user=> (Highgui/imwrite "resources/images/blurred.png" blurred)
true
user=> (exit)
Bye for now!
以下が平滑化された新しいLenaの画像である。
次のステップ
このチュートリアルは、CLJ REPLでOpenCVと対話できるようにするためのごく基本的な環境構築を紹介したにすぎない。
Clojure初心者には、Clojure Java Interop chapterを読み、Clojure内でより慣用的かつ関数的に使えるようラップされていない素のjavaライブラリと相互運用するために必要なことをすべて理解することを勧める。
OpenCV Java APIは、Qtに依存するhighguiモジュールの機能(例: namedWindow や imshow)をラップしていない。REPLからOpenCVと対話しながらウィンドウを作成して画像を表示したい場合、現時点では自力で対処する必要がある。そのギャップを埋めるにはJava Swingを利用できる。
ライセンス
Copyright © 2013 Giacomo (Mimmo) Cosenza aka Magomimmo
BSD 3-clause Licenseの下で配布されている。