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

前のチュートリアル: ArUcoボードの検出
次のチュートリアル: ダイヤモンドマーカーの検出
ArUcoマーカーとボードは、高速な検出と汎用性の高さから非常に有用である。しかし、ArUcoマーカーの問題の一つは、サブピクセル精緻化を適用した後でも、コーナー位置の精度がそれほど高くないことである。

これとは対照的に、チェスボードパターンのコーナーは、各コーナーが2つの黒い正方形に囲まれているため、より高精度に精緻化できる。しかし、チェスボードパターンを見つけることは、ArUcoボードを見つけるほど汎用的ではない。すなわち、全体が完全に見えている必要があり、オクルージョン(遮蔽)は許されない。

ChArUcoボードは、これら2つのアプローチの利点を組み合わせようとするものである:

Charuco definition

ArUco部分はチェスボードのコーナー位置を補間するために使用される。これにより、オクルージョンや部分的な見え方を許容できるため、マーカーボードの汎用性を持つ。さらに、補間されたコーナーはチェスボードに属しているため、サブピクセル精度の点で非常に正確である。

カメラキャリブレーションのように高い精度が必要な場合、Charucoボードは標準的なArUcoボードよりも良い選択肢となる。

目標

このチュートリアルでは以下を学ぶ:

  • charucoボードを作成する方法は?
  • カメラキャリブレーションを行わずにcharucoコーナーを検出する方法は?
  • カメラキャリブレーションと姿勢推定を用いてcharucoコーナーを検出する方法は?

ソースコード

このコードは samples/cpp/tutorial_code/objectDetection/detect_board_charuco.cpp にある

目的の一覧に挙げたすべての処理を実現する方法のサンプルコードを次に示す。

int squaresX = parser.get<int>("w");
int squaresY = parser.get<int>("h");
float squareLength = parser.get<float>("sl");
float markerLength = parser.get<float>("ml");
bool refine = parser.has("rs");
int camId = parser.get<int>("ci");
string video;
if(parser.has("v")) {
video = parser.get<string>("v");
}
Mat camMatrix, distCoeffs;
readCameraParamsFromCommandLine(parser, camMatrix, distCoeffs);
aruco::DetectorParameters detectorParams = readDetectorParamsFromCommandLine(parser);
aruco::Dictionary dictionary = readDictionatyFromCommandLine(parser);
if(!parser.check()) {
parser.printErrors();
return 0;
}
VideoCapture inputVideo;
int waitTime = 0;
if(!video.empty()) {
inputVideo.open(video);
} else {
inputVideo.open(camId);
waitTime = 10;
}
float axisLength = 0.5f * ((float)min(squaresX, squaresY) * (squareLength));
// create charuco board object
aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary);
// create charuco detector
aruco::CharucoParameters charucoParams;
charucoParams.tryRefineMarkers = refine; // if tryRefineMarkers, refineDetectedMarkers() will be used in detectBoard()
charucoParams.cameraMatrix = camMatrix; // cameraMatrix can be used in detectBoard()
charucoParams.distCoeffs = distCoeffs; // distCoeffs can be used in detectBoard()
aruco::CharucoDetector charucoDetector(charucoBoard, charucoParams, detectorParams);
double totalTime = 0;
int totalIterations = 0;
while(inputVideo.grab()) {
Mat image, imageCopy;
inputVideo.retrieve(image);
double tick = (double)getTickCount();
vector<int> markerIds, charucoIds;
vector<vector<Point2f> > markerCorners;
vector<Point2f> charucoCorners;
Vec3d rvec, tvec;
// detect markers and charuco corners
charucoDetector.detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds);
// estimate charuco board pose
bool validPose = false;
if(camMatrix.total() != 0 && distCoeffs.total() != 0 && charucoIds.size() >= 4) {
Mat objPoints, imgPoints;
charucoBoard.matchImagePoints(charucoCorners, charucoIds, objPoints, imgPoints);
validPose = solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);
}
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(markerIds.size() > 0) {
aruco::drawDetectedMarkers(imageCopy, markerCorners);
}
if(charucoIds.size() > 0) {
aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));
}
if(validPose)
cv::drawFrameAxes(imageCopy, camMatrix, distCoeffs, rvec, tvec, axisLength);
imshow("out", imageCopy);
if(waitKey(waitTime) == 27) break;
}

