OpenCV 5.0.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 5.0.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
ArUcoマーカーの検出

次のチュートリアル: ArUcoボードの検出

原著者Sergio Garrido, Alexander Panov
互換性OpenCV >= 4.7.0

姿勢推定は、多くのコンピュータビジョンの応用において非常に重要である: ロボットのナビゲーション、拡張現実(AR)など多岐にわたる。この処理は、実環境中の点とそれらの2D画像投影との対応を見つけることに基づいている。これは通常困難なステップであり、そのため作業を容易にするために合成マーカーや基準マーカー(fiducial marker)を用いるのが一般的である。

最も普及しているアプローチの一つが、2値の正方形基準マーカーの利用である。これらのマーカーの主な利点は、1つのマーカーだけでカメラ姿勢を求めるのに十分な対応(その4つのコーナー)が得られることである。また、内部の2値符号化により特に頑健であり、誤り検出・訂正技術を適用できる可能性をもたらす。

aruco モジュールは ArUco ライブラリ を基にしている。これは Rafael Muñoz と Sergio Garrido によって開発された、正方形の基準マーカー検出のための人気ライブラリである [102]

aruco機能は次に含まれている:

マーカーと辞書

ArUcoマーカーは、太い黒い枠と、その識別子(id)を決定する内部の2値行列で構成される合成正方形マーカーである。黒い枠は画像内での高速な検出を容易にし、2値符号化はその識別と誤り検出・訂正技術の適用を可能にする。マーカーサイズは内部行列のサイズを決定する。例えば4x4のマーカーサイズは16ビットで構成される。

ArUcoマーカーの例:

Example of markers images

マーカーは環境中で回転した状態で見つかることがある点に注意すべきである。しかし検出処理では、各コーナーが一意に識別されるよう、その元の回転を判定できる必要がある。これも2値符号化に基づいて行われる。

マーカーの辞書とは、特定の応用で考慮されるマーカーの集合である。単純に言えば、それぞれのマーカーの2値符号化のリストである。

辞書の主な特性は、辞書サイズとマーカーサイズである。

  • 辞書サイズは、辞書を構成するマーカーの数である。
  • マーカーサイズは、それらのマーカーのサイズ(ビット/モジュールの数)である。

arucoモジュールには、さまざまな辞書サイズとマーカーサイズをカバーする、いくつかの定義済み辞書が含まれている。

マーカーidは2値符号化を10進数に変換して得られる数である、と考えるかもしれない。しかし、マーカーサイズが大きい場合はビット数が多すぎて、そのような巨大な数を扱うのは実用的でないため、これは不可能である。代わりに、マーカーidは単に、それが属する辞書内でのマーカーのインデックスである。例えば、辞書内の最初の5つのマーカーは 0, 1, 2, 3, 4 というidを持つ。

辞書に関する詳細は「Selecting a dictionary」の節で説明する。

マーカーの生成

検出の前に、環境中に配置するためにマーカーを印刷する必要がある。マーカー画像は generateImageMarker() 関数を使って生成できる。

例えば、次の呼び出しを分析してみよう:

cv::Mat markerImage;
cv::aruco::generateImageMarker(dictionary, 23, 200, markerImage, 1);
cv::imwrite("marker23.png", markerImage);
Comma-separated Matrix Initializer.
Definition mat.hpp:964
Dictionary is a set of unique ArUco markers of the same size.
Definition aruco_dictionary.hpp:29
bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
Saves an image to a specified file.
void generateImageMarker(const Dictionary &dictionary, int id, int sidePixels, OutputArray img, int borderBits=1)
Generate a canonical marker image.
Dictionary getPredefinedDictionary(PredefinedDictionaryType name)
Returns one of the predefined dictionaries defined in PredefinedDictionaryType.
@ DICT_6X6_250
6x6 bits, minimum hamming distance between any two codes = 11, 250 codes
Definition aruco_dictionary.hpp:119

まず、cv::aruco::Dictionary オブジェクトを、arucoモジュールに用意された定義済み辞書の中から1つ選んで生成する。具体的には、この辞書は250個のマーカーで構成され、マーカーサイズは6x6ビットである(cv::aruco::DICT_6X6_250)。

cv::aruco::generateImageMarker() の引数は次のとおりである。

  • 第1引数は、先ほど生成した cv::aruco::Dictionary オブジェクトである。
  • 第2引数はマーカーIDであり、この場合は辞書 cv::aruco::DICT_6X6_250 のマーカー23を指す。各辞書はそれぞれ異なる数のマーカーで構成されている点に注意すること。この場合、有効なIDは0から249までである。有効範囲外のIDを指定すると例外が発生する。
  • 第3引数の200は、出力マーカー画像のサイズである。この場合、出力画像のサイズは200x200ピクセルとなる。この引数は、対象の辞書のビット数を格納できる程度に十分大きくする必要がある点に注意すること。したがって、たとえば6x6ビットのマーカーサイズに対して5x5ピクセルの画像を生成することはできない(マーカーの境界を考慮しなくてもである)。さらに、変形を避けるため、この引数はビット数+境界サイズに比例した値にするか、少なくともマーカーサイズよりはるかに大きな値(例では200)にして、変形が無視できる程度になるようにするべきである。
  • 第4引数は出力画像である。
  • 最後の引数は省略可能で、マーカーの黒い境界の幅を指定する。サイズはビット数に比例して指定する。たとえば値2は、境界が内部の2ビット分のサイズに相当する幅を持つことを意味する。デフォルト値は1である。

