JavaでOpenCVを使う

#はじめに
JavaでOpenCVライブラリを使いたかったのですが，意外とてこずったのでその手順を書き留めておきます．周辺知識の整理として，Javaのライブラリの使い方，packageなどの基礎的なことの理解にも繋がったので，そこにも触れておきます．
①ターミナルでコンパイルを行うときと，②IDE(特にIntelliJ)を使ってコンパイルするときの方法の両方を記しておきます．

#環境
- macOS Catalina 10.15.5
- [Java OpenJDK 13.0.2](https://www.oracle.com/java/technologies/javase-jdk13-downloads.html)
- [OpenCV](https://opencv.org/) 4.3.0

#Javaのインストール
念のためJavaのインストール方法も残しておきます(macOS)．Homebrewを使ってインストールをしました．

```zsh
# install Homebrew (if needed)
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# update packages, install cask, and allow cask to look up versions
brew update
brew tap homebrew/cask
brew tap homebrew/cask-versions
# install Java
brew cask install java #java13などとすればバージョンを指定してインストールできる
```

これにより，`/Library/`にJavaがインストールされます．階層構造は以下のようになるはず．


```zsh
/Library/Java
          |-- Extensions/ # Javaの拡張フォルダ
          `-- JavaVirtualMachine/
               `-- openjdk-13.0.2.jdk/ # インストールしたJDK
                    `-- Contents/
                         |-- Home/ # JDKのホームディレクトリ
                         |    |--
                         |     ...
                         |-- MacOS/
                         `-- Info.plist
```

ちなみに，例によって

```zsh
java --version
```
とするとバージョンを確認できます．

#OpenCV
以下の一つ目のサイトに従っていくと，OpenCV JavaをmacOSにインストールできます．2020年6月現在，最新のJavaは4.3.0なので，バージョンを指定しないでインストールするとこれがインストールされます．インストール自体には案外時間がかかります．

- [OpenCV Java Tutorials: Install OpenCV 3.x under macOS](https://opencv-java-tutorials.readthedocs.io/en/latest/01-installing-opencv-for-java.html#install-opencv-3-x-under-macos)
- [【Mac】JavaでOpenCVの環境設定に苦労したメモ](http://autolab-minoya.hatenablog.com/entry/2017/11/04/224627)

うまくいけば，`/usr/local/Cellar/opencv/4.3.0_4/share/java/`に以下のような階層構造でライブラリがインストールされます．これはマシンやソフトウェアのバージョンによって変わるはず．

```zsh
/java/
  `-- opencv4/
       |-- opencv-430.jar # 静的ライブラリ
       `-- libopencv_java430.dylib* # 動的ライブラリ
            |--
             ...
```

ちなみに.jarファイルは静的ライブラリ，.dylibファイルは動的ライブラリで(dynamic libraryの略)，動的ライブラリはコンパイル時ではなく実行時に参照されるライブラリだそうです[^1]．ちなみにjarファイルはコンパイルで作成されるclassファイルを一つに集めて圧縮したファイルです[^2]．

#コンパイルと実行


```java:Test.java
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.CvType;
import org.opencv.core.Scalar;

public class Test {
    // Compulsory
    static{ System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }

    public static void main(String[] args) {
        System.out.println("Welcome to OpenCV " + Core.VERSION);
        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());
    }
}
```
試しにこのようなファイルを作って，コンパイルしてみます．
普通に.javaファイルをコンパイルするようにそのファイルのあるディレクトリで，

```zsh
javac Test.java
```
としてコンパイルすると下のようにたくさんのエラーが出ます．
<img width="565" alt="Screen Shot 2020-06-08 at 20.16.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505262/9960ac0e-ebe6-1f2f-07a6-2fbc9934d3d0.png">
"package org.opencv.core does not exist"とあるので，これはOpenCVライブラリを見つけられないということです．つまりせっかくOpenCVをインストールしたもののコンパイル時にjavacがそのライブラリを見つけられていないということになります．
これを解決するには，以下のように**クラスパス**を通してあげる必要があるそうです．コンパイル時に`-classpath` or `-cp`オプションをつけることによってjavacがそこを探すようになります．

```zsh
javac -classpath /usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4/opencv-430.jar Test.java
```
また，実行時には動的ライブラリのパスも指定してあげる必要があります．

```zsh
java -Djava.library.path="/usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4" -classpath /usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4/opencv-430.jar: Test
```
ちなみに実行時にはクラスパスの後にコロン`:`を入れないといけません(macOS)．Windowsの場合はセミコロン`;`です．
正しくできていれば，

```:実行結果
Welcome to OpenCV 4.3.0
OpenCV Mat: Mat [ 5*10*CV_8UC1, isCont=true, isSubmat=false, nativeObj=0x7fd0f2e3f230, dataAddr=0x7fd0f2e3f0c0 ]
OpenCV 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]
```
のように表示されます．

##より正しくは
Javaでクラスを別々のファイルに作りそのクラスを別のJavaファイル内で使うときは，`import`や`package`を使います．上の例は一つのファイル単体ですが，プログラムを組みときはこのようにファイルが依存し合っている場合がほとんどです．それに加えて今回はOpenCVすなわち外部ライブラリも参照したいケースということになります．
このようなときに，先ほどと同じく

```zsh
javac -classpath /usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4/opencv-430.jar Test.java
```
としてコンパイルしようとすると，今度はpackageやimportでエラーが出てしまいました．

これは，クラスパスを指定したことによってjavacがそのパスからpackageやimportを探しに行ってしまったために見つけられなかったことを意味します．Javaは，packageやimportの階層構造をスラッシュ`/`ではなくてドット`.`によって表現するのですが，それはそもそもコンパイルを実行するカレントディレクトリからの相対パスで書いてあります([参考サイト](https://www.sejuku.net/blog/63008))．つまり**デフォルトでクラスパスはカレントディレクトリになっていた**のですが，これを`-classpath`オプションによって上書きしてしまったので，今度はそちらが探せなくなってしまったということになります．

一つ考えられる解決策はpackageやimportのパスを，①絶対パスにするか，②コンパイル時に指定する -classpathオプションからの相対パスに書き換えるかです．ただ，絶対パスは長いし，別PCにプログラムを移したときにいちいち書き換えなければいけなくなります．また，コンパイルオプションを考慮してファイルの中身を変えておくのはナンセンスですし，それこそ外部ライブラリの場所は各PCによって異なるので，その都度書き直さなくてはいけません．やはりメインファイルからの相対パスにするのが普通です．

よって，クラスパスを書き換えるのではなくて(OpenCVライブラリ用のパスも)追加するような指定の仕方をしたいわけですが，コロン`:`によって繋ぐとクラスパスを複数指定できるようなのでこれを使います．
カレントディレクトリの`.`も含めて`-classpath .:(OpenCVのパス)`として指定するということです．こうすることで，元のpackage依存関係などは保存しながら，新たにOpenCVライブラリ用のクラスパスも追加したことになります

```zsh
javac -classpath .:/usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4/opencv-430.jar Test.java # コンパイル
java -Djava.library.path="/usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4" -classpath .:/usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4/opencv-430.jar: Test # 実行
```

これで正しくコンパイルや実行が行えるようになりました．
(参考サイト)
[Java Programming: Package and Classpath](https://www3.ntu.edu.sg/home/ehchua/programming/java/J9c_PackageClasspath.html)

##別の方法
この方法はコンパイルや実行時に一時的にクラスパスを指定する方法ですが，`/Library/Extensions/`はJavaの拡張フォルダなので，ここにjarファイルとdylibファイルを突っ込んでしまうことでパスを通すという方法もあるそうです．その場合は毎回毎回-classpathオプションを指定してあげる必要はありません．

#IntelliJでの設定方法
[**IntelliJ**](https://www.jetbrains.com/idea/promo/ultimate/?gclid=Cj0KCQjwoPL2BRDxARIsAEMm9y-AFlyJ1PpLvFySDiJJ-ABoUi0xzY9fdK4FnQLvO4dX-6PMt7MdBdgaAurkEALw_wcB)とはJavaの開発によく使われる優れもののIDE(Integrated Development Environment)です．さっきはエディタとターミナルで開発するときのOpenCVの使い方でしたが，IntelliJを使う場合の設定方法も書き残しておきます．
ちなみにIntelliJ自体のチュートリアルは以下にあります．

- [Overview of the user interface](https://www.jetbrains.com/help/idea/guided-tour-around-the-user-interface.html)
- [Video tutorials](https://www.jetbrains.com/idea/documentation/)

##基本設定
念のため，IntelliJのインストールから基本的な設定の手順も残しておきます．
####インストール
[インストールページ](https://www.jetbrains.com/idea/download/#section=mac)からOSを選んでCommunity Editionをダウンロード(学生ならlicensedをダウンロードすることもできる)．

####プラグイン
(必要なら)*Welcome to IntelliJ IDEA*ウィンドウの*Configure -> Plugins*からプラグインを入れる("Java Visualizer"などが便利)．プラグインを入れた場合はIntelliJを再起動する必要がある．

####SDKの設定
SDK(Software Development Kit)とはソフトウェア開発ツールのことでソフトウェアの開発にはこれが必要になります．Java用のSDKはJDK(Java Development Kit)と呼ばれ，これは既にOSにインストール済なのでターミナル上で開発する際はいいのですが，IntelliJにもこれを分からせてあげる，つまりパスを通す必要があります．
<img width="200" alt="Screen Shot 2020-06-08 at 12.16.39.png" src="https://i.stack.imgur.com/TO6NR.png">

"*Configure -> Structure for New Projects*"からSDK(Software Development Kit)を設定します．[このドキュメント](https://www.jetbrains.com/help/idea/sdk.html)に従って行えばできます．

##OpenCVライブラリの設定
上の設定で基本的なプログラムは開発できるのですが，今回のように外部ライブラリを使うときには，その設定をしなくてはいけません．IntelliJとはIDEで，その中でコンパイルや実行を行うので，ターミナルでコンパイルや実行をするときにclasspathを指定したようなことを事前に設定する必要があるということです．
今回のOpenCVを例に取って，その流れを説明します．

"*File -> Project Structure*"を選択してウィンドウを開き，左のタブから"*Modules*"を選択します．そして"*Depencencies*"の欄を選択し，追加ボタン(左下の＋ボタン)から"*JARs or directories*"を選んで，インストールしたOpenCVの中にある.jarファイルを追加します．
僕の場合は`/usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4/opencv-430.jar`という場所にありました．
そのファイルを選択すると，このように追加されます
<img width="700" alt="Screen Shot 2020-06-08 at 13.09.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505262/158a188a-174d-456e-d22f-73a6ea32630c.png">

次に，その追加されたjarファイルの欄をダブルクリックすると"*Configure Module Library*"というウィンドウが開くので，また左下の追加ボタンから，今度は先ほどのjarファイルと同じディレクトリにある
`/usr/local/Cellar/opencv/4.3.0_4/share/java/opencv4/libopencv_java430.dylib`
というファイルを選択します．
すると，写真のように"*Native Library Locations*"という欄が追加されるので，これで完了です．
<img width="1000" alt="Screen Shot 2020-06-08 at 12.57.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505262/c59b4661-25fa-1d3a-cf97-fd05780b52cb.png">


試しに上と同じテストファイルを実行してみて正しい結果が返ってくれば正しく設定できたことになります．
ちなみに，

- importの段階でコンパイルエラーが出るのなら，External Libraryとして`opencv-430.jar`がちゃんと設定されていないことになるので1枚目の写真の段階を確認．
- `static{ System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }`
でエラーが出るのなら，これは"*Native Library Locations*"がちゃんと設定されていないということになるので2枚目の写真の段階で設定がうまくできていないことになります．


この手順は，以下のサイトに丁寧に書かれているものを参考にしました．OpenCVのバージョンによって多少の修正が必要になるかもしれません．

- [How to set-up OpenCV in Intellij IDEA](https://medium.com/@aadimator/how-to-set-up-opencv-in-intellij-idea-6eb103c1d45c)


[^1]: https://qastack.jp/software/66305/what-is-a-dylib
[^2]: https://www.sejuku.net/blog/65313