Edited at
OpenCVDay 23

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


はじめに

この記事は、前後編となっております。ArUcoライブラリのビルドをされてない方は、

JavaでOpenCV_Contrib(ArUco)を使う!(前編 - ビルド編) を参照してください。

また、今回使ったソースコードはだいたいOpenCV_ArucoTest[GitHub]にありますので、参考にしていただければ幸いです。

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

本当はもっと早く書こうと思ってたんですけれど、遅れました。(大晦日前の夜に書きはじめた…)

一応、OpenCV Advent Calendar 2018に登録しています。(2019年の正月に投稿しておいて何言ってんだこいつ)


ArUcoをEclipseで使う準備

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


Marker画像の生成

ArUcoライブラリがちゃんとビルド、セットアップされているか確認をするために

Marker画像の生成を行ってみましょう。

使う関数は、

・Aruco.drawMarker(dictionary, markerID, sidePixels, markerImage)

dictionary: Markerの種類を決めるものです。四角の大きさや解像度、種類の数が異なります。

markerID: Dictionaryに定義されたMarkerのIDで、それぞれで形が異なります。

sidePixels: 解像度を定めます。

以下ソースコード(詳細: createMarker[GitHub])

    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

こんなのが生成されるはずです。


Markerの認識

さて、ここから本番です。

使う関数は次の2つ

・Aruco.detectMarkers(inputImage, dictionary, corners, markerIds, parameters);

・Aruco.drawDetectedMarkers(inputImage, corners, markerIds);

まぁ関数の通りですが、detectMarkersは複数のマーカーを認識します。

その後、cornersを用いて、drawDetectedMarkersではMarkerの四隅をなぞるようなことをします。

corners: Markerの画面上の座標を返却します。(なんでListかと言うと、複数のMarkerの認識をすることと、Matは行列であり座標を保管できるからです)

(他の引数は省略)

以下ソースコード(詳細: detectMarker[GitHub])

    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

適当にGIMPで変形したものですが、上の画像では、

marker_2018-12-01_detected.png

こんな感じになってくれるはずです。


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

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

先ほどと違うのはJavaFXで動かすので、ScheduledServiceというやつを使って、返す値はImage型ということです。

ScheduledServiceとは、JavaFXでマルチスレッド処理を行う際に使うクラスで、Taskを自動的に復帰させることができるものだそうです。詳細は公式JavaDocを参照してください。

また、Image型で返す理由は、Controllerクラス側で定義されている画像を表示する要素が、引数に

Imageをとるためです。MatからImageに変換するやつは適当に調べて出てきたものを使いました。

以下ソースコード(詳細: detectMarkerByCamera - MarkerDetectorService.java [GitHub])

    @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

こんな感じになってくれるはずです。

また、detectMarker(Center)CoordinatesByCamera [GitHub]では、OpenCV側の関数を利用して、Markerの4隅の点から、中心(重心)の座標を求めて点を描き込んでいます。


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

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

さて、早速やっていきたいところでしょうが実は姿勢推定を行う前にやることがあります。

カメラキャリブレーションというものです。こちらで得られたMatがなければ次の関数は実行できませんので先に JavaでOpenCVのカメラキャリブレーション(CameraCalibration)をやった を参照していただきたいです。

(実はこの前の章との間に開発上は、2~3週間かかっちゃってます。遅れた原因はこれ)

改めまして、関数から入っていきましょう。

・Aruco.estimatePoseSingleMarkers(corners, 0.05f, cameraMatrix, distortionCoefficients,

rotationMatrix, translationVectors);

・Aruco.drawAxis(inputImage, cameraMatrix, distortionCoefficients,

rotationMatrix, translationVectors, 0.1f);

上の関数は、名前の通りMarkerの姿勢推定を行い、回転行列と平行移動ベクトルを返してきてくれます。

そして下の関数では、Axisつまり座標軸を描画してくれます。

以下ソースコード(詳細: detectMarkerPoseByCamera [GitHub])

    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

SC_test_2018-12-31_0-36-13_No-00.png

SC_test_2018-12-31_0-35-37_No-00.png

こんな感じになってくれるはずです。


おわりに

そこそこ時間がかかりましたが、とりあえず動くようになりました。

特にカメラキャリブレーションの理解が大変でしたね…。

これからもJava版OpenCVに精進して行きたい所存です。

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

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


参考リンク

公式が一番わかりやすかったです。

ArUco marker detection (aruco module) - OpenCV公式

Detection of ArUco Markers - OpenCV公式

Opencv Aruco で Pose estimation する その1 - 機械学習備忘録

Opencv Aruco で Pose estimation する その2 - 機械学習備忘録

OpenCV arucoマーカ - プログラム関連の個人的メモ

arucoモジュールを使ってみる - atinfinity/lab(GitHub)

マーカーを用いたカメラ位置姿勢推定(OpenCV+ArUco)- 空飛ぶロボットのつくりかた

軽量な AR ライブラリの ArUco について調査中

自分用:OpenCV3.4.3 カメラキャリブレーション