生成された画像は次のとおりである。

Generated marker

完全に動作する例は、samples/cpp/tutorial_code/objectDetection/ 内の create_marker.cpp に含まれている。

サンプルは、cv::CommandLineParser を使ってコマンドラインから入力を受け取るようになっている。このファイルの場合、引数の例は次のようになる。

"marker23.png" -d=10 -id=23

create_marker.cpp の引数:

const char* keys =
"{@outfile |res.png| Output image }"
"{d | 0 | dictionary: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2,"
"DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, "
"DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12,"
"DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL=16,"
"DICT_APRILTAG_16h5=17, DICT_APRILTAG_25h9=18, DICT_APRILTAG_36h10=19, DICT_APRILTAG_36h11=20, DICT_ARUCO_MIP_36h12=21}"
"{cd | | Input file with custom dictionary }"
"{id | 0 | Marker id in the dictionary }"
"{ms | 200 | Marker size in pixels }"
"{bb | 1 | Number of bits in marker borders }"
"{si | false | show generated image }";
}

マーカーの検出

ArUcoマーカーを含む画像が与えられると、検出処理は検出されたマーカーのリストを返す。検出された各マーカーには次の情報が含まれる。

  • 画像中における4つのコーナーの位置(元の順序のまま)。
  • マーカーのID。

マーカー検出処理は、主に2つのステップで構成される。

  1. マーカー候補の検出。このステップでは、マーカーの候補となる四角形を見つけるために画像を解析する。まず適応的しきい値処理によってマーカーをセグメント化し、次にしきい値処理された画像から輪郭を抽出して、凸でないものや四角形に近似できないものを除外する。さらにいくつかの追加フィルタリングも適用される(小さすぎる輪郭や大きすぎる輪郭の除去、互いに近すぎる輪郭の除去など)。
  2. 候補検出の後、内部の符号化を解析して、それらが実際にマーカーであるかどうかを判定する必要がある。このステップは、各マーカーのマーカービットを抽出することから始まる。そのために、まず透視変換を適用してマーカーを正規形で取得する。次に、正規化された画像を大津の方法(Otsu)でしきい値処理して、白ビットと黒ビットを分離する。画像はマーカーサイズと境界サイズに応じて複数のセルに分割される。そして、各セル内の黒または白のピクセル数をカウントして、それが白ビットか黒ビットかを判定する。最後に、ビットを解析して、そのマーカーが特定の辞書に属するかどうかを判定する。必要に応じて誤り訂正の手法が用いられる。

次の画像を考える。

Image with an assortment of markers

そして、この画像を写真に印刷したものは次のとおりである。

Original image with markers

これらが検出されたマーカー(緑色)である。一部のマーカーは回転していることに注意すること。小さな赤い四角はマーカーの左上コーナーを示している。

Image with detected markers

そして、これらは識別ステップで棄却されたマーカー候補(ピンク色)である。

Image with rejected candidates

arucoモジュールでは、検出は cv::aruco::ArucoDetector::detectMarkers() 関数で行われる。この関数はモジュール内で最も重要であり、その他のすべての機能は cv::aruco::ArucoDetector::detectMarkers() が返す検出済みマーカーに基づいている。

マーカー検出の例:

