JavaでOpenCV_Contrib(ArUco: ARMarker)を使う！(後編 - プログラミング編)

##はじめに
ArUcoとは、ARを行えるライブラリです。ARマーカーの生成、認識ができます。
この記事は、前後編となっております。ArUcoライブラリのビルドをされてない方は、
[JavaでOpenCV_Contrib(ArUco)を使う！(前編 - ビルド編)](https://qiita.com/smk7758/items/33bbadf230dd9a85b04e) を参照してください。

また、今回使ったソースコードはだいたい[OpenCV_ArucoTest[GitHub]](https://github.com/smk7758/OpenCV_ArucoTest)にありますので、参考にしていただければ幸いです。

この記事では、ArUcoライブラリをJavaで使う時のプログラムについて解説をしています。

##ArUcoをEclipseで使う準備
既存のOpenCVをインポートする手順と同様に、Contrib内包のjarファイルで行ってください。

##Marker画像の生成
ArUcoライブラリがちゃんとビルド、セットアップされているか確認をするために
Marker画像の生成を行ってみましょう。

使う関数は、
・Aruco.drawMarker(dictionary, markerID, sidePixels, markerImage)
dictionary: Markerの種類を決めるものです。四角の大きさや解像度、種類の数が異なります。
markerID: Dictionaryに定義されたMarkerのIDで、それぞれで形が異なります。
sidePixels: 解像度を定めます。

以下ソースコード(詳細: [createMarker[GitHub]](https://github.com/smk7758/OpenCV_ArucoTest/tree/master/test-2018-12-01_OpenCV_Contrib_Aruco_0_createMarker))

```java
	public static void createMarker() {
		Dictionary dictionary = Aruco.getPredefinedDictionary(Aruco.DICT_4X4_50);

		final int markerID = 0;
		final int sidePixels = 200;
		Mat markerImage = new Mat();
		Aruco.drawMarker(dictionary, markerID, sidePixels, markerImage);

		Imgcodecs.imwrite("F:\\users\\smk7758\\Desktop\\marker_2018-12-01.png", markerImage);
	}
```
![marker_2018-12-01.png](https://qiita-image-store.s3.amazonaws.com/0/211361/09435eea-4c1f-0e89-4afd-5d1300744d41.png)
こんなのが生成されるはずです。
##Markerの認識
さて、ここから本番です。

使う関数は次の2つ
・Aruco.detectMarkers(inputImage, dictionary, corners, markerIds, parameters);
・Aruco.drawDetectedMarkers(inputImage, corners, markerIds);
まぁ関数の通りですが、detectMarkersは複数のマーカーを認識します。
その後、cornersを用いて、drawDetectedMarkersではMarkerの四隅をなぞるようなことをします。
corners: Markerの画面上の座標を返却します。(なんでList<Mat>かと言うと、複数のMarkerの認識をすることと、Matは行列であり座標を保管できるからです)

(他の引数は省略)


以下ソースコード(詳細: [detectMarker[GitHub]](https://github.com/smk7758/OpenCV_ArucoTest/tree/master/test-2018-12-01_OpenCV_Contrib_Aruco_1_detectMarker))

```java
	public static void detectMarker() {
		Dictionary dictionary = Aruco.getPredefinedDictionary(Aruco.DICT_4X4_50);

		Mat inputImage = Imgcodecs.imread("F:\\users\\smk7758\\Desktop\\marker_2018-12-01_test.png");
		List<Mat> corners = new ArrayList<>();
		Mat markerIds = new Mat();
		DetectorParameters parameters = DetectorParameters.create();
		Aruco.detectMarkers(inputImage, dictionary, corners, markerIds, parameters);

		Aruco.drawDetectedMarkers(inputImage, corners, markerIds);

		Imgcodecs.imwrite("F:\\users\\smk7758\\Desktop\\marker_2018-12-01_detected.png", inputImage);
	}
```

![marker_2018-12-01_test.png](https://qiita-image-store.s3.amazonaws.com/0/211361/32844aac-8241-24ce-8389-7ac0c1bdd607.png)
適当にGIMPで変形したものですが、上の画像では、

![marker_2018-12-01_detected.png](https://qiita-image-store.s3.amazonaws.com/0/211361/6be1f229-4ffe-c4fc-4fd6-07788f850cfb.png)
こんな感じになってくれるはずです。

##カメラを用いたリアルタイムMarker認識

正直ここまで来たら、ほとんど自己満足みたいなものですが、次はJavaFXとVideoCapture(OpenCV)を組み合わせて、リアルタイム認識を行いました。

先ほどと違うのはJavaFXで動かすので、ScheduledServiceというやつを使って、返す値はImage型ということです。
ScheduledServiceとは、JavaFXでマルチスレッド処理を行う際に使うクラスで、Taskを自動的に復帰させることができるものだそうです。詳細は公式JavaDocを参照してください。
また、Image型で返す理由は、Controllerクラス側で定義されている画像を表示する要素が、引数に
Imageをとるためです。MatからImageに変換するやつは適当に調べて出てきたものを使いました。

以下ソースコード(詳細: [detectMarkerByCamera - MarkerDetectorService.java [GitHub]](https://github.com/smk7758/OpenCV_ArucoTest/blob/master/test-2018-12-01_OpenCV_Contrib_Aruco_3_detectMarkerByCamera/src/com/github/smk7758/OpenCV_Contrib_Aruco_0/MarkerDetectorService.java))

```java
	@Override
	protected Task<Image> createTask() {
		return new Task<Image>() {
			@Override
			protected Image call() throws Exception {
				if (!vc.isOpened()) {
					System.err.println("VC is not opened.");
					this.cancel();
					return null;
				}

				Mat inputImage = new Mat();

				if (!vc.read(inputImage) || inputImage == null) {
					System.err.println("Cannot load camera image.");
					this.cancel();
					return null;
				}

				List<Mat> corners = new ArrayList<>();
				Mat markerIds = new Mat();
				// DetectorParameters parameters = DetectorParameters.create();
				Aruco.detectMarkers(inputImage, dictionary, corners, markerIds);

				Aruco.drawDetectedMarkers(inputImage, corners, markerIds);

				return convertMatToImage(inputImage);
			}
		};
	}

	private Image convertMatToImage(Mat inputImage) {
		MatOfByte byte_mat = new MatOfByte();
		Imgcodecs.imencode(".bmp", inputImage, byte_mat);

		return new Image(new ByteArrayInputStream(byte_mat.toArray()));
	}
```

![SC_test_2018-12-2_21-37-9_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/211361/cffd2013-8f43-2a36-e66f-d66564eb639a.png)
こんな感じになってくれるはずです。

また、[detectMarker(Center)CoordinatesByCamera [GitHub]](https://github.com/smk7758/OpenCV_ArucoTest/blob/master/test-2018-12-01_OpenCV_Contrib_Aruco_3_detectMarkerCoordinatesByCamera/src/com/github/smk7758/OpenCV_Contrib_Aruco_0/MarkerDetectorService.java)では、OpenCV側の関数を利用して、Markerの4隅の点から、中心(重心)の座標を求めて点を描き込んでいます。



##カメラを用いたリアルタイムMarker姿勢推定

いよいよ一番やりたいでしょう、姿勢推定です。

さて、早速やっていきたいところでしょうが実は姿勢推定を行う前にやることがあります。
カメラキャリブレーションというものです。こちらで得られたMatがなければ次の関数は実行できませんので先に [JavaでOpenCVのカメラキャリブレーション(CameraCalibration)をやった](https://qiita.com/smk7758/items/b678c492eac42aff7442) を参照していただきたいです。
(実はこの前の章との間に開発上は、2～3週間かかっちゃってます。遅れた原因はこれ)

改めまして、関数から入っていきましょう。
・Aruco.estimatePoseSingleMarkers(corners, 0.05f, cameraMatrix, distortionCoefficients,
rotationMatrix, translationVectors);
・Aruco.drawAxis(inputImage, cameraMatrix, distortionCoefficients,
rotationMatrix, translationVectors, 0.1f);

上の関数は、名前の通りMarkerの姿勢推定を行い、回転行列と平行移動ベクトルを返してきてくれます。
そして下の関数では、Axisつまり座標軸を描画してくれます。

以下ソースコード(詳細: [detectMarkerPoseByCamera [GitHub]](https://github.com/smk7758/OpenCV_ArucoTest/blob/master/test-2018-12-01_OpenCV_Contrib_Aruco_4_detectMarkerPoseByCamera/src/com/github/smk7758/OpenCV_Contrib_Aruco_0/MarkerDetectorService.java))

```java
	List<Mat> corners = new ArrayList<>();
	Mat markerIds = new Mat();
	// DetectorParameters parameters = DetectorParameters.create();
	Aruco.detectMarkers(inputImage, dictionary, corners, markerIds);

	Aruco.drawDetectedMarkers(inputImage, corners, markerIds);

	Mat rotationMatrix = new Mat(), translationVectors = new Mat(); // 受け取る
	Aruco.estimatePoseSingleMarkers(corners, 0.05f, cameraMatrix, distortionCoefficients,rotationMatrix, translationVectors);

	for (int i = 0; i < markerIds.size().height; i++) { // TODO
		Aruco.drawAxis(inputImage, cameraMatrix, distortionCoefficients, rotationMatrix, translationVectors, 0.1f);
	}
```

![SC_test_2018-12-31_0-36-22_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/211361/87bb3d1b-a57e-573b-e3e9-34064c144ba5.png)
![SC_test_2018-12-31_0-36-13_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/211361/fe0a07f8-d957-c2d6-66a7-25a4b33bc814.png)
![SC_test_2018-12-31_0-35-37_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/211361/6987ee30-a0c7-b2d1-20db-385693ae0d99.png)
こんな感じになってくれるはずです。

##おわりに
そこそこ時間がかかりましたが、とりあえず動くようになりました。
特にカメラキャリブレーションの理解が大変でしたね…。
これからもJava版OpenCVに精進して行きたい所存です。

(2018年最後に楽しめました)[記事自体は2019年最初に書いてますが]

また、この記事は初学者が書いておりますので、間違いや良くない点がございましたらコメントまたはツイッターにてご意見いただけたら幸いです。

###参考リンク
公式が一番わかりやすかったです。

・[ArUco marker detection (aruco module) - OpenCV公式](https://docs.opencv.org/3.4/d9/d6d/tutorial_table_of_content_aruco.html)
・[Detection of ArUco Markers - OpenCV公式](https://docs.opencv.org/3.4/d5/dae/tutorial_aruco_detection.html)
・[Opencv Aruco で Pose estimation する その１ - 機械学習備忘録](http://pongsuke.hatenadiary.jp/entry/2018/04/19/102146)
・[Opencv Aruco で Pose estimation する その２ - 機械学習備忘録](http://pongsuke.hatenadiary.jp/entry/2018/04/19/112554)
・[OpenCV arucoマーカ - プログラム関連の個人的メモ](https://seesaawiki.jp/asama-yaya/d/OpenCVaruco%A5%DE%A1%BC%A5%AB)
・[arucoモジュールを使ってみる - atinfinity/lab(GitHub)](https://github.com/atinfinity/lab/wiki/aruco%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B)
・[マーカーを用いたカメラ位置姿勢推定（OpenCV+ArUco）- 空飛ぶロボットのつくりかた](http://robonchu.hatenablog.com/entry/2017/08/21/151412)
・[軽量な AR ライブラリの ArUco について調査中](https://qiita.com/nonbiri15/items/b893cb614d7dd6c8c86a)
・[自分用：OpenCV3.4.3 カメラキャリブレーション](https://qiita.com/kazz4423/items/f173f298704bd121043d)