Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

AprilTag マーカーを使用したカメラ キャリブレーション

AprilTag は、オブジェクト検出や位置推定を行うアプリケーションの視覚的なマーカーとして、またカメラ キャリブレーションのターゲットとして広く使用されています [1]。AprilTag は QR コードに似ていますが、符号化するデータが少なくなるように設計されているため、より高速に復号化できます。これは、たとえば、リアルタイムのロボット工学アプリケーションに役立ちます。

この例では、関数readAprilTagを使用して、キャリブレーション パターンで AprilTag を検出して位置推定を行います。関数 readAprilTag では、すべての公式のタグ ファミリをサポートしています。この例では、追加の Computer Vision Toolbox™ 関数も使用し、エンドツーエンドのカメラ キャリブレーションを実行します。既定のチェッカーボード パターンは、等間隔の AprilTag のグリッドに置き換えられます。キャリブレーションにチェッカーボード パターンを使用する例については、単一カメラ キャリブレーター アプリを参照してください。

キャリブレーション パターンとして AprilTag を使用する利点には、よりロバストな特徴点の検出や、一貫性があり反復可能な検出を行えることがあります。この例は、一般的なチェッカーボード パターンの代わりに、円のフィールドなどの他のカスタム キャリブレーション パターンを使用するテンプレートとしても使用できます。

手順 1: キャリブレーション パターンの生成

タグ イメージのダウンロードと準備

サポートされているすべてのファミリ用に事前に生成されたタグは、Web ブラウザーを使用するか、次のコードを実行することで、こちらからダウンロードできます。

downloadURL  = 'https://github.com/AprilRobotics/apriltag-imgs/archive/master.zip';
dataFolder   = fullfile(tempdir, 'apriltag-imgs', filesep); 
options      = weboptions('Timeout', Inf);
zipFileName  = [dataFolder, 'apriltag-imgs-master.zip'];
folderExists = exist(dataFolder, 'dir');

% Create a folder in a temporary directory to save the downloaded file
if ~folderExists  
    mkdir(dataFolder); 
    disp('Downloading apriltag-imgs-master.zip (60.1 MB)...') 
    websave(zipFileName, downloadURL, options); 
    
    % Extract contents of the downloaded file
    disp('Extracting apriltag-imgs-master.zip...') 
    unzip(zipFileName, dataFolder); 
end
Downloading apriltag-imgs-master.zip (60.1 MB)...
Extracting apriltag-imgs-master.zip...

例の最後にある関数 helperGenerateAprilTagPattern を使用すると、タグの配置を指定するタグ イメージを使ってキャリブレーション ターゲットを生成できます。パターン イメージは calibPattern に含まれており、(MATLAB から) パターンを出力するために使用できます。例では、tag36h11 ファミリを使用しています。これは、検出能力と偽陽性検出のロバスト性の間の妥当なトレードオフを提供しています。

% Set the properties of the calibration pattern.
tagArrangement = [5,8];
tagFamily = 'tag36h11';

% Generate the calibration pattern using AprilTags.
tagImageFolder = [dataFolder 'apriltag-imgs-master/' tagFamily];
imdsTags = imageDatastore(tagImageFolder);
calibPattern = helperGenerateAprilTagPattern(imdsTags, tagArrangement, tagFamily);

このパターンで関数 readAprilTag を使用すると、個々のタグのコーナーの位置がグループとしてまとめられて検出が行われます。関数 helperAprilTagToCheckerLocations を使用して、この配置をチェッカーボードと同様に列優先の配置に変換できます。

% Read and localize the tags in the calibration pattern.
[tagIds, tagLocs] = readAprilTag(calibPattern, tagFamily);

% Sort the tags based on their ID values.
[~, sortIdx] = sort(tagIds);
tagLocs = tagLocs(:,:,sortIdx);

% Reshape the tag corner locations into an M-by-2 array.
tagLocs = reshape(permute(tagLocs,[1,3,2]), [], 2);

% Convert the AprilTag corner locations to checkerboard corner locations.
checkerIdx = helperAprilTagToCheckerLocations(tagArrangement);
imagePoints = tagLocs(checkerIdx(:),:);

% Display corner locations.
figure; imshow(calibPattern); hold on
plot(imagePoints(:,1), imagePoints(:,2), 'ro-', 'MarkerSize',15)

キャリブレーション用のイメージの準備

