OpenCVをJava+Tomcatで使ってみた

この記事はウェブクルー [Advent Calendar 2017](https://qiita.com/advent-calendar/2017/webcrew)の2日目の記事です。
昨日は@kouaresさんの「[PlayFramework 2.6.7, play-slick 3.0.2（Slick 3.2） でCRUDを実装する](https://qiita.com/kouares/items/ab221443571bcba40bd1)」でした。

##**はじめに**
2日目は、@hahegawaが担当します。
よろしくお願いします。

##**概要**
- 業務にて、画像から物体を検出したいなと思う機会があったので、OpenCVを試してみた。
- OpenCVについては、巷ではPython、C++での実装例や説明が多いのだが、業務利用でメンテなんかを考慮するとJavaでできたらいいなと思ったので、Javaで試してみた。
- また、OpenCVをJavaで使う際のインストールから実行までの「全体の流れ」を言及する記事が少なかったので、そこの辺りを意識してまとめてみた。

##**前提**
- 今回試してみたのは「特徴量検出」と「特徴量のマッチング」。
- OpenCVそのものの仕組みや実装イメージについては、以下を参考にさせていただいた。
    - 仕組み：http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_feature2d/py_table_of_contents_feature2d/py_table_of_contents_feature2d.html#py-table-of-content-feature2d
    - 実装の仕方：https://qiita.com/hmichu/items/f5f1c778a155c7c414fd

##**環境**
ちょっと古めだが、以下の環境で試した。
　・Windows 10
　・Eclipse　Oxygen
　・OpenCV3.1
　・Apache Tomcat7.0


##**プロジェクト構築**
１．Eclipseのインストールとプロジェクト作成
　　　本記事では、省略
　　　　
２．OpenCVライブラリのダウンロード
　　　以下のページから、使いたいVerのライブラリを適当な場所にダウンロード＆解凍
　　　　　https://opencv.org/releases.html
![アドベントカレンダー1-min.png](https://qiita-image-store.s3.amazonaws.com/0/211865/ffece524-ce55-a4b8-c096-19165317ce92.png)


３．Windowsのシステム環境変数を追加
　　　解凍したフォルダ内の「\build\java\」のさらに下にあるフォルダをPathに追加
![アドベントカレンダー2-min.png](https://qiita-image-store.s3.amazonaws.com/0/211865/32922e6d-8642-ae66-4423-07c548888806.png)


　
４．Eclipseのプロジェクトを右クリック→プロパティでビルドパスの設定
![アドベントカレンダー3-min.png](https://qiita-image-store.s3.amazonaws.com/0/211865/682e0b67-0275-8187-2295-6eb10311abcd.png)



５．ユーザライブラリを追加
　　　追加する際に、ネイティブライブラリーのロケーションに３と同じパスを設定する
![アドベントカレンダー4-min.png](https://qiita-image-store.s3.amazonaws.com/0/211865/1e076537-412b-a688-b27c-fc031ee9ee39.png)

　　
６．実行時構成の追加
　　　Webアプリを想定してOpenCVを動かす場合、実行時構成のクラスパスに５と
　　　同じユーザライブラリを追加
![アドベントカレンダー5-min.png](https://qiita-image-store.s3.amazonaws.com/0/211865/f87db2d1-749e-448b-84fd-a22ecfe8d878.png)


７．ソースコード作成
　　以下のような処理手順を踏むことで、画像から特徴点の検出とマッチングを行った
　　　（１）画像を読み込みBufferedImageクラスに格納
　　　（２）BufferedImageクラスからMatクラスに変換
　　　　　　→ OpenCVライブラリのメソッドで画像を加工する際には、
　　　　　　　このMatクラスを使うことが基本となる
　　　（３）特徴検出器生成
　　　　　　→ 今回は、AKAZEを利用
　　　（４）特徴検出実行＋クロスチェック
　　　　　　→ クロスチェック処理を入れた方が検出精度があがるが、
　　　　　　　処理時間と負荷が増えるので、悩みどころ
　　　（５）特徴点の検出結果を描画

```java:(2)～(5)のソースコード
　　//画面から送られてきた画像のMat変換
　　Mat src = new Mat(bufImg.getHeight(), bufImg.getWidth(), CvType.CV_8UC3);
　　byte[] bufImgBinary = ((DataBufferByte)bufImg.getRaster().getDataBuffer()).getData();
　　src.put(0, 0, bufImgBinary);

　　//特徴検出する際に用いるテンプレート画像のMat変換
　　Mat tempSrc = new Mat(tmpImg.getHeight(), tmpImg.getWidth(), CvType.CV_8UC3);
　　byte[] tempImgBinary = ((DataBufferByte)tmpImg.getRaster().getDataBuffer()).getData();
　　tempSrc.put(0, 0, tempImgBinary);

　　//特徴検出に向けたグレースケール変換
　　Mat srcGray = new Mat(bufImg.getHeight(), bufImg.getWidth(), CvType.CV_8UC1);
　　Imgproc.cvtColor(src, srcGray, Imgproc.COLOR_BGR2GRAY);
　　Core.normalize(srcGray, srcGray, 0, 255, Core.NORM_MINMAX);
　　Mat tempSrcGray = new Mat(tmpImg.getHeight(), tmpImg.getWidth(), CvType.CV_8UC1);
　　Imgproc.cvtColor(tempSrc, tempSrcGray, Imgproc.COLOR_BGR2GRAY);
　　Core.normalize(tempSrcGray, tempSrcGray, 0, 255, Core.NORM_MINMAX);

　　MatOfKeyPoint keyPointSrc = new MatOfKeyPoint();
　　MatOfKeyPoint keyPointTemp = new MatOfKeyPoint();
　　Mat descriptersSrc = new Mat(src.rows(), src.cols(), src.type());
　　Mat descriptersBase = new Mat(tempSrc.rows(), tempSrc.cols(), tempSrc.type());

　　//特徴検出器生成１
　　FeatureDetector akazeDetector = FeatureDetector.create(FeatureDetector.AKAZE);
　　DescriptorExtractor akazeExtractor = DescriptorExtractor.create(DescriptorExtractor.AKAZE);
　　akazeDetector.detect(srcGray, keyPointSrc);　　
　　akazeDetector.detect(tempSrcGray, keyPointTemp);
　　akazeExtractor.compute(srcGray, keyPointSrc, descriptersSrc);　　
　　akazeExtractor.compute(tempSrcGray, keyPointTemp, descriptersBase);

　　//特徴検出器生成２
　　DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);

　　MatOfDMatch matche = new MatOfDMatch();
　　List<MatOfDMatch> matches = new ArrayList<MatOfDMatch>();
　　List<DMatch> dmatch = new ArrayList<DMatch>();

　　//特徴クロスチェック有
　　if(CROSS_CHECK_FLG){

　　　　MatOfDMatch srcToBase = new MatOfDMatch();
　　　　MatOfDMatch baseToSrc = new MatOfDMatch();
　　　　
　　　　matcher.match(descriptersSrc, descriptersBase, srcToBase);
　　　　matcher.match(descriptersBase, descriptersSrc, baseToSrc);

　　　　List<DMatch> ldm_srcToBase = srcToBase.toList();
　　　　List<DMatch> ldm_baseToSrc = baseToSrc.toList();

　　　　for(int i=0; i<ldm_srcToBase.size(); i++) {

　　　　　　DMatch forward = ldm_srcToBase.get(i);
　　　　　　DMatch backward = ldm_baseToSrc.get(forward.trainIdx);

　　　　　　if(backward.trainIdx == forward.queryIdx){
　　　　　　　　dmatch.add(forward);
　　　　　　}

　　　　}

　　　　matche.fromList(dmatch);

　　}
　　//特徴クロスチェック無
　　else{

　　　　System.out.println("特徴通常チェック　実行");
　　　　matcher.match(descriptersSrc, descriptersBase, matche);

　　}

　　Mat matchResult = new Mat(src.rows()*2, src.cols()*2, src.type());
　　Features2d.drawMatches(src, keyPointSrc, tempSrc, keyPointTemp, matche, matchResult);
　　//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓検証用画像の出力↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
　　BufferedImage image = ImageConverter.convertMToBI(matchResult);
　　File ouptut = new File("C:\\testlog\\sample_20171202.jpg");
　　ImageIO.write(image, "jpg", ouptut);
```

　　※ちなみに、OpenCVの処理を通る前にライブラリのロードを
　　通しておかないと実行時エラーになるので、要注意

```java:OpenCVライブラリのロード
　　static{
　　　　System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
　　}
```

##**実行結果**
誤認識も見受けられるが、特徴検出できた
![6-min](https://user-images.githubusercontent.com/27848357/33469563-f176d380-d6a6-11e7-9572-768eda83f2b5.jpg)

　
##**やってみて思ったこと**
- 私はコード作成が遅いので特に時間がかかったが、コードを思い付いたり書くのが速い人は、今回の記事に書いた基本事項と各種メソッドの説明さえ理解しておけば、簡単に画像処理ができると感じた。
- 業務を企画する上で、本当に使いたいのは、「特徴検出の先に何をやるか」になるはずなので、本記事を読んで、余計な工数は省いて本当に実装したい処理に注力していただければ幸いです。

明日は、@wc_moriyama さんです。よろしくお願いします。