Introduction

OpenCVSharp に新しいPull Requestを投げました。
内容は、OpenCV の拡張モジュールに含まれる、ArUco と呼ばれる拡張現実アプリケーション用軽量ライブラリのOpenCVSharpへの移植です。

移植作業において、P/Invokeを書くことも大変だったのですが、ライブラリそのものの使い方も大変でしたのでメモします。

What is ArUco?

まず、ArUcoの公式ページは下記のようです。

ArUcoという名称は、おそらく、Augmented Reality Universidad de Córdobaの略なのだと思います。
ucoというは、Universidad de Córdoba、つまりスペインのコルドバ大学のドメインです。

特徴

下記は、ArUcoについてのOpenCVの下記のドキュメントページです。

そこで、

ArUco markers are binary square fiducial markers that can be used for camera pose estimation. Their main benefit is that their detection is robust, fast and simple. The aruco module includes the detection of these types of markers and the tools to employ them for pose estimation and camera calibration. Also, the ChArUco functionalities combine ArUco markers with traditional chessboards to allow an easy and versatile corner detection. The module also includes the functions to detect ChArUco corners and use them for pose estimation and camera calibration.

AuUcoマーカーはカメラの姿勢推定に用いことが可能な二値化された位置合わせマーカーです。主な利点はそれらの検出がロバスト、高速かつシンプルであることです。
ArUcoモジュールには、これらの種別の検出、姿勢推定とカメラキャリブレーションのためのツールを含みます。
また、ChArUcoの機能は、ArUcoマーカーに従来のチェスボードを組み合わせることで、簡単で多目的に使えるコーナー検出を可能にします。モジュールは、ChArUcoのコーナー検出、それらを使用した姿勢推定とカメラキャリブレーション機能も含みます。

と記述してあります。

とりあえず、位置合わせマーカーが全ての起点となります。
画像中からマーカーを見つけ出し、マーカーの位置関係から、壁や床の向き、角度を計算できる模様。

How to use

最初に使い方が大変だったと書きましたが、コード自体は大したことがないです。後述のソース見ればわかりますが、マーカーの検出自体は数行です。
何にはまったかというと、

  • OpenCVに含まれるサンプル画像は使えない
  • 辞書が一致しないと検出できない

とい2点です。

サンプル画像が使えない

例えば、https://github.com/opencv/opencv_contrib/blob/master/modules/aruco/tutorials/aruco_detection/images/singlemarkersoriginal.pngというサンプルがあります。OpenCVの公式リポジトリにあるサンプルです。

普通に、この画像を読み込んで、cv::aruco::detectMarkersに渡しても何も返ってきません。
どうも、サンプル画像のマーカーは、現行の辞書で生成される画像とは異なる模様。
下記のコミュニティのAnswerでの回答です。

なので、自分でマーカーを作って、検出しているのが、後述のソースになります。

辞書が一致しないと検出できない

これは、考えてみれば当たり前なのですが、マーカーを検出する際、どの辞書を使うかを引数に渡しています。
つまり、検出対象の画像に含まれるマーカーの基になった辞書と、検出に利用する辞書が一致しないと何も検出できません。
これに気づかなかったため、OpenCVSharpに移植する際、P/Invokeの記述ミスやメモリ破壊を疑ってしまい、無駄に時間を消費してしまいました。

Source

下記がサンプルです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/aruco.hpp>
#include <opencv2/opencv.hpp>

int main(int argc, const char * argv[])
{
// 共通パラメータ
const auto name = cv::aruco::DICT_6X6_250;
const auto dictionary = cv::aruco::getPredefinedDictionary(name);

// マーカー画像を生成
const auto markerSidePixels = 128;
const auto columns = 4;
const auto rows = 5;
const auto margin = 20;

auto width = columns * markerSidePixels + margin * (columns + 1);
auto height = rows * markerSidePixels + margin * (rows + 1);

auto id = 0;
cv::Rect roi(0, 0, markerSidePixels, markerSidePixels);
cv::Mat marker(cv::Size(width, height), CV_8UC1, cv::Scalar::all(255));

for (auto y = 0; y < rows; y++)
{
roi.y = y * markerSidePixels + margin * (y + 1);

for (auto x = 0; x < columns; x++)
{
roi.x = x * markerSidePixels + margin * (x + 1);

cv::Mat roiMat(marker, roi);
cv::Mat markerImage;
cv::aruco::drawMarker(dictionary, id++, markerSidePixels, markerImage, 1);
markerImage.copyTo(roiMat);
}
}

std::vector<int> markerIds;
std::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;
cv::Ptr<cv::aruco::DetectorParameters> parameters(new cv::aruco::DetectorParameters());

// マーカーの検出
cv::aruco::detectMarkers(marker, dictionary, markerCorners, markerIds, parameters, rejectedCandidates);
cv::Mat detected_marker;
cv::cvtColor(marker, detected_marker, cv::COLOR_GRAY2RGB);
cv::aruco::drawDetectedMarkers(detected_marker, markerCorners, markerIds, CvScalar(255, 0, 0));
// rejectedCandidatesにはidがないのでnoArray
cv::aruco::drawDetectedMarkers(detected_marker, rejectedCandidates, cv::noArray(), CvScalar(0, 0, 255));

/* 表示 */
cv::namedWindow("marker");
cv::namedWindow("detected");
cv::imshow("marker", marker);
cv::imshow("detected", detected_marker);
cv::waitKey(0);

return 0;
}

これを動かすと、次のようになります。

マーカー画像
マーカー画像

検出後
検出後

検出後の画像には、マーカーを青で囲んでいます。
赤い領域は、候補として検出されたがマーカーとしては識別されなかった棄却された領域です。

Conclusion

今回は歪みもない画像なので検出精度は100%ですが、現実の空間でもきちんとマーカーの位置を検出できます。
マーカーを紙面に印刷し、壁やボードに貼ることで、カメラの位置もわかりますし、付近の物体の位置関係も推定できそうです。