Main Content

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

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

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

キャリブレーション パターンとして AprilTag を使用することには、特徴点の検出率が向上する、一貫した繰り返し可能な検出を行えるなどの利点があります。この例では、関数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  = fullfile(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 = fullfile(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) で決定した対応するイメージ座標におけるワールド座標点を取得できます。

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

AprilTagSize.png

% 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: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",MarkerColor="g",Size=5);
I = insertMarker(I,params.ReprojectedPoints(:,:,10),"x",MarkerColor="r",Size=5);

% Display the image.
figure
imshow(I)

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

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

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

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

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

カメラ キャリブレーター アプリへの AprilTag キャリブレーション パターン サポートの統合

利便性を向上させるために、上記のワークフローをカメラ キャリブレーター アプリに統合することもできます。全体的なワークフローは変わらず、手順は次のとおりです。

1.AprilTag を使用してイメージを追加します。

2.AprilTag 用のカスタム パターン検出器クラスをインポートします。検出器は次を行わなければなりません。

  • AprilTag の検出と位置推定

  • キャリブレーション パターンのワールド ポイントの生成

3.カメラ パラメーターを推定します。

AprilTag を使用したイメージの追加

カメラ キャリブレーター アプリを開きます。

  • MATLAB ツールストリップ: [アプリ] タブの [イメージ処理とコンピューター ビジョン] セクションで、[カメラ キャリブレーター] アイコンをクリックします。

  • MATLAB コマンド プロンプト: 単一カメラ キャリブレーター アプリの使用と入力します。

[キャリブレーション] タブの [ファイル] セクションで、[イメージの追加] をクリックして [From file] を選択します。複数のフォルダーからイメージを追加するには、各フォルダーで [イメージの追加] をクリックします。上記と同じイメージを再利用します。カメラのキャリブレーションには、少なくとも 2 つのイメージが必要です。イメージを追加すると、次の UI が表示されます。

UI1.png

[カスタム パターン] パネルを展開し、その他のオプションを表示します。

UI2.png

カスタム パターン検出器クラスのインポート

上記の UI は、パターン選択のドロップダウン リストを示しています。既定では、アプリに AprilTag 用のパターン検出器は含まれていません。カスタム パターン検出器クラスを作成し、それをリストに追加してアプリで使用できます。カスタム パターンの作成方法の詳細については、情報アイコン () をクリックしてください。AprilTag のカスタム パターン検出器クラスは、MyCustomAprilTagPatternDetector.m ファイルで提供されています。このクラスには、検出器に必要なパラメーターの UI コードと、カスタム AprilTag キャリブレーション パターンを検出および処理するための関数が含まれています。

この例では、関数 configureUIComponents() を使用して UI コンポーネントを構成し、initializePropertyValues() を使用してそれを初期化します。例の最後に含まれている関数 helperDrawImageAxesLabels は、カメラ キャリブレーター アプリのダイアログに表示されるキャリブレーション イメージの原点、X 軸、および Y 軸のラベルをレンダリングするために使用されます。

主なキャリブレーション関数は次のとおりです。

  • detectPatternPoints() - 撮影したイメージから AprilTag を検出して位置を推定し、キャリブレーション手順のキー ポイントとして使用するために並べ替えます。この関数は、例の最後に掲載されている関数 helperDetectAprilTagCorners を使用して実装されます。

  • generateWorldPoints() - AprilTag パターンの対応するイメージ座標のワールド座標を計算します。この関数は、例の最後に掲載されている関数 helperGenerateAprilTagPattern を使用して実装されます。

[カスタム パターン] パネルの下の [パターン検出器のインポート] ボタンをクリックして、カスタム パターン検出器クラスをインポートします。クラス ファイル "MyCustomAprilTagPatternDetector.m" を選択します。クラスにエラーがなかった場合、次のビューが表示されます。

1.PNG

この例では、[プロパティ] パネルのすべてのフィールドに正しい値があります。ただし、これらの値は必要に応じてカスタマイズできます。[Square Size] はタグの幅をワールド単位で表しており、イメージ内の各タグの間隔に等しいことが想定されていることにも注意してください。

[OK] をクリックすると、以下のように [データ ブラウザー] ペインに ID 付きのイメージのリストが表示されます。

これらのイメージには、検出されたパターンが含まれます。イメージを表示するには、[データ ブラウザー] ペインからイメージを選択します。

2.PNG

カメラ パラメーターの推定

ここでのカメラのキャリブレーション プロセスは、単一カメラ キャリブレーター アプリの使用に掲載されているものと同じです。

既定のキャリブレーション設定で、[キャリブレーション] タブの [キャリブレーション] ボタンをクリックします。[再投影誤差] ペインを検査してキャリブレーションの精度を可視化し、次に [カメラ中心] ペインで、カメラに対して配置されたパターンを示す外部カメラ パラメーターの推定値を可視化します。

Calibrated images.PNG

サポート関数とサポート クラス

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

helperDrawImageAxesLabels は、キャリブレーター アプリに表示されるキャリブレーション イメージの原点、X 軸、および Y 軸のラベルをレンダリングします。

function [originLabel,xLabel,yLabel] = helperDrawImageAxesLabels(boardSize,imagePoints)

    numBoardRows = boardSize(1)-1;
    numBoardCols = boardSize(2)-1;
    
    % Reshape checkerboard corners to boardSize shaped array
    boardCoordsX = reshape(imagePoints(:,1), [numBoardRows,numBoardCols]);
    boardCoordsY = reshape(imagePoints(:,2), [numBoardRows,numBoardCols]);
    boardCoords = cat(3, boardCoordsX,boardCoordsY);
    
    % Origin label (check if origin location is inside the image)
    if ~isnan(boardCoordsX(1,1))
        p1 = boardCoords(1,1,:);
        
        refPointIdx = find(~isnan(boardCoordsX(:,1)),2);
        p2 = boardCoords(refPointIdx(2),1,:);
        
        refPointIdx = find(~isnan(boardCoordsX(1,:)),2);
        p3 = boardCoords(1,refPointIdx(2),:);
        
        [loc, theta] = getAxesLabelPosition(p1,p2,p3);
        
        originLabel.Location = loc;
        originLabel.Orientation = theta;
    else
        originLabel = struct;
    end
    
    % X-axis label
    firstRowIdx = numBoardCols:-1:1;
    refPointIdx13 = find(~isnan(boardCoordsX(1,firstRowIdx)),2);
    refPointIdx13 = firstRowIdx(refPointIdx13);
    
    p1 = boardCoords(1,refPointIdx13(1),:);
    p3 = boardCoords(1,refPointIdx13(2),:);
    
    refPointIdx2 = find(~isnan(boardCoordsX(:,refPointIdx13(1))),2);
    p2 = boardCoords(refPointIdx2(2),refPointIdx13(1),:);
    
    [loc, theta] = getAxesLabelPosition(p1,p2,p3);
    theta = 180 + theta;
    
    xLabel.Location    = loc;
    xLabel.Orientation = theta;
    
    % Y-axis label
    firstColIdx = numBoardRows:-1:1;
    refPointIdx12 = find(~isnan(boardCoordsX(firstColIdx,1)),2);
    refPointIdx12 = firstColIdx(refPointIdx12);
    
    p1 = boardCoords(refPointIdx12(1),1,:);
    p2 = boardCoords(refPointIdx12(2),1,:);
    
    refPointIdx3 = find(~isnan(boardCoordsX(refPointIdx12(1),:)), 2);
    p3 = boardCoords(refPointIdx12(1),refPointIdx3(2),:);
    
    [loc,theta] = getAxesLabelPosition(p1,p2,p3);
    
    yLabel.Location = loc;
    yLabel.Orientation = theta;
    
    %--------------------------------------------------------------
    % p1+v
    %  \
    %   \     v1
    %    p1 ------ p2
    %    |
    % v2 |
    %    |
    %    p3
    function [loc,theta] = getAxesLabelPosition(p1,p2,p3)
        v1 = p3 - p1;
        theta = -atan2d(v1(2),v1(1));
        
        v2 = p2 - p1;
        v = -v1 - v2;
        d = hypot(v(1),v(2));
        minDist = 40;
        if d < minDist
            v = (v / d) * minDist;
        end
        loc = p1 + v;
    end
    %--------------------------------------------------------------
    
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.