ARToolKitみたいなもんだと思ってもらって大丈夫。
個人的にはプログラムがすっきりしているのでこっちのほうが好き。

Aruco付きOpenCVのインストールについては
opencvとarucoのインストール

作ったプログラム(サンプル撮影画像もあります)
https://bitbucket.org/asama-yaya/opencv_arucomarke...

サンプルページ(バージョンによって細かいところが違うので注意)

プログラムについて

追加のヘッダーファイル
#include <opencv2/aruco.hpp>		// aruco marker
#include <opencv2/aruco/charuco.hpp>    // for aruco chess board
MyAruco.h内
namespace{
  const int ARUCO_SingleMarker = 0;
  const int ARUCO_Board = 1;
  const int ARUCO_ChessBoard = 2;
  const int ARUCO_Diamond = 3;
}

class TMyAruco{
  // marker type
  int markerType = ARUCO_SingleMarker;

  // single marker
  cv::Ptr<cv::aruco::Dictionary> markerDictionary;
  double markerSingleSize = 0.132; // marker size [m]
  vector<cv::Vec3d> rvecsSingle, tvecsSingle;

  // board
  cv::Ptr<cv::aruco::GridBoard> markerBoard;
  cv::Point2d markerBoardSize = cv::Point2d(0.025, 0.007);
  cv::Vec3d rvecBoard, tvecBoard;

  // chess
  cv::Ptr<cv::aruco::CharucoBoard> markerChess;
  cv::Point2d markerChessSize = cv::Point2d(0.031, 0.0155);
  vector<cv::Point2f> cornersChess;
  vector<int> idsChess;

  // diamond
  cv::Point2d markerDiamondSize = cv::Point2d(0.053, 0.0315);	
  vector<cv::Vec3d> rvecsDiamond, tvecsDiamond;
};
最初にあげたもの4つをmarkerTypeを変更することでそれぞれ認識できるようになっている。
  • Single
markerSingleSizeはマーカの一辺の長さ
マーカとカメラ間の位置関係を求める
  • Board
markerBoardSizeはマーカの一辺の長さとマーカ間の長さ
ボードとカメラ間の位置関係を求める
単一マーカより精度は良い
  • Chess
markerChessSizeは四角形の一辺の長さとマーカの一辺の長さ
チェッカの交点を求める
カメラキャリブレーションにも使える
上のボードとしても使える
  • Diamond
markerDiamondSizeは上に同じ
ダイアモンドとカメラ間の位置関係を求める
ダイアモンドについては参考ページの画像見ればわかると思う

全部単位は[m]です。
マーカ作成
cv::Mat markerImage;

// single marker
if (markerType == ARUCO_SingleMarker){
  cv::Ptr<cv::aruco::Dictionary> dictionary =
    cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);

  cv::aruco::drawMarker(dictionary, 23, 200, markerImage, 1);
  cv::imwrite("data\\marker.png", markerImage);
}
// board
else if (markerType == ARUCO_Board){
  cv::Ptr<cv::aruco::Dictionary> dictionary =
    cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);

  cv::Ptr<cv::aruco::GridBoard> board =
    cv::aruco::GridBoard::create(5, 7, 0.04, 0.01, dictionary);
  board->draw(cv::Size(600, 500), markerImage, 10, 1);
  cv::imwrite("data\\board.png", markerImage);
}		
// chessboard
else if (markerType == ARUCO_ChessBoard){
  cv::Ptr<cv::aruco::Dictionary> dictionary =
    cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);

  cv::Ptr<cv::aruco::CharucoBoard> board =
    cv::aruco::CharucoBoard::create(5, 7, 0.04, 0.02, dictionary);
  board->draw(cv::Size(600, 500), markerImage, 10, 1);
  cv::imwrite("data\\chess.png", markerImage);
}
// diamond
else if (markerType == ARUCO_Diamond){
  cv::Ptr<cv::aruco::Dictionary> dictionary =
    cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);

  cv::aruco::drawCharucoDiamond(dictionary, cv::Vec4i(45, 68, 28, 74), 200, 120, markerImage);
  cv::imwrite("data\\diamond.png", markerImage);
  cv::aruco::drawCharucoDiamond(dictionary, cv::Vec4i(15, 28, 26, 34), 200, 120, markerImage);
  cv::imwrite("data\\diamond(2).png", markerImage);
}
cv::aruco::Dictionary(以下、辞典)はマーカの種類を指定している。
DICT_6X6_250は6x6の格子でマーカを作って、IDは合計250個ということ。
おまじないだと思ってもらってかまわない。
  • Single
特にコメントなし
cv::aruco::drawMarker(辞典, マーカID, marker_size[pix], OutputImage, ややこしいので1);
  • Board
作った辞典を元に複数のマーカを並べる。
cv::aruco::GridBoard::create(X軸のマーカ数, Y軸のマーカ数, マーカの長さ[m], マーカ間の長さ[m], 辞典);
board->draw(画像サイズ, OutputImage, マーカ間の最小の長さ[pix], ややこしいので1);
  • ChessBoard
上とほとんど同じ
マーカの長さ→四角形の長さ
マーカ間の長さ→マーカの長さ
に置き換えてください
  • Diamond