cv::Mat inputImage;
// ... read inputImage ...
std::vector<int> markerIds;
std::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;
cv::aruco::ArucoDetector detector(dictionary, detectorParams);
detector.detectMarkers(inputImage, markerCorners, markerIds, rejectedCandidates);
The main functionality of ArucoDetector class is detection of markers in an image with detectMarkers(...
Definition aruco_detector.hpp:282
struct DetectorParameters is used by ArucoDetector
Definition aruco_detector.hpp:27

cv::aruco::ArucoDetector オブジェクトを生成する際には、コンストラクタに次の引数を渡す必要がある。

  • 辞書オブジェクト。この場合は定義済み辞書の1つ(cv::aruco::DICT_6X6_250)である。
  • cv::aruco::DetectorParameters 型のオブジェクト。このオブジェクトには、検出処理中にカスタマイズできるすべての引数が含まれる。これらの引数については次節で説明する。

cv::aruco::ArucoDetector::detectMarkers() の引数は次のとおりである。

  • 第1引数は、検出対象のマーカーを含む画像である。
  • The detected markers are stored in the markerCorners and markerIds structures:
    • markerCorners は、検出されたマーカーのコーナーのリストである。各マーカーについて、4つのコーナーが元の順序(左上から時計回り)で返される。したがって、最初のコーナーは左上、続いて右上、右下、左下となる。
    • markerIds は、markerCorners 内の検出された各マーカーのIDのリストである。返される markerCornersmarkerIds のベクトルは同じサイズを持つ点に注意すること。
  • 最後の省略可能な引数 rejectedCandidates は、マーカー候補、すなわち見つかって検討されたが有効なマーカーを含まなかった形状のリストとして返される。各候補も4つのコーナーで定義され、その形式は markerCorners 引数と同じである。この引数は省略可能で、デバッグ目的や「再検出(refind)」戦略でのみ役立つ(cv::aruco::ArucoDetector::refineDetectedMarkers() を参照)。

cv::aruco::ArucoDetector::detectMarkers() の後におそらく次にやりたいのは、マーカーが正しく検出されたかどうかを確認することである。幸い、arucoモジュールには検出されたマーカーを入力画像に描画する関数が用意されており、それが drawDetectedMarkers() である。たとえば次のようになる。

cv::Mat outputImage = inputImage.clone();
cv::aruco::drawDetectedMarkers(outputImage, markerCorners, markerIds);
CV_NODISCARD_STD Mat clone() const
Creates a full copy of the array and the underlying data.
void drawDetectedMarkers(InputOutputArray image, InputArrayOfArrays corners, InputArray ids=noArray(), Scalar borderColor=Scalar(0, 255, 0))
Draw detected markers in image.
  • outputImage は、マーカーが描画される入出力画像である(通常、マーカーが検出された画像と同じものになる)。
  • markerCornersmarkerIds は、cv::aruco::ArucoDetector::detectMarkers() 関数によって返された、検出されたマーカーの構造体である。
Image with detected markers

この関数は可視化のためにのみ提供されており、その使用は省略してもよい点に注意すること。

これら2つの関数を使えば、カメラからマーカーを検出する基本的なマーカー検出ループを作成できる。

cv::aruco::ArucoDetector detector(dictionary, detectorParams);
cv::VideoCapture inputVideo;
int waitTime;
if(!video.empty()) {
inputVideo.open(video);
waitTime = 0;
} else {
inputVideo.open(camId);
waitTime = 10;
}
double totalTime = 0;
int totalIterations = 0;
// set coordinate system
cv::Mat objPoints(4, 1, CV_32FC3);
objPoints.ptr<Vec3f>(0)[0] = Vec3f(-markerLength/2.f, markerLength/2.f, 0);
objPoints.ptr<Vec3f>(0)[1] = Vec3f(markerLength/2.f, markerLength/2.f, 0);
objPoints.ptr<Vec3f>(0)[2] = Vec3f(markerLength/2.f, -markerLength/2.f, 0);
objPoints.ptr<Vec3f>(0)[3] = Vec3f(-markerLength/2.f, -markerLength/2.f, 0);
while(inputVideo.grab()) {
cv::Mat image, imageCopy;
inputVideo.retrieve(image);
double tick = (double)getTickCount();
vector<int> ids;
vector<vector<Point2f> > corners, rejected;
// detect markers and estimate pose
detector.detectMarkers(image, corners, ids, rejected);
size_t nMarkers = corners.size();
vector<Vec3d> rvecs(nMarkers), tvecs(nMarkers);
if(estimatePose && !ids.empty()) {
// Calculate pose for each marker
for (size_t i = 0; i < nMarkers; i++) {
solvePnP(objPoints, corners.at(i), camMatrix, distCoeffs, rvecs.at(i), tvecs.at(i));
}
}
double currentTime = ((double)getTickCount() - tick) / getTickFrequency();
totalTime += currentTime;
totalIterations++;
if(totalIterations % 30 == 0) {
cout << "Detection Time = " << currentTime * 1000 << " ms "
<< "(Mean = " << 1000 * totalTime / double(totalIterations) << " ms)" << endl;
}
// draw results
image.copyTo(imageCopy);
if(!ids.empty()) {
cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);
if(estimatePose) {
for(unsigned int i = 0; i < ids.size(); i++)
cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvecs[i], tvecs[i], markerLength * 1.5f, 2);
}
}
if(showRejected && !rejected.empty())
cv::aruco::drawDetectedMarkers(imageCopy, rejected, noArray(), Scalar(100, 0, 255));
imshow("out", imageCopy);
char key = (char)waitKey(waitTime);
if(key == 27) break;
}

検出引数オブジェクトや棄却された候補の出力ベクトルなど、一部の省略可能な引数が省略されている点に注意すること。

完全に動作する例は、samples/cpp/tutorial_code/objectDetection/ 内の detect_markers.cpp に含まれている。

サンプルは、cv::CommandLineParser を使ってコマンドラインから入力を受け取るようになっている。このファイルの場合、引数の例は次のようになる。

-v=/path_to_opencv/opencv/doc/tutorials/objdetect/aruco_detection/images/singlemarkersoriginal.jpg -d=10

detect_markers.cpp の引数:

const char* keys =
"{d | 0 | dictionary: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2,"
"DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, "
"DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12,"
"DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16,"
"DICT_APRILTAG_16h5=17, DICT_APRILTAG_25h9=18, DICT_APRILTAG_36h10=19, DICT_APRILTAG_36h11=20, DICT_ARUCO_MIP_36h12=21}"
"{cd | | Input file with custom dictionary }"
"{v | | Input from video or image file, if ommited, input comes from camera }"
"{ci | 0 | Camera id if input doesnt come from video (-v) }"
"{c | | Camera intrinsic parameters. Needed for camera pose }"
"{l | 0.1 | Marker side length (in meters). Needed for correct scale in camera pose }"
"{dp | | File of marker detector parameters }"
"{r | | show rejected candidates too }"
"{refine | | Corner refinement: CORNER_REFINE_NONE=0, CORNER_REFINE_SUBPIX=1,"
"CORNER_REFINE_CONTOUR=2, CORNER_REFINE_APRILTAG=3}";