生成されたキャリブレーション パターンは平らな面に印刷しなければなりません。キャリブレーションが必要なカメラを使用してパターンのイメージを撮影します。キャリブレーション用のイメージを準備する際の注意点は次のとおりです。

  • この例では、パターンを紙に印刷しますが、平らで、湿気などによる変形の影響を受けていない面にパターンを印刷するようにします。

  • キャリブレーション手順ではパターンが平面であると想定しているため、パターンに何らかの欠陥 (たとえば、平らでない表面) があると、キャリブレーションの精度が低下する可能性があります。

  • キャリブレーション手順では、パターンのイメージが少なくとも 2 つ必要ですが、10 から 20 のイメージを使用すると、より正確な結果が得られます。

  • パターンがイメージの大部分を占めるようにパターンのさまざまなイメージを撮影し、視野全体がカバーされるようにします。たとえば、レンズ歪みを最大限に得るには、イメージ フレームのすべてのエッジにパターンのイメージを配置します。

  • パターンが部分的に表示されているイメージは除外されるため、撮影したイメージにパターン全体が表示されていることを確認してください。

  • キャリブレーション パターンのイメージの準備に関する詳細については、パターン、カメラおよびイメージの準備を参照してください。

手順 2: AprilTag の検出と位置推定

例の最後に含められている関数 helperDetectAprilTagCorners を使用し、撮影したイメージからタグを検出して位置推定を行い、キャリブレーション手順のキー ポイントとして使用するためにチェッカーボード形式で配置します。

% Create an imageDatastore object to store the captured images.
imdsCalib = imageDatastore("aprilTagCalibImages/");

% Detect the calibration pattern from the images.
[imagePoints, boardSize] = helperDetectAprilTagCorners(imdsCalib, tagArrangement, tagFamily);

手順 3: キャリブレーション パターンのワールド座標点の生成

生成された AprilTag パターンは、タグがチェッカーボード形式で配置されるようになっているため、関数generateCheckerboardPointsを使用して、上記 (imagePoints) で決定した対応するイメージ座標におけるワールド座標点を取得できます。

ここでは、正方形のサイズがタグのサイズに置き換えられ、ボードのサイズは前の手順で取得されます。タグの片側で外側の黒いエッジ間のタグ サイズを測定します。

% Generate world point coordinates for the pattern.
tagSize = 16; % in millimeters
worldPoints = generateCheckerboardPoints(boardSize, tagSize);

手順 4: カメラ パラメーターの推定

イメージとワールド座標点の対応関係を利用し、関数estimateCameraParametersを使用してカメラ パラメーターを推定します。

% Determine the size of the images.
I = readimage(imdsCalib, 1);
imageSize = [size(I,1), size(I,2)];

% Estimate the camera parameters.
params = estimateCameraParameters(imagePoints, worldPoints, 'ImageSize', imageSize);

キャリブレーションの精度と、撮影したイメージのキャリブレーション パターンの平面を示す外部カメラ パラメーターを可視化します。

% Display the reprojection errors.
figure
showReprojectionErrors(params)

% Display the extrinsics.
figure
showExtrinsics(params)

検出されたイメージ点と、推定されたカメラ パラメーターを使用して取得した、再投影された点の位置を検査します。

% Read a calibration image.
I = readimage(imdsCalib, 10);

% Insert markers for the detected and reprojected points.
I = insertMarker(I, imagePoints(:,:,10), 'o', 'Color', 'g', 'Size', 5);
I = insertMarker(I, params.ReprojectedPoints(:,:,10), 'x', 'Color', 'r', 'Size', 5);

% Display the image.
figure
imshow(I)

他のキャリブレーション パターンの使用

この例では、キャリブレーション パターンに AprilTag マーカーを使用していますが、同じワークフローを他の平面パターンにも同様に拡張できます。カメラ パラメーターを取得するために使用される estimateCameraParameters には、次のものが必要です。

  • imagePoints: 撮影したイメージから取得されたイメージ座標におけるキャリブレーション パターンのキー ポイント。

  • worldPoints: キャリブレーション パターンのキー ポイントに対応するワールド座標点。

これらのキー ポイントを取得する方法があれば、他のキャリブレーション ワークフローは同じままです。

サポート関数

helperGenerateAprilTagPattern は、AprilTag ベースのキャリブレーション パターンを生成します。

function calibPattern = helperGenerateAprilTagPattern(imdsTags, tagArragement, tagFamily)

numTags = tagArragement(1)*tagArragement(2);
tagIds = zeros(1,numTags);