ChArUcoボードの作成

arucoモジュールは、Charucoボードを表す cv::aruco::CharucoBoard クラスを提供する。これは cv::aruco::Board クラスを継承している。

このクラスは、ChArUcoの他の機能と同様に、以下で定義されている:

cv::aruco::CharucoBoard を定義するには、以下が必要である:

  • X方向およびY方向のチェスボードの正方形の数。
  • 正方形の一辺の長さ。
  • マーカーの一辺の長さ。
  • マーカーの辞書。
  • 全マーカーのID。

cv::aruco::GridBoard オブジェクトの場合と同様に、arucoモジュールは cv::aruco::CharucoBoard を簡単に作成する手段を提供する。このオブジェクトは、cv::aruco::CharucoBoard コンストラクタを用いて、これらのパラメータから簡単に作成できる:

aruco::Dictionary dictionary = readDictionatyFromCommandLine(parser);
cv::aruco::CharucoBoard board(Size(squaresX, squaresY), (float)squareLength, (float)markerLength, dictionary);
  • 第1パラメータは、それぞれX方向およびY方向の正方形の数である。
  • 第2および第3パラメータは、それぞれ正方形とマーカーの長さである。任意の単位で指定できるが、このボードに対して推定される姿勢は同じ単位で計測される点に留意すること(通常はメートルが用いられる)。
  • 最後に、マーカーの辞書を指定する。

各マーカーのIDは、cv::aruco::GridBoard コンストラクタと同様に、デフォルトでは0から始まる昇順で割り当てられる。これは、cv::aruco::Board 親クラスと同様に、board.ids を通じてidsベクトルにアクセスすることで簡単にカスタマイズできる。

cv::aruco::CharucoBoard オブジェクトが得られたら、それを印刷するための画像を作成できる。これには2つの方法がある:

  1. スクリプト apps/pattern-tools/generate_pattern.py を使用する。キャリブレーションパターンの作成を参照。
  2. 関数 cv::aruco::CharucoBoard::generateImage() を使用する。

関数 cv::aruco::CharucoBoard::generateImage()cv::aruco::CharucoBoard クラスで提供されており、以下のコードを用いて呼び出すことができる:

Mat boardImage;
Size imageSize;
imageSize.width = squaresX * squareLength + 2 * margins;
imageSize.height = squaresY * squareLength + 2 * margins;
board.generateImage(imageSize, boardImage, margins, borderBits);
  • 第1パラメータは、出力画像のサイズ(ピクセル単位)である。これがボードの寸法に比例していない場合、画像の中央に配置される。
  • 第2パラメータは、charucoボードを描画した出力画像である。
  • 第3パラメータは、(省略可能な)マージン(ピクセル単位)であり、どのマーカーも画像の境界に接しないようにする。
  • 最後に、マーカー境界のサイズで、cv::aruco::generateImageMarker() 関数と同様である。デフォルト値は1である。

出力画像は次のようになる:

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

サンプル create_board_charuco.cpp は、現在 cv::CommandLineParser を介してコマンドラインから入力を受け取る。このファイルの場合、例となるパラメータは次のようになる:

"_output_path_/chboard.png" -w=5 -h=7 -sl=100 -ml=60 -d=10

ChArUcoボードの検出

ChArUcoボードを検出するとき、実際に検出しているのは、ボードの各チェスボードコーナーである。