姿勢推定

マーカーを検出した後、おそらく次にやりたいのは、それらを使ってカメラの姿勢を求めることである。

カメラ姿勢推定を行うには、カメラのキャリブレーション引数を知っておく必要がある。これはカメラ行列と歪み係数である。カメラのキャリブレーション方法がわからない場合は、calibrateCamera() 関数やOpenCVのキャリブレーションチュートリアルを参照するとよい。Calibration with ArUco and ChArUco チュートリアルで説明されているように、arucoモジュールを使ってカメラをキャリブレーションすることもできる。これは、カメラの光学系が変更されない限り(たとえばフォーカスの変更など)、一度だけ行えばよい点に注意すること。

キャリブレーションの結果として、カメラ行列(焦点距離とカメラ中心の座標を持つ3x3要素の行列。いわゆる内部パラメータ)と、歪み係数(カメラが生み出す歪みをモデル化する5個以上の要素を持つベクトル)が得られる。

ArUcoマーカーで姿勢を推定する場合、各マーカーの姿勢を個別に推定できる。複数のマーカーから1つの姿勢を推定したい場合は、ArUcoボードを使用する(Detection of ArUco Boards チュートリアルを参照)。単一のマーカーの代わりにArUcoボードを使うと、一部のマーカーが隠れていても対応できる。

マーカーに対するカメラの姿勢は、マーカー座標系からカメラ座標系への3次元変換である。これは回転ベクトルと並進ベクトルで指定される。OpenCV はこれを行うために cv::solvePnP() 関数を提供している。

Mat camMatrix, distCoeffs;
if(estimatePose) {
// You can read camera parameters from tutorial_camera_params.yml
readCameraParamsFromCommandLine(parser, camMatrix, distCoeffs);
}
// set coordinate system
cv::Mat objPoints(4, 1, CV_32FC3);
objPoints.ptr<Vec3f>(0)[0] = Vec3f(-markerLength/2.f, markerLength/2.f, 0);
objPoints.ptr<Vec3f>(0)[1] = Vec3f(markerLength/2.f, markerLength/2.f, 0);
objPoints.ptr<Vec3f>(0)[2] = Vec3f(markerLength/2.f, -markerLength/2.f, 0);
objPoints.ptr<Vec3f>(0)[3] = Vec3f(-markerLength/2.f, -markerLength/2.f, 0);
vector<int> ids;
vector<vector<Point2f> > corners, rejected;
// detect markers and estimate pose
detector.detectMarkers(image, corners, ids, rejected);
size_t nMarkers = corners.size();
vector<Vec3d> rvecs(nMarkers), tvecs(nMarkers);
if(estimatePose && !ids.empty()) {
// Calculate pose for each marker
for (size_t i = 0; i < nMarkers; i++) {
solvePnP(objPoints, corners.at(i), camMatrix, distCoeffs, rvecs.at(i), tvecs.at(i));
}
}
  • corners 引数は、cv::aruco::ArucoDetector::detectMarkers() 関数によって返されるマーカーコーナーのベクトルである。
  • 第2引数は、メートルまたは任意の単位でのマーカーの一辺のサイズである。推定された姿勢の並進ベクトルは同じ単位になる点に注意すること。
  • camMatrixdistCoeffs は、カメラキャリブレーション処理中に生成されたカメラキャリブレーション引数である。
  • 出力引数 rvecstvecs は、corners 内の検出された各マーカーについての、それぞれ回転ベクトルと並進ベクトルである。

この関数が想定するマーカー座標系は、次の画像のように、マーカーの中心(デフォルト)または左上コーナーに配置され、Z軸が外向きになっている。軸と色の対応はX:赤、Y:緑、Z:青である。この画像中で回転したマーカーの軸の向きに注意すること。

Image with axes drawn

OpenCVは上の画像のように軸を描画する関数を提供しており、姿勢推定を確認できる。

// draw results
image.copyTo(imageCopy);
if(!ids.empty()) {
cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);
if(estimatePose) {
for(unsigned int i = 0; i < ids.size(); i++)
cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvecs[i], tvecs[i], markerLength * 1.5f, 2);
}
}
  • imageCopy は、検出されたマーカーが表示される入出力画像である。
  • camMatrixdistCoeffs は、カメラキャリブレーション引数である。
  • rvecs[i]tvecs[i] は、検出された各マーカーについての、それぞれ回転ベクトルと並進ベクトルである。
  • 最後の引数は軸の長さで、tvecと同じ単位(通常はメートル)である。

サンプル動画:

完全に動作する例は、samples/cpp/tutorial_code/objectDetection/ 内の detect_markers.cpp に含まれている。

サンプルは、cv::CommandLineParser を使ってコマンドラインから入力を受け取るようになっている。このファイルの場合、引数の例は次のようになる。

-v=/path_to_opencv/opencv/doc/tutorials/objdetect/aruco_detection/images/singlemarkersoriginal.jpg -d=10
-c=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_camera_params.yml