% Read the first image.
I = readimage(imdsTags, 3);
Igray = im2gray(I);

% Scale up the thumbnail tag image.
Ires = imresize(Igray, 15, 'nearest');

% Detect the tag ID and location (in image coordinates).
[tagIds(1), tagLoc] = readAprilTag(Ires, tagFamily);

% Pad image with white boundaries (ensures the tags replace the black
% portions of the checkerboard).
tagSize = round(max(tagLoc(:,2)) - min(tagLoc(:,2)));
padSize = round(tagSize/2 - (size(Ires,2) - tagSize)/2);
Ires = padarray(Ires, [padSize,padSize], 255);

% Initialize tagImages array to hold the scaled tags.
tagImages = zeros(size(Ires,1), size(Ires,2), numTags);
tagImages(:,:,1) = Ires;

for idx = 2:numTags
   
    I = readimage(imdsTags, idx + 2);
    Igray = im2gray(I);
    Ires = imresize(Igray, 15, 'nearest');
    Ires = padarray(Ires, [padSize,padSize], 255);
    
    tagIds(idx) = readAprilTag(Ires, tagFamily);
    
    % Store the tag images.
    tagImages(:,:,idx) = Ires;
     
end

% Sort the tag images based on their IDs.
[~, sortIdx] = sort(tagIds);
tagImages = tagImages(:,:,sortIdx);

% Reshape the tag images to ensure that they appear in column-major order
% (montage function places image in row-major order).
columnMajIdx = reshape(1:numTags, tagArragement)';
tagImages = tagImages(:,:,columnMajIdx(:));

% Create the pattern using 'montage'.
imgData = montage(tagImages, 'Size', tagArragement);
calibPattern = imgData.CData;

end

helperDetectAprilTagCorners は、イメージ内の AprilTag キャリブレーション パターンを検出します。

function [imagePoints, boardSize, imagesUsed] = helperDetectAprilTagCorners(imdsCalib, tagArrangement, tagFamily)

% Get the pattern size from tagArrangement.
boardSize = tagArrangement*2 + 1;

% Initialize number of images and tags.
numImages = length(imdsCalib.Files);
numTags = tagArrangement(1)*tagArrangement(2);

% Initialize number of corners in AprilTag pattern.
imagePoints = zeros(numTags*4,2,numImages);
imagesUsed = zeros(1, numImages);

% Get checkerboard corner indices from AprilTag corners.
checkerIdx = helperAprilTagToCheckerLocations(tagArrangement);

for idx = 1:numImages

    % Read and detect AprilTags in image.
    I = readimage(imdsCalib, idx);
    [tagIds, tagLocs] = readAprilTag(I, tagFamily);

    % Accept images if all tags are detected.
    if numel(tagIds) == numTags
        % Sort detected tags using ID values.
        [~, sortIdx] = sort(tagIds);
        tagLocs = tagLocs(:,:,sortIdx);
        
        % Reshape tag corner locations into a M-by-2 array.
        tagLocs = reshape(permute(tagLocs,[1,3,2]), [], 2);
        
        % Populate imagePoints using checkerboard corner indices.
        imagePoints(:,:,idx) = tagLocs(checkerIdx(:),:);
        imagesUsed(idx) = true; 
    else
        imagePoints(:,:,idx) = [];
    end
    
end

end

helperAprilTagToCheckerLocations は、AprilTag のコーナーをチェッカーボードのコーナーに変換します。

function checkerIdx = helperAprilTagToCheckerLocations(tagArrangement)

numTagRows = tagArrangement(1);
numTagCols = tagArrangement(2);
numTags = numTagRows * numTagCols;

% Row index offsets.
rowIdxOffset = [0:numTagRows - 1; 0:numTagRows - 1];

% Row indices for first and second columns in board.
col1Idx = repmat([4 1]', numTagRows, 1);
col2Idx = repmat([3 2]', numTagRows, 1);
col1Idx = col1Idx + rowIdxOffset(:)*4;
col2Idx = col2Idx + rowIdxOffset(:)*4;

% Column index offsets
colIdxOffset = 0:4*numTagRows:numTags*4 - 1;

% Implicit expansion to get all indices in order.
checkerIdx = [col1Idx;col2Idx] + colIdxOffset;

end

リファレンス

[1] E. Olson, "AprilTag: A robust and flexible visual fiducial system," 2011 IEEE International Conference on Robotics and Automation, Shanghai, 2011, pp. 3400-3407, doi: 10.1109/ICRA.2011.5979561.