cv::aruco::drawCharucoDiamond(辞典, cv::Vec4i(ID1, ID2, ID3, ID4), 四角形の長さ, マーカの長さ, OutputImage);
認識前の準備
if (markerType == ARUCO_SingleMarker){
  markerDictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
}
else if (markerType == ARUCO_Board){
  markerDictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
  markerBoard =cv::aruco::GridBoard::create(5, 7,
    markerBoardSize.x, markerBoardSize.y, markerDictionary);
}
else if (markerType == ARUCO_ChessBoard){
  markerDictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
  markerChess = cv::aruco::CharucoBoard::create(5, 7,
    markerChessSize.x, markerChessSize.y, markerDictionary);
}
else if (markerType == ARUCO_Diamond){
  markerDictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
}

ほぼマーカ作成のときにやったことと同じ。
使用する辞典が何かとか、マーカの大きさとかは事前に登録してる。
認識
undistImage: 歪補正後の画像
intrinsic: カメラの内部パラメータ
distortion: カメラの歪パラメータ(今回は歪補正後なので値は全部0)

detect markerのところで、まずマーカを認識する。
その後、それぞれのタイプに応じて推定を行う。
// detect marker
vector<int> ids;
vector<vector<cv::Point2f>> corners;
cv::aruco::detectMarkers(undistImage, markerDictionary, corners, ids);

// if at least one marker is detected
if (ids.size() > 0){
  // single marker
  if (markerType == ARUCO_SingleMarker){
    // draw id
    cv::aruco::drawDetectedMarkers(undistImage, corners, ids);

    // pose estimation
    rvecsSingle.clear(); tvecsSingle.clear();
    cv::aruco::estimatePoseSingleMarkers(corners, markerSingleSize,
      intrinsic, distortion, rvecsSingle, tvecsSinge);

    // draw axis
    for (int ii = 0; ii < ids.size(); ii++)
      cv::aruco::drawAxis(undistImage, intrinsic, distortion,
        rvecsSingle[ii], tvecsSingle[ii], 0.1);	
  }
  // board
  else if (markerType == ARUCO_Board){
    cv::aruco::drawDetectedMarkers(undistImage, corners, ids);
    int valid = cv::aruco::estimatePoseBoard(corners, ids, 
      markerBoard, intrinsic, distortion, rvecBoard, tvecBoad);

    // if at least one board marker detected
    if (valid > 0)
      cv::aruco::drawAxis(undistImage, intrinsic, distortion,
        rvecBoard, tvecBoard, 0.1);
  }
  // chessboard
  else if (markerType == ARUCO_ChessBoard){
    cornersChess.clear();
    idsChess.clear();
    cv::aruco::interpolateCornersCharuco(corners, ids, undistImage,
      markerChess, cornersChess, idsChess);
    cv::aruco::drawDetectedMarkers(undistImage, corners, ids);

    // if at least one charuco corner detected
    if (idsChess.size() > 0)
      cv::aruco::drawDetectedCornersCharuco(undistImage, 
        cornersChess, idsChess, cv::Scalar(0, 0, 255));
  }
  // diamond
  else if (markerType == ARUCO_Diamond){
    // detect diamon diamonds
    vector<cv::Vec4i> idsDiamond;
    vector<vector<cv::Point2f>> cornersDiamond;
    cv::aruco::detectCharucoDiamond(undistImage, corners, ids,
      (markerDiamondSize.x / markerDiamondSize.y), cornersDiamond, idsDiamond);
    cv::aruco::drawDetectedMarkers(undistImage, corners, ids);

    // estimate poses	
    rvecsDiamond.clear();
    tvecsDiamond.clear();
    cv::aruco::estimatePoseSingleMarkers(cornersDiamond, markerDiamondSize.x, 
      intrinsic, distortion, rvecsDiamond, tvecsDiamond);

    // draw axis
    for (unsigned int i = 0; i<rvecsDiamond.size(); i++)
      cv::aruco::drawAxis(undistImage, intrinsic, distortion,
        rvecsDiamond[i], tvecsDiamond[i], 0.1);
  }
}
  • Single
cv::aruco::estimatePoseSingleMarkers
マーカの端っこの点の情報からマーカの位置推定を行う。

rvecs, tvecsにそれぞれのマーカとの位置関係が入っている。
rが回転成分、tが直進成分
回転成分に関してはrodriguesなので注意
http://opencv.jp/opencv-2svn/cpp/camera_calibratio...
  • Board
cv::aruco::estimatePoseBoard
見つけたマーカ全部の情報を使ってボードの位置推定を行う
マーカがある程度隠れていてもOKで精度も良い。
  • ChessBoard
cv::aruco::interpolateCornersCharuco
チェッカーの交点を求める。
交点にもIDが割り振られているみたいでidsChessに入ってる。

普通にチェッカー求めるより精度がいいのかな?
スピードはかなりはやくなってると思う。
  • Diamond
cv::aruco::detectCharucoDiamond
cv::aruco::estimatePoseSingleMarkers

まずDiamondを見つけて、それを一つのマーカとして位置推定を行うといった流れ。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

管理人/副管理人のみ編集できます