detect_markers.cpp の引数:

const char* keys =
"{d | 0 | dictionary: DICT_4X4_50=0, DICT_4X4_100=1, DICT_4X4_250=2,"
"DICT_4X4_1000=3, DICT_5X5_50=4, DICT_5X5_100=5, DICT_5X5_250=6, DICT_5X5_1000=7, "
"DICT_6X6_50=8, DICT_6X6_100=9, DICT_6X6_250=10, DICT_6X6_1000=11, DICT_7X7_50=12,"
"DICT_7X7_100=13, DICT_7X7_250=14, DICT_7X7_1000=15, DICT_ARUCO_ORIGINAL = 16,"
"DICT_APRILTAG_16h5=17, DICT_APRILTAG_25h9=18, DICT_APRILTAG_36h10=19, DICT_APRILTAG_36h11=20, DICT_ARUCO_MIP_36h12=21}"
"{cd | | Input file with custom dictionary }"
"{v | | Input from video or image file, if ommited, input comes from camera }"
"{ci | 0 | Camera id if input doesnt come from video (-v) }"
"{c | | Camera intrinsic parameters. Needed for camera pose }"
"{l | 0.1 | Marker side length (in meters). Needed for correct scale in camera pose }"
"{dp | | File of marker detector parameters }"
"{r | | show rejected candidates too }"
"{refine | | Corner refinement: CORNER_REFINE_NONE=0, CORNER_REFINE_SUBPIX=1,"
"CORNER_REFINE_CONTOUR=2, CORNER_REFINE_APRILTAG=3}";

辞書の選択

arucoモジュールは、マーカーの辞書を表すために Dictionary クラスを提供している。

マーカーサイズと辞書内のマーカー数に加えて、辞書にはもう1つ重要な引数がある。それはマーカー間距離である。マーカー間距離は、辞書内のマーカー間の最小ハミング距離であり、誤りを検出し訂正する辞書の能力を決定する。

一般に、辞書のサイズが小さく、マーカーサイズが大きいほどマーカー間距離は大きくなり、その逆も成り立つ。ただし、サイズの大きいマーカーは画像から抽出すべきビット数が多くなるため、検出はより難しくなる。

たとえば、アプリケーションで10個のマーカーしか必要としない場合は、1000個のマーカーで構成される辞書を使うよりも、その10個のマーカーだけで構成される辞書を使うほうがよい。その理由は、10個のマーカーで構成される辞書のほうがマーカー間距離が大きくなり、誤りに対してより頑健になるからである。

結果として、arucoモジュールにはシステムの頑健性を高められるよう、マーカーの辞書を選択する複数の方法が用意されている。

定義済みの辞書

これは辞書を選択する最も簡単な方法である。arucoモジュールには、さまざまなマーカーサイズとマーカー数の定義済み辞書のセットが含まれている。たとえば次のとおりである。

cv::aruco::DICT_6X6_250 は、6x6ビットで合計250個のマーカーを持つ定義済みマーカー辞書の一例である。

提供されているすべての辞書の中から、アプリケーションに合う最小のものを選ぶことが推奨される。たとえば、6x6ビットのマーカーが200個必要な場合は、cv::aruco::DICT_6X6_1000 よりも cv::aruco::DICT_6X6_250 を使うほうがよい。辞書が小さいほど、マーカー間距離は大きくなる。

利用可能な定義済み辞書の一覧は、PredefinedDictionaryType 列挙型のドキュメントに記載されている。

辞書の自動生成

辞書は、所望のマーカー数とビット数に合わせ、マーカー間距離を最適化するように自動生成できる。

Dictionary extendDictionary(int nMarkers, int markerSize, const Dictionary &baseDictionary=Dictionary(), int randomSeed=0)
Extend base dictionary by new nMarkers.

これにより、5x5ビットの36個のマーカーで構成されるカスタマイズされた辞書が生成される。この処理は引数によっては数秒かかることがある(辞書が大きくビット数が多いほど遅くなる)。

また、opencv/samples/cpp 内の aruco_dict_utils.cpp サンプルを使うこともできる。このサンプルは、生成された辞書の最小ハミング距離を計算し、反射に強いマーカーを作成することも可能にする。

辞書の手動定義

最後に、辞書を手動で構成することもでき、任意の符号化を使用できる。そのためには、cv::aruco::Dictionary オブジェクトの引数を手動で割り当てる必要がある。これを手動で行う特別な理由がない限り、前述の方法のいずれかを使うほうが望ましい点に注意すること。

cv::aruco::Dictionary の引数は次のとおりである。

class Dictionary {
public:
cv::Mat bytesList; // marker code information
int markerSize; // number of bits per dimension
int maxCorrectionBits; // maximum number of bits that can be corrected
...
}

bytesList は、マーカーコードに関するすべての情報を含む配列である。markerSize は各マーカー次元のサイズである(たとえば5x5ビットのマーカーなら5)。最後に maxCorrectionBits は、マーカー検出中に訂正できる誤りビットの最大数である。この値が大きすぎると、多数の偽陽性につながる可能性がある。

bytesList の各行は、辞書のマーカーの1つを表す。ただし、マーカーはバイナリ形式では格納されず、検出を簡略化するための特別な形式で格納される。