ChArUcoボード上の各コーナーには、一意の識別子(id)が割り当てられている。これらのidは0からボード内のコーナーの総数までである。charucoボード検出の手順は、以下のステップに分解できる:

  • 入力画像の取得
Mat image, imageCopy;
inputVideo.retrieve(image);

マーカーを検出する対象となる元画像。この画像は、ChArUcoコーナーでのサブピクセル精緻化を行うために必要である。

  • カメラキャリブレーションパラメータの読み込み(キャリブレーション付き検出の場合のみ)
if(parser.has("c")) {
bool readOk = readCameraParameters(parser.get<std::string>("c"), camMatrix, distCoeffs);
if(!readOk) {
throw std::runtime_error("Invalid camera file\n");
}
}

readCameraParameters のパラメータは次のとおりである:

  • 第1パラメータは、カメラ内部行列と歪み係数へのパスである。
  • 第2および第3パラメータは、cameraMatrix と distCoeffs である。

この関数はこれらのパラメータを入力として受け取り、カメラキャリブレーションパラメータが有効かどうかを表すブール値を返す。キャリブレーションなしでcharucoコーナーを検出する場合、このステップは不要である。

  • マーカーの検出とマーカーからのcharucoコーナーの補間

ChArUcoコーナーの検出は、先に検出されたマーカーに基づく。したがって、まずマーカーが検出され、次にそのマーカーからChArUcoコーナーが補間される。ChArUcoコーナーを検出するメソッドは cv::aruco::CharucoDetector::detectBoard() である。

// detect markers and charuco corners
charucoDetector.detectBoard(image, charucoCorners, charucoIds, markerCorners, markerIds);

detectBoard のパラメータは次のとおりである:

  • image - 入力画像。
  • charucoCorners - 検出されたコーナーの画像位置の出力リスト。
  • charucoIds - charucoCorners 内の検出された各コーナーの出力id。
  • markerCorners - 検出されたマーカーコーナーの入出力ベクトル。
  • markerIds - 検出されたマーカーの識別子の入出力ベクトル

markerCorners と markerIds が空の場合、この関数はarucoマーカーとidを検出する。

キャリブレーションパラメータが与えられている場合、ChArUcoコーナーは、まずArUcoマーカーから大まかな姿勢を推定し、次にChArUcoコーナーを画像へ再投影することで補間される。

一方、キャリブレーションパラメータが与えられていない場合、ChArUcoコーナーは、ChArUco平面とChArUco画像投影との間の対応するホモグラフィを計算することで補間される。

ホモグラフィを用いる場合の主な問題は、補間が画像の歪みに対してより敏感になることである。実際には、歪みの影響を抑えるため、ホモグラフィは各ChArUcoコーナーに最も近いマーカーのみを使って計算される。

ChArUcoボードのマーカーを検出する際、特にホモグラフィを使う場合は、マーカーのコーナー精緻化を無効にすることが推奨される。その理由は、チェスボードのマス目が近接しているため、サブピクセル処理がコーナー位置に大きな偏差を生じさせ、それがChArUcoコーナーの補間に伝播して結果が悪化するためである。

覚え書き
偏差を避けるため、チェスボードのマス目とArUcoマーカーの間のマージンは、1マーカーモジュールの70%より大きくすべきである。

さらに、周囲の2つのマーカーが両方とも見つかったコーナーのみが返される。周囲の2つのマーカーのいずれかが検出されなかった場合、通常はその領域でオクルージョンが生じているか画像品質が良くないことを意味する。いずれにせよ、補間されたChArUcoコーナーが非常に正確であることを確実にしたいので、そのコーナーは考慮しないほうが望ましい。

ChArUcoコーナーが補間された後、サブピクセル精緻化が実行される。

ChArUcoコーナーを補間したら、おそらくそれらを描画して検出が正しいかどうかを確認したくなるだろう。これは cv::aruco::drawDetectedCornersCharuco() 関数を使えば簡単に行える。

aruco::drawDetectedCornersCharuco(imageCopy, charucoCorners, charucoIds, cv::Scalar(255, 0, 0));
  • imageCopy はコーナーが描画される画像である(通常はコーナーが検出されたのと同じ画像になる)。
  • outputImage は、コーナーが描画された inputImage のクローンになる。
  • charucoCornerscharucoIds は、cv::aruco::CharucoDetector::detectBoard() 関数で検出されたCharucoコーナーである。
  • 最後の引数は、コーナーを描画する際の(省略可能な)色であり、型は cv::Scalar である。

この画像に対して:

Image with Charuco board

結果は次のようになる:

Charuco board detected

次の画像のようにオクルージョンが存在する場合、一部のコーナーは明らかに見えていても、オクルージョンのため周囲のマーカーがすべて検出されているわけではなく、そのため補間されない:

Charuco detection with occlusion

サンプル動画:

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

サンプル detect_board_charuco.cpp は、cv::CommandLineParser を介してコマンドラインから入力を受け取るようになった。このファイルの場合、例として与える引数は次のようになる:

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10 -v=/path_to_opencv/opencv/doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg

ChArUco 姿勢推定

ChArUcoボードの最終的な目的は、高精度のキャリブレーションや姿勢推定のためにコーナーを非常に正確に見つけることである。

arucoモジュールには、ChArUcoの姿勢推定を簡単に行うための関数が用意されている。cv::aruco::GridBoard の場合と同様に、cv::aruco::CharucoBoard の座標系はボード平面上に置かれ、Z軸は内向きで、ボードの左下隅を原点とする。

覚え書き
OpenCV 4.6.0以降、ボードの座標系に互換性のない変更があり、現在は座標系がボード平面上に置かれ、Z軸は平面に対して内向きになっている(以前は軸が平面の外向きだった)。CW順の objPoints はZ軸が平面に対して内向きの場合に対応する。CCW順の objPoints はZ軸が平面に対して外向きの場合に対応する。PR https://github.com/opencv/opencv_contrib/pull/3174 を参照。

charucoボードの姿勢推定を行うには、cv::aruco::CharucoBoard::matchImagePoints()cv::solvePnP() を使用する:

// estimate charuco board pose
bool validPose = false;
if(camMatrix.total() != 0 && distCoeffs.total() != 0 && charucoIds.size() >= 4) {
Mat objPoints, imgPoints;
charucoBoard.matchImagePoints(charucoCorners, charucoIds, objPoints, imgPoints);
validPose = solvePnP(objPoints, imgPoints, camMatrix, distCoeffs, rvec, tvec);
}
  • charucoCornerscharucoIds の引数は、cv::aruco::CharucoDetector::detectBoard() 関数で検出されたcharucoコーナーである。
  • cameraMatrixdistCoeffs は、姿勢推定に必要なカメラキャリブレーションパラメータである。
  • 最後に、rvectvec の引数は、Charucoボードの出力姿勢である。
  • cv::solvePnP() は、姿勢が正しく推定された場合に true を、そうでない場合に false を返す。失敗の主な原因は、姿勢推定に十分なコーナーがないか、それらが同一直線上にあることである。

姿勢が正しく推定されたことを確認するため、cv::drawFrameAxes() を使って軸を描画できる。結果は次のようになる: (X:赤, Y:緑, Z:青)

Charuco Board Axis

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

サンプル detect_board_charuco.cpp は、cv::CommandLineParser を介してコマンドラインから入力を受け取るようになった。このファイルの場合、例として与える引数は次のようになる:

-w=5 -h=7 -sl=0.04 -ml=0.02 -d=10
-v=/path_to_opencv/opencv/doc/tutorials/objdetect/charuco_detection/images/choriginal.jpg
-c=/path_to_opencv/opencv/samples/cpp/tutorial_code/objectDetection/tutorial_camera_charuco.yml