![]() |
OpenCV 4.13.0
Open Source Computer Vision
|
次のチュートリアル: ArUcoボードの検出
| 原著者 | Sergio Garrido, Alexander Panov |
| 互換性 | OpenCV >= 4.7.0 |
姿勢推定は、多くのコンピュータビジョンの応用において非常に重要である: ロボットのナビゲーション、拡張現実(AR)など多岐にわたる。この処理は、実環境中の点とそれらの2D画像投影との対応を見つけることに基づいている。これは通常困難なステップであり、そのため作業を容易にするために合成マーカーや基準マーカー(fiducial marker)を用いるのが一般的である。
最も普及しているアプローチの一つが、2値の正方形基準マーカーの利用である。これらのマーカーの主な利点は、1つのマーカーだけでカメラ姿勢を求めるのに十分な対応(その4つのコーナー)が得られることである。また、内部の2値符号化により特に頑健であり、誤り検出・訂正技術を適用できる可能性をもたらす。
arucoモジュールは、Rafael MuñozとSergio Garridoによって開発された正方形基準マーカー検出のための人気ライブラリである ArUco library [101] に基づいている。
aruco機能は次に含まれている:
ArUcoマーカーは、太い黒い枠と、その識別子(id)を決定する内部の2値行列で構成される合成正方形マーカーである。黒い枠は画像内での高速な検出を容易にし、2値符号化はその識別と誤り検出・訂正技術の適用を可能にする。マーカーサイズは内部行列のサイズを決定する。例えば4x4のマーカーサイズは16ビットで構成される。
ArUcoマーカーの例:
マーカーは環境中で回転した状態で見つかることがある点に注意すべきである。しかし検出処理では、各コーナーが一意に識別されるよう、その元の回転を判定できる必要がある。これも2値符号化に基づいて行われる。
マーカーの辞書とは、特定の応用で考慮されるマーカーの集合である。単純に言えば、それぞれのマーカーの2値符号化のリストである。
辞書の主な特性は、辞書サイズとマーカーサイズである。
arucoモジュールには、さまざまな辞書サイズとマーカーサイズをカバーする、いくつかの定義済み辞書が含まれている。
マーカーidは2値符号化を10進数に変換して得られる数である、と考えるかもしれない。しかし、マーカーサイズが大きい場合はビット数が多すぎて、そのような巨大な数を扱うのは実用的でないため、これは不可能である。代わりに、マーカーidは単に、それが属する辞書内でのマーカーのインデックスである。例えば、辞書内の最初の5つのマーカーは 0, 1, 2, 3, 4 というidを持つ。
辞書に関する詳細は「Selecting a dictionary」の節で説明する。
検出の前に、環境中に配置するためにマーカーを印刷する必要がある。マーカー画像は generateImageMarker() 関数を使って生成できる。
例えば、次の呼び出しを分析してみよう:
まず、cv::aruco::Dictionary オブジェクトを、arucoモジュールに用意された定義済み辞書の中から1つ選んで生成する。具体的には、この辞書は250個のマーカーで構成され、マーカーサイズは6x6ビットである(cv::aruco::DICT_6X6_250)。
cv::aruco::generateImageMarker() の引数は次のとおりである。
cv::aruco::Dictionary オブジェクトである。cv::aruco::DICT_6X6_250 のマーカー23を指す。各辞書はそれぞれ異なる数のマーカーで構成されている点に注意すること。この場合、有効なIDは0から249までである。有効範囲外のIDを指定すると例外が発生する。生成された画像は次のとおりである。
完全に動作する例は、samples/cpp/tutorial_code/objectDetection/ 内の create_marker.cpp に含まれている。
サンプルは、cv::CommandLineParser を使ってコマンドラインから入力を受け取るようになっている。このファイルの場合、引数の例は次のようになる。
create_marker.cpp の引数:
ArUcoマーカーを含む画像が与えられると、検出処理は検出されたマーカーのリストを返す。検出された各マーカーには次の情報が含まれる。
マーカー検出処理は、主に2つのステップで構成される。
次の画像を考える。
そして、この画像を写真に印刷したものは次のとおりである。
これらが検出されたマーカー(緑色)である。一部のマーカーは回転していることに注意すること。小さな赤い四角はマーカーの左上コーナーを示している。
そして、これらは識別ステップで棄却されたマーカー候補(ピンク色)である。
arucoモジュールでは、検出は cv::aruco::ArucoDetector::detectMarkers() 関数で行われる。この関数はモジュール内で最も重要であり、その他のすべての機能は cv::aruco::ArucoDetector::detectMarkers() が返す検出済みマーカーに基づいている。
マーカー検出の例:
cv::aruco::ArucoDetector オブジェクトを生成する際には、コンストラクタに次の引数を渡す必要がある。
cv::aruco::DICT_6X6_250)である。cv::aruco::DetectorParameters 型のオブジェクト。このオブジェクトには、検出処理中にカスタマイズできるすべての引数が含まれる。これらの引数については次節で説明する。cv::aruco::ArucoDetector::detectMarkers() の引数は次のとおりである。
markerCorners and markerIds structures:markerCorners は、検出されたマーカーのコーナーのリストである。各マーカーについて、4つのコーナーが元の順序(左上から時計回り)で返される。したがって、最初のコーナーは左上、続いて右上、右下、左下となる。markerIds は、markerCorners 内の検出された各マーカーのIDのリストである。返される markerCorners と markerIds のベクトルは同じサイズを持つ点に注意すること。rejectedCandidates は、マーカー候補、すなわち見つかって検討されたが有効なマーカーを含まなかった形状のリストとして返される。各候補も4つのコーナーで定義され、その形式は markerCorners 引数と同じである。この引数は省略可能で、デバッグ目的や「再検出(refind)」戦略でのみ役立つ(cv::aruco::ArucoDetector::refineDetectedMarkers() を参照)。cv::aruco::ArucoDetector::detectMarkers() の後におそらく次にやりたいのは、マーカーが正しく検出されたかどうかを確認することである。幸い、arucoモジュールには検出されたマーカーを入力画像に描画する関数が用意されており、それが drawDetectedMarkers() である。たとえば次のようになる。
outputImage は、マーカーが描画される入出力画像である(通常、マーカーが検出された画像と同じものになる)。markerCorners と markerIds は、cv::aruco::ArucoDetector::detectMarkers() 関数によって返された、検出されたマーカーの構造体である。
この関数は可視化のためにのみ提供されており、その使用は省略してもよい点に注意すること。
これら2つの関数を使えば、カメラからマーカーを検出する基本的なマーカー検出ループを作成できる。
検出引数オブジェクトや棄却された候補の出力ベクトルなど、一部の省略可能な引数が省略されている点に注意すること。
完全に動作する例は、samples/cpp/tutorial_code/objectDetection/ 内の detect_markers.cpp に含まれている。
サンプルは、cv::CommandLineParser を使ってコマンドラインから入力を受け取るようになっている。このファイルの場合、引数の例は次のようになる。
detect_markers.cpp の引数:
マーカーを検出した後、おそらく次にやりたいのは、それらを使ってカメラの姿勢を求めることである。
カメラ姿勢推定を行うには、カメラのキャリブレーション引数を知っておく必要がある。これはカメラ行列と歪み係数である。カメラのキャリブレーション方法がわからない場合は、calibrateCamera() 関数やOpenCVのキャリブレーションチュートリアルを参照するとよい。Calibration with ArUco and ChArUco チュートリアルで説明されているように、arucoモジュールを使ってカメラをキャリブレーションすることもできる。これは、カメラの光学系が変更されない限り(たとえばフォーカスの変更など)、一度だけ行えばよい点に注意すること。
キャリブレーションの結果として、カメラ行列(焦点距離とカメラ中心の座標を持つ3x3要素の行列。いわゆる内部パラメータ)と、歪み係数(カメラが生み出す歪みをモデル化する5個以上の要素を持つベクトル)が得られる。
ArUcoマーカーで姿勢を推定する場合、各マーカーの姿勢を個別に推定できる。複数のマーカーから1つの姿勢を推定したい場合は、ArUcoボードを使用する(Detection of ArUco Boards チュートリアルを参照)。単一のマーカーの代わりにArUcoボードを使うと、一部のマーカーが隠れていても対応できる。
マーカーに対するカメラ姿勢は、マーカー座標系からカメラ座標系への3次元変換である。これは回転ベクトルと並進ベクトルで指定される。OpenCVはこれを行うために cv::solvePnP() 関数を提供している。
corners 引数は、cv::aruco::ArucoDetector::detectMarkers() 関数によって返されるマーカーコーナーのベクトルである。camMatrix と distCoeffs は、カメラキャリブレーション処理中に生成されたカメラキャリブレーション引数である。rvecs と tvecs は、corners 内の検出された各マーカーについての、それぞれ回転ベクトルと並進ベクトルである。この関数が想定するマーカー座標系は、次の画像のように、マーカーの中心(デフォルト)または左上コーナーに配置され、Z軸が外向きになっている。軸と色の対応はX:赤、Y:緑、Z:青である。この画像中で回転したマーカーの軸の向きに注意すること。
OpenCVは上の画像のように軸を描画する関数を提供しており、姿勢推定を確認できる。
imageCopy は、検出されたマーカーが表示される入出力画像である。camMatrix と distCoeffs は、カメラキャリブレーション引数である。rvecs[i] と tvecs[i] は、検出された各マーカーについての、それぞれ回転ベクトルと並進ベクトルである。サンプル動画:
完全に動作する例は、samples/cpp/tutorial_code/objectDetection/ 内の detect_markers.cpp に含まれている。
サンプルは、cv::CommandLineParser を使ってコマンドラインから入力を受け取るようになっている。このファイルの場合、引数の例は次のようになる。
detect_markers.cpp の引数:
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 列挙型のドキュメントに記載されている。
辞書は、所望のマーカー数とビット数に合わせ、マーカー間距離を最適化するように自動生成できる。
これにより、5x5ビットの36個のマーカーで構成されるカスタマイズされた辞書が生成される。この処理は引数によっては数秒かかることがある(辞書が大きくビット数が多いほど遅くなる)。
また、opencv/samples/cpp 内の aruco_dict_utils.cpp サンプルを使うこともできる。このサンプルは、生成された辞書の最小ハミング距離を計算し、反射に強いマーカーを作成することも可能にする。
最後に、辞書を手動で構成することもでき、任意の符号化を使用できる。そのためには、cv::aruco::Dictionary オブジェクトの引数を手動で割り当てる必要がある。これを手動で行う特別な理由がない限り、前述の方法のいずれかを使うほうが望ましい点に注意すること。
cv::aruco::Dictionary の引数は次のとおりである。
bytesList は、マーカーコードに関するすべての情報を含む配列である。markerSize は各マーカー次元のサイズである(たとえば5x5ビットのマーカーなら5)。最後に maxCorrectionBits は、マーカー検出中に訂正できる誤りビットの最大数である。この値が大きすぎると、多数の偽陽性につながる可能性がある。
bytesList の各行は、辞書のマーカーの1つを表す。ただし、マーカーはバイナリ形式では格納されず、検出を簡略化するための特別な形式で格納される。
幸い、マーカーは静的メソッド Dictionary::getByteListFromBits() を使ってこの形式に簡単に変換できる。
例:
cv::aruco::ArucoDetector の引数の1つは cv::aruco::DetectorParameters オブジェクトである。このオブジェクトには、マーカー検出処理中にカスタマイズできるすべてのオプションが含まれる。
本節では、各検出器引数について説明する。引数は、それらが関与する処理に応じて分類できる。
マーカー検出処理における最初のステップの1つが、入力画像の適応的しきい値処理である。
例えば、上で使用したサンプル画像に対するしきい値処理後の画像は次のようになる。
このしきい値処理は、以下の引数でカスタマイズできる。
adaptiveThreshWinSizeMin と adaptiveThreshWinSizeMax の引数は、適応的しきい値処理に用いるしきい値処理ウィンドウサイズ(ピクセル単位)を選択する区間を表す(詳細はOpenCVの threshold() および adaptiveThreshold() 関数を参照)。
adaptiveThreshWinSizeStep 引数は、adaptiveThreshWinSizeMin から adaptiveThreshWinSizeMax までのウィンドウサイズの増分を示す。
例えば、adaptiveThreshWinSizeMin = 5、adaptiveThreshWinSizeMax = 21、adaptiveThreshWinSizeStep = 4 の場合、ウィンドウサイズ 5、9、13、17、21 の5つのしきい値処理ステップが行われる。各しきい値処理画像から、マーカー候補が抽出される。
ウィンドウサイズの値が小さすぎると、マーカーのサイズが大きすぎる場合にマーカーの境界が「途切れる」ことがあり、次の画像のように検出されなくなる。
一方、値が大きすぎると、マーカーが小さすぎる場合に同様の影響が生じ、性能も低下しうる。さらに処理がグローバルなしきい値処理に近づき、適応的処理の利点が失われる。
最も単純なケースは、adaptiveThreshWinSizeMin と adaptiveThreshWinSizeMax に同じ値を使うことで、これは1回のしきい値処理ステップになる。ただし、通常はウィンドウサイズに値の範囲を持たせる方がよい。とはいえ、しきい値処理ステップが多すぎると性能を大きく低下させることもある。
adaptiveThreshConstant 引数は、しきい値処理で加算される定数値を表す(詳細はOpenCVの threshold() および adaptiveThreshold() 関数を参照)。デフォルト値はほとんどの場合に適した選択である。
しきい値処理の後、輪郭が検出される。ただし、すべての輪郭がマーカー候補とみなされるわけではない。マーカーである可能性が非常に低い輪郭を破棄するため、複数のステップでフィルタリングされる。このセクションの引数は、このフィルタリング処理をカスタマイズする。
ほとんどの場合、これは検出能力と性能のバランスの問題であることに注意する必要がある。検討対象となるすべての輪郭は後続の段階で処理され、それらは通常より計算コストが高い。したがって、無効な候補を後の段階よりもこの段階で破棄する方が望ましい。
一方、フィルタリング条件が厳しすぎると、実際のマーカーの輪郭が破棄され、その結果検出されなくなる可能性がある。
これらの引数は、マーカーの最小サイズと最大サイズ、具体的にはマーカー周囲長の最小値と最大値を決定する。これらは絶対的なピクセル値ではなく、入力画像の最大の次元に対する相対値で指定される。
例えば、サイズ 640x480 の画像で相対的なマーカー周囲長の最小値が 0.05 の場合、640 が画像の最大の次元であるため、マーカー周囲長の最小値は 640x0.05 = 32 ピクセルになる。maxMarkerPerimeterRate 引数についても同様である。
minMarkerPerimeterRate が小さすぎると、後続の段階で検討される輪郭が大幅に増えるため、検出性能が著しく低下しうる。maxMarkerPerimeterRate 引数ではこのペナルティはそれほど顕著ではない。通常、大きな輪郭よりも小さな輪郭の方がはるかに多いためである。minMarkerPerimeterRate の値が 0 で maxMarkerPerimeterRate の値が 4(以上)であれば、画像内のすべての輪郭を検討することと等価になるが、性能上の理由からこれは推奨されない。
各候補に多角形近似が適用され、正方形に近い形状のものだけが受け入れられる。この値は、多角形近似が生み出しうる最大誤差を決定する(詳細は approxPolyDP() 関数を参照)。
この引数は候補の長さ(ピクセル単位)に対する相対値である。したがって、候補の周囲長が 100 ピクセルで polygonalApproxAccuracyRate の値が 0.04 の場合、最大誤差は 100x0.04=5.4 ピクセルになる。
ほとんどの場合、デフォルト値で十分に機能するが、歪みの大きい画像ではより大きな誤差値が必要になることがある。
同じマーカー内の任意のコーナーペア間の最小距離。マーカーの周囲長に対する相対値で表される。ピクセル単位の最小距離は Perimeter * minCornerDistanceRate である。
2つの異なるマーカーの任意のコーナーペア間の最小距離。2つのマーカーのうち小さい方のマーカー周囲長に対する相対値で表される。2つの候補が近すぎる場合、小さい方が無視される。
マーカーのいずれかのコーナーから画像の境界までの最小距離(ピクセル単位)。画像の境界で部分的に隠れているマーカーでも、隠れている部分が小さければ正しく検出できる。ただし、コーナーの1つが隠れている場合、返されるコーナーは通常、画像の境界付近の誤った位置に配置される。
マーカーのコーナー位置が重要な場合、例えば姿勢推定を行いたい場合は、コーナーが画像の境界に近すぎるマーカーを破棄する方がよい。それ以外の場合は必要ない。
候補の検出後、各候補のビットが解析され、マーカーであるかどうかが判定される。
バイナリコード自体を解析する前に、ビットを抽出する必要がある。そのために、透視歪みを補正し、得られた画像を大津のしきい値処理(Otsu threshold)でしきい値処理して黒と白のピクセルを分離する。
これは、マーカーの透視歪みを除去した後に得られる画像の例である。
次に、画像はマーカー内のビット数と同じ数のセルを持つグリッドに分割される。各セルで黒と白のピクセル数を数え、(多数を占める値から)そのセルに割り当てるビット値を決定する。
この処理をカスタマイズできる引数がいくつかある。
この引数はマーカー境界の幅を示す。各ビットのサイズに対する相対値である。したがって、値が 2 の場合は境界が内部ビット2つ分の幅を持つことを示す。
この引数は、使用しているマーカーの境界サイズと一致する必要がある。境界サイズは、generateImageMarker() などのマーカー描画関数で設定できる。
この値は、大津のしきい値処理を行うためのピクセル値の最小標準偏差を決定する。偏差が小さい場合、おそらく正方形全体が黒(または白)であることを意味し、大津の手法を適用しても意味がない。この場合、平均値が 128 より高いか低いかに応じて、すべてのビットが 0(または 1)に設定される。
この引数は、透視歪みを補正した後に得られる画像における(セルあたりの)ピクセル数を決定する(境界を含む)。これは上の画像の赤い正方形のサイズである。
例えば、5x5 ビットで境界サイズが 1 ビットのマーカーを扱っているとする(markerBorderBits を参照)。すると、1次元あたりのセル/ビットの総数は 5 + 2*1 = 7 になる(境界は両側に数える必要がある)。セルの総数は 7x7 である。
perspectiveRemovePixelPerCell の値が 10 の場合、得られる画像のサイズは 10*7 = 70 -> 70x70 ピクセルになる。
この引数の値を大きくすると、(ある程度まで)ビット抽出処理を改善できるが、性能を低下させる可能性がある。
各セルのビットを抽出する際、黒と白のピクセル数が数えられる。一般に、セルのすべてのピクセルを考慮することは推奨されない。代わりに、セルのマージン部分の一部のピクセルを無視する方がよい。
その理由は、透視歪みを除去した後、セルの色は一般に完全には分離されておらず、白いセルが黒いセルのピクセルの一部に入り込むことがある(逆も同様)ためである。したがって、誤ったピクセルを数えるのを避けるため、一部のピクセルを無視する方がよい。
例えば、次の画像では、
緑の正方形の内側のピクセルのみが考慮される。右の画像を見ると、結果として得られるピクセルには隣接セルからのノイズが少ないことがわかる。perspectiveRemoveIgnoredMarginPerCell 引数は、赤い正方形と緑の正方形の差を示す。
この引数はセルの総サイズに対する相対値である。例えば、セルサイズが 40 ピクセルでこの引数の値が 0.1 の場合、セル内で 40*0.1=4 ピクセルのマージンが無視される。つまり、各セルで実際に解析されるピクセルの総数は、40x40 ではなく 32x32 になる。
ビットの抽出後、次のステップは、抽出されたコードがマーカー辞書に属するかどうかを確認することであり、必要に応じて誤り訂正を行うことができる。
マーカー境界のビットは黒であるべきである。この引数は、境界内で許容される誤りビット数、すなわち境界内の白ビットの最大数を指定する。マーカー内のビットの総数に対する相対値で表される。
各マーカー辞書には、訂正可能なビット数の理論上の最大値がある(Dictionary.maxCorrectionBits)。ただし、この値は errorCorrectionRate 引数で変更できる。
例えば、(使用する辞書で)訂正可能なビット数が 6 で errorCorrectionRate の値が 0.5 の場合、実際に訂正可能なビット数の最大値は 6*0.5=3 ビットになる。
この値は、誤検出を避けるために誤り訂正能力を低下させるのに役立つ。
マーカーが検出・識別された後、最後のステップはコーナー位置のサブピクセル精緻化を行うことである(OpenCVの cornerSubPix() および cv::aruco::CornerRefineMethod を参照)。
このステップは省略可能で、例えば姿勢推定のためにマーカーのコーナー位置を正確にする必要がある場合にのみ意味を持つことに注意する。これは通常、時間のかかるステップであるため、デフォルトでは無効になっている。
この引数は、コーナーのサブピクセル処理を行うかどうか、また行う場合にどの手法を使用するかを決定する。正確なコーナーが不要な場合は無効にできる。指定可能な値は CORNER_REFINE_NONE、CORNER_REFINE_SUBPIX、CORNER_REFINE_CONTOUR、CORNER_REFINE_APRILTAG である。
この引数は、コーナー精緻化処理の最大ウィンドウサイズを決定する。
値が大きいと、画像の近くのコーナーがウィンドウ領域に含まれることがあり、その結果、処理中にマーカーのコーナーが別の誤った位置に移動してしまう。また、性能に影響する可能性もある。ArUcoマーカーが小さすぎる場合はウィンドウサイズが縮小されることがある。cv::aruco::DetectorParameters::relativeCornerRefinmentWinSize を確認すること。最終的なウィンドウサイズは min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize) として計算される。ここで averageArucoModuleSize はピクセル単位でのArUcoマーカーの平均モジュールサイズである。
Arucoモジュールサイズに対する、コーナー精緻化用の動的ウィンドウサイズ(デフォルト0.3)。
最終的なウィンドウサイズは min(cornerRefinementWinSize, averageArucoModuleSize*relativeCornerRefinmentWinSize) として計算される。ここで averageArucoModuleSize はピクセル単位でのArUcoマーカーの平均モジュールサイズである。互いに離れた位置にあるマーカーの場合、この引数の値を 0.4~0.5 に増やすと有用なことがある。互いに近い位置にあるマーカーの場合、引数の値を 0.1~0.2 に減らすと有用なことがある。
これら2つの引数は、サブピクセル精緻化処理の停止基準を決定する。cornerRefinementMaxIterations は最大反復回数を示し、cornerRefinementMinAccuracy は処理を停止する前の最小誤差値を示す。
反復回数が多すぎると、性能に影響する可能性がある。一方、少なすぎると、サブピクセル精緻化が不十分になることがある。