幸い、マーカーは静的メソッド Dictionary::getByteListFromBits() を使ってこの形式に簡単に変換できる。

例:

// Markers of 6x6 bits
dictionary.markerSize = 6;
// Maximum number of bit corrections
dictionary.maxCorrectionBits = 3;
// Let's create a dictionary of 100 markers
for(int i = 0; i < 100; i++)
{
// Assume generateMarkerBits() generates a new marker in binary format, so that
// markerBits is a 6x6 matrix of CV_8UC1 type, only containing 0s and 1s
cv::Mat markerBits = generateMarkerBits();
cv::Mat markerCompressed = cv::aruco::Dictionary::getByteListFromBits(markerBits);
// Add the marker as a new row
dictionary.bytesList.push_back(markerCompressed);
}
void push_back(const _Tp &elem)
Adds elements to the bottom of the matrix.
int maxCorrectionBits
maximum number of bits that can be corrected
Definition aruco_dictionary.hpp:34
int markerSize
number of bits per dimension
Definition aruco_dictionary.hpp:33
Mat bytesList
marker code information. See class description for more details
Definition aruco_dictionary.hpp:32
static Mat getByteListFromBits(const Mat &bits)
Transform matrix of bits to list of bytes with 4 marker rotations.

検出器の引数

cv::aruco::ArucoDetector の引数の1つは cv::aruco::DetectorParameters オブジェクトである。このオブジェクトには、マーカー検出処理中にカスタマイズできるすべてのオプションが含まれる。

本節では、各検出器引数について説明する。引数は、それらが関与する処理に応じて分類できる。

しきい値処理

マーカー検出処理における最初のステップの1つが、入力画像の適応的しきい値処理である。

例えば、上で使用したサンプル画像に対するしきい値処理後の画像は次のようになる。

Thresholded image

このしきい値処理は、以下の引数でカスタマイズできる。

adaptiveThreshWinSizeMin, adaptiveThreshWinSizeMax, adaptiveThreshWinSizeStep

adaptiveThreshWinSizeMinadaptiveThreshWinSizeMax の引数は、適応的しきい値処理に用いるしきい値処理ウィンドウサイズ(ピクセル単位)を選択する区間を表す(詳細はOpenCVの threshold() および adaptiveThreshold() 関数を参照)。

adaptiveThreshWinSizeStep 引数は、adaptiveThreshWinSizeMin から adaptiveThreshWinSizeMax までのウィンドウサイズの増分を示す。

例えば、adaptiveThreshWinSizeMin = 5、adaptiveThreshWinSizeMax = 21、adaptiveThreshWinSizeStep = 4 の場合、ウィンドウサイズ 5、9、13、17、21 の5つのしきい値処理ステップが行われる。各しきい値処理画像から、マーカー候補が抽出される。

ウィンドウサイズの値が小さすぎると、マーカーのサイズが大きすぎる場合にマーカーの境界が「途切れる」ことがあり、次の画像のように検出されなくなる。

Broken marker image

一方、値が大きすぎると、マーカーが小さすぎる場合に同様の影響が生じ、性能も低下しうる。さらに処理がグローバルなしきい値処理に近づき、適応的処理の利点が失われる。

最も単純なケースは、adaptiveThreshWinSizeMinadaptiveThreshWinSizeMax に同じ値を使うことで、これは1回のしきい値処理ステップになる。ただし、通常はウィンドウサイズに値の範囲を持たせる方がよい。とはいえ、しきい値処理ステップが多すぎると性能を大きく低下させることもある。

参照
cv::aruco::DetectorParameters::adaptiveThreshWinSizeMin, cv::aruco::DetectorParameters::adaptiveThreshWinSizeMax, cv::aruco::DetectorParameters::adaptiveThreshWinSizeStep

adaptiveThreshConstant

adaptiveThreshConstant 引数は、しきい値処理で加算される定数値を表す(詳細はOpenCVの threshold() および adaptiveThreshold() 関数を参照)。デフォルト値はほとんどの場合に適した選択である。

参照
cv::aruco::DetectorParameters::adaptiveThreshConstant

輪郭のフィルタリング

しきい値処理の後、輪郭が検出される。ただし、すべての輪郭がマーカー候補とみなされるわけではない。マーカーである可能性が非常に低い輪郭を破棄するため、複数のステップでフィルタリングされる。このセクションの引数は、このフィルタリング処理をカスタマイズする。

ほとんどの場合、これは検出能力と性能のバランスの問題であることに注意する必要がある。検討対象となるすべての輪郭は後続の段階で処理され、それらは通常より計算コストが高い。したがって、無効な候補を後の段階よりもこの段階で破棄する方が望ましい。

一方、フィルタリング条件が厳しすぎると、実際のマーカーの輪郭が破棄され、その結果検出されなくなる可能性がある。

minMarkerPerimeterRate と maxMarkerPerimeterRate

これらの引数は、マーカーの最小サイズと最大サイズ、具体的にはマーカー周囲長の最小値と最大値を決定する。これらは絶対的なピクセル値ではなく、入力画像の最大の次元に対する相対値で指定される。

例えば、サイズ 640x480 の画像で相対的なマーカー周囲長の最小値が 0.05 の場合、640 が画像の最大の次元であるため、マーカー周囲長の最小値は 640x0.05 = 32 ピクセルになる。maxMarkerPerimeterRate 引数についても同様である。

minMarkerPerimeterRate が小さすぎると、後続の段階で検討される輪郭が大幅に増えるため、検出性能が著しく低下しうる。maxMarkerPerimeterRate 引数ではこのペナルティはそれほど顕著ではない。通常、大きな輪郭よりも小さな輪郭の方がはるかに多いためである。minMarkerPerimeterRate の値が 0 で maxMarkerPerimeterRate の値が 4(以上)であれば、画像内のすべての輪郭を検討することと等価になるが、性能上の理由からこれは推奨されない。

参照
cv::aruco::DetectorParameters::minMarkerPerimeterRate, cv::aruco::DetectorParameters::maxMarkerPerimeterRate

polygonalApproxAccuracyRate

各候補に多角形近似が適用され、正方形に近い形状のものだけが受け入れられる。この値は、多角形近似が生み出しうる最大誤差を決定する(詳細は approxPolyDP() 関数を参照)。

この引数は候補の長さ(ピクセル単位)に対する相対値である。したがって、候補の周囲長が 100 ピクセルで polygonalApproxAccuracyRate の値が 0.04 の場合、最大誤差は 100x0.04=5.4 ピクセルになる。

ほとんどの場合、デフォルト値で十分に機能するが、歪みの大きい画像ではより大きな誤差値が必要になることがある。

参照
cv::aruco::DetectorParameters::polygonalApproxAccuracyRate

minCornerDistanceRate

同じマーカー内の任意のコーナーペア間の最小距離。マーカーの周囲長に対する相対値で表される。ピクセル単位の最小距離は Perimeter * minCornerDistanceRate である。

参照
cv::aruco::DetectorParameters::minCornerDistanceRate

minMarkerDistanceRate

2つの異なるマーカーの任意のコーナーペア間の最小距離。2つのマーカーのうち小さい方のマーカー周囲長に対する相対値で表される。2つの候補が近すぎる場合、小さい方が無視される。

参照
cv::aruco::DetectorParameters::minMarkerDistanceRate

minDistanceToBorder

マーカーのいずれかのコーナーから画像の境界までの最小距離(ピクセル単位)。画像の境界で部分的に隠れているマーカーでも、隠れている部分が小さければ正しく検出できる。ただし、コーナーの1つが隠れている場合、返されるコーナーは通常、画像の境界付近の誤った位置に配置される。

マーカーのコーナー位置が重要な場合、例えば姿勢推定を行いたい場合は、コーナーが画像の境界に近すぎるマーカーを破棄する方がよい。それ以外の場合は必要ない。

参照
cv::aruco::DetectorParameters::minDistanceToBorder

ビットの抽出

候補の検出後、各候補のビットが解析され、マーカーであるかどうかが判定される。

バイナリコード自体を解析する前に、ビットを抽出する必要がある。そのために、透視歪みを補正し、得られた画像を大津のしきい値処理(Otsu threshold)でしきい値処理して黒と白のピクセルを分離する。

これは、マーカーの透視歪みを除去した後に得られる画像の例である。

Perspective removing

次に、画像はマーカー内のビット数と同じ数のセルを持つグリッドに分割される。各セルで黒と白のピクセル数を数え、(多数を占める値から)そのセルに割り当てるビット値を決定する。

Marker cells

この処理をカスタマイズできる引数がいくつかある。

markerBorderBits

この引数はマーカー境界の幅を示す。各ビットのサイズに対する相対値である。したがって、値が 2 の場合は境界が内部ビット2つ分の幅を持つことを示す。

この引数は、使用しているマーカーの境界サイズと一致する必要がある。境界サイズは、generateImageMarker() などのマーカー描画関数で設定できる。

参照
cv::aruco::DetectorParameters::markerBorderBits

minOtsuStdDev

この値は、大津のしきい値処理を行うためのピクセル値の最小標準偏差を決定する。偏差が小さい場合、おそらく正方形全体が黒(または白)であることを意味し、大津の手法を適用しても意味がない。この場合、平均値が 128 より高いか低いかに応じて、すべてのビットが 0(または 1)に設定される。

参照
cv::aruco::DetectorParameters::minOtsuStdDev

perspectiveRemovePixelPerCell

この引数は、透視歪みを補正した後に得られる画像における(セルあたりの)ピクセル数を決定する(境界を含む)。これは上の画像の赤い正方形のサイズである。

例えば、5x5 ビットで境界サイズが 1 ビットのマーカーを扱っているとする(markerBorderBits を参照)。すると、1次元あたりのセル/ビットの総数は 5 + 2*1 = 7 になる(境界は両側に数える必要がある)。セルの総数は 7x7 である。

perspectiveRemovePixelPerCell の値が 10 の場合、得られる画像のサイズは 10*7 = 70 -> 70x70 ピクセルになる。

この引数の値を大きくすると、(ある程度まで)ビット抽出処理を改善できるが、性能を低下させる可能性がある。

参照
cv::aruco::DetectorParameters::perspectiveRemovePixelPerCell

perspectiveRemoveIgnoredMarginPerCell

各セルのビットを抽出する際、黒と白のピクセル数が数えられる。一般に、セルのすべてのピクセルを考慮することは推奨されない。代わりに、セルのマージン部分の一部のピクセルを無視する方がよい。

その理由は、透視歪みを除去した後、セルの色は一般に完全には分離されておらず、白いセルが黒いセルのピクセルの一部に入り込むことがある(逆も同様)ためである。したがって、誤ったピクセルを数えるのを避けるため、一部のピクセルを無視する方がよい。

例えば、次の画像では、

Marker cell margins

緑の正方形の内側のピクセルのみが考慮される。右の画像を見ると、結果として得られるピクセルには隣接セルからのノイズが少ないことがわかる。perspectiveRemoveIgnoredMarginPerCell 引数は、赤い正方形と緑の正方形の差を示す。

この引数はセルの総サイズに対する相対値である。例えば、セルサイズが 40 ピクセルでこの引数の値が 0.1 の場合、セル内で 40*0.1=4 ピクセルのマージンが無視される。つまり、各セルで実際に解析されるピクセルの総数は、40x40 ではなく 32x32 になる。

参照
cv::aruco::DetectorParameters::perspectiveRemoveIgnoredMarginPerCell

マーカーの識別

ビットの抽出後、次のステップは、抽出されたコードがマーカー辞書に属するかどうかを確認することであり、必要に応じて誤り訂正を行うことができる。

maxErroneousBitsInBorderRate

マーカー境界のビットは黒であるべきである。この引数は、境界内で許容される誤りビット数、すなわち境界内の白ビットの最大数を指定する。マーカー内のビットの総数に対する相対値で表される。

参照
cv::aruco::DetectorParameters::maxErroneousBitsInBorderRate

errorCorrectionRate

各マーカー辞書には、訂正可能なビット数の理論上の最大値がある(Dictionary.maxCorrectionBits)。ただし、この値は errorCorrectionRate 引数で変更できる。

例えば、(使用する辞書で)訂正可能なビット数が 6 で errorCorrectionRate の値が 0.5 の場合、実際に訂正可能なビット数の最大値は 6*0.5=3 ビットになる。

この値は、誤検出を避けるために誤り訂正能力を低下させるのに役立つ。

参照
cv::aruco::DetectorParameters::errorCorrectionRate

コーナーの精密化

マーカーが検出・識別された後、最後のステップはコーナー位置のサブピクセル精緻化を行うことである(OpenCVの cornerSubPix() および cv::aruco::CornerRefineMethod を参照)。

このステップは省略可能で、例えば姿勢推定のためにマーカーのコーナー位置を正確にする必要がある場合にのみ意味を持つことに注意する。これは通常、時間のかかるステップであるため、デフォルトでは無効になっている。

cornerRefinementMethod

この引数は、コーナーのサブピクセル処理を行うかどうか、また行う場合にどの手法を使用するかを決定する。正確なコーナーが不要な場合は無効にできる。指定可能な値は CORNER_REFINE_NONECORNER_REFINE_SUBPIXCORNER_REFINE_CONTOURCORNER_REFINE_APRILTAG である。

参照
cv::aruco::DetectorParameters::cornerRefinementMethod

cornerRefinementWinSize

この引数は、コーナー精緻化処理の最大ウィンドウサイズを決定する。

値が大きいと、画像の近くのコーナーがウィンドウ領域に含まれることがあり、その結果、処理中にマーカーのコーナーが別の誤った位置に移動してしまう。また、性能に影響する可能性もある。ArUcoマーカーが小さすぎる場合はウィンドウサイズが縮小されることがある。cv::aruco::DetectorParameters::relativeCornerRefinmentWinSize を確認すること。最終的なウィンドウサイズは min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize) として計算される。ここで averageArucoModuleSize はピクセル単位でのArUcoマーカーの平均モジュールサイズである。

参照
cv::aruco::DetectorParameters::cornerRefinementWinSize

relativeCornerRefinmentWinSize

Arucoモジュールサイズに対する、コーナー精緻化用の動的ウィンドウサイズ(デフォルト0.3)。

最終的なウィンドウサイズは min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize) として計算される。ここで averageArucoModuleSize はピクセル単位でのArUcoマーカーの平均モジュールサイズである。互いに離れた位置にあるマーカーの場合、この引数の値を 0.4~0.5 に増やすと有用なことがある。互いに近い位置にあるマーカーの場合、引数の値を 0.1~0.2 に減らすと有用なことがある。

参照
cv::aruco::DetectorParameters::relativeCornerRefinmentWinSize

cornerRefinementMaxIterations と cornerRefinementMinAccuracy

これら2つの引数は、サブピクセル精緻化処理の停止基準を決定する。cornerRefinementMaxIterations は最大反復回数を示し、cornerRefinementMinAccuracy は処理を停止する前の最小誤差値を示す。

反復回数が多すぎると、性能に影響する可能性がある。一方、少なすぎると、サブピクセル精緻化が不十分になることがある。

参照
cv::aruco::DetectorParameters::cornerRefinementMaxIterations, cv::aruco::DetectorParameters::cornerRefinementMinAccuracy