Main Content

Esta página se ha traducido mediante traducción automática. Haga clic aquí para ver la última versión en inglés.

SLAM de punto de referencia usando marcadores AprilTag

Este ejemplo muestra cómo combinar datos de odometría del robot y marcadores fiduciales observados llamados AprilTags para estimar mejor la trayectoria del robot y las posiciones de los puntos de referencia en el entorno. El ejemplo utiliza un enfoque de gráfico de pose y un enfoque de gráfico de factores, y compara los dos gráficos.

Descargar conjunto de datos y cargar datos del sensor

Descargue un archivo .zip que contiene datos sin procesar registrados desde un rosbag en un ClearPath Robotics™ Jackal™. Los datos sin procesar incluyen:

  • Imágenes tomadas con una cámara de red Axis™ M1013 con una resolución de 640 x 480.

  • Datos de odometría preprocesados ​​y sincronizados con los datos de la imagen.

Descomprima el archivo, que contiene el conjunto de datos como un archivo rosbag y como un archivo .mat . Puede utilizar la función auxiliar exampleHelperExtractDataFromRosbag , que se proporciona al final de este ejemplo, para ver cómo se extrajeron los datos del rosbag y se preprocesaron. Para utilizar la función auxiliar, debe tener una licencia de ROS Toolbox .

filename = matlab.internal.examples.downloadSupportFile("spc/robotics","apriltag_odometry_slam_dataset.zip");
unzip(filename)

En este ejemplo, se imprimió un conjunto de marcadores AprilTag y se colocó aleatoriamente en el entorno de prueba. El gráfico de pose y los gráficos de factor tratan las etiquetas como puntos de referencia, que son características distinguibles del entorno que le permiten localizar con mayor precisión. Al utilizar los elementos intrínsecos de la cámara y el tamaño de cada marcador, puede convertir las imágenes con observaciones de AprilTag en mediciones de puntos de referencia utilizando la función readAprilTag (Computer Vision Toolbox) . Estas medidas de referencia son relativas a la estructura del robot actual. Este ejemplo incluye los parámetros intrínsecos de la cámara en el archivo cameraParams.mat , pero para determinar los parámetros intrínsecos de su propia cámara, siga los pasos del ejemplo Camera Calibration Using AprilTag Markers (Computer Vision Toolbox) , o use un patrón de tablero de ajedrez con la app Camera Calibrator (Computer Vision Toolbox) . Los marcadores AprilTag utilizados en este ejemplo son de la familia "tag46h11", sin ID duplicados. El tamaño de la etiqueta impresa es de 136,65 mm.

tagFamily = "tag36h11";
tagSize = 136.65; % mm

Cargue los datos del sensor y los parámetros de la cámara en el espacio de trabajo de MATLAB® .

load apriltag_odometry_slam_dataset/apriltag_odometry_slam_dataset.mat
load cameraParams.mat

Cree una estructura de datos dictionary vacía para mantener la asignación entre los ID de etiqueta y sus ID de nodo.

tagToNodeIDDic = dictionary([],[]);

Los sensores registraron los datos de odometría en el archivo .mat desde el marco de odometría hasta el marco láser que se mueve con el robot. Aplique una transformación fija entre el marco del láser y el marco de la cámara.

R1 = [0 0 1; -1 0 0; 0 -1 0];
Ta = blkdiag(R1,1); % The camera frame z-axis points forward and y-axis points down
Tb = eye(4);
T2(1,3) = -0.11;
T(3,3) = 0.146; % Fixed translation of camera frame origin to 'laser' frame

Construya un gráfico de pose a partir de datos del sensor

Después de importar los datos del sensor sincronizado y los parámetros de la cámara, cree un gráfico de pose tridimensional de estimaciones de nodos a partir de las mediciones en los datos del sensor. El gráfico de pose contiene estimaciones de nodos, restricciones de bordes y cierres de bucles que estiman las poses del robot.

Primero crea el gráfico de pose.

pg = poseGraph3D;

Al utilizar marcadores fiduciales como AprilTags, el patrón de bloques en la imagen proporciona identificaciones únicas para cada observación de punto de referencia. Esta información de identificación reduce la necesidad de algoritmos de asociación de datos difíciles al realizar localización y mapeo simultáneos (SLAM).

Cree una variable para almacenar la pose anterior y el ID del nodo.

lastTform = [];
lastPoseNodeId = 1;

Cree variables para almacenar todos los ID de los nodos de punto de referencia.

lmkIDs = [];

Este ejemplo estima la trayectoria del robot basándose en las mediciones de pose de los datos del odómetro y las mediciones de puntos de referencia de las observaciones de AprilTag. Repita los datos del sensor y siga estos pasos:

  1. Agregue datos de odometría al gráfico de pose usando la función addRelativePose . Calcule las poses relativas entre cada odometría antes de agregarlas al gráfico de pose.

  2. Agregue mediciones de puntos de referencia de las observaciones de AprilTag en las imágenes usando la función readAprilTag . Debido a que una imagen puede contener varias etiquetas, repita todos los ID devueltos. Agregue puntos de referencia relativos al nodo de pose actual utilizando la función addPointLandmark .

  3. Muestre la imagen con marcadores alrededor de las observaciones de AprilTag. La imagen se actualiza a medida que recorre los datos.

figure(Visible="on")
for i = 1:numel(imageData)
    
    % Add odometry data to pose graph
    T = odomData{i};
    if isempty(lastTform)
        nodePair = addRelativePose(pg,[0 0 0 1 0 0 0],[],lastPoseNodeId);
    else
        relPose = exampleHelperComputeRelativePose(lastTform,T);
        nodePair = addRelativePose(pg,relPose,[],lastPoseNodeId);
    end
    currPoseNodeId = nodePair(2);
    
    % Add landmark measurement based on AprilTag observation in the image.
    I = imageData{i};
    [id,loc,poseRigid3d,detectedFamily] = readAprilTag(I,tagFamily,cameraParams.Intrinsics,tagSize);
    
    for j = 1:numel(id)
        % Insert observation markers to image.
        markerRadius = 6;
        numCorners = size(loc,1);
        markerPosition = [loc(:,:,j) repmat(markerRadius,numCorners,1)];
        I = insertShape(I,FilledCircle=markerPosition,Color="red",Opacity=1);

        t = poseRigid3d(j).Translation/1000; % change from mm to meters
        po = [t(:); 1];
        p = Tb*Ta*po;
                
        if isKey(tagToNodeIDDic,id(j))
            lmkNodeId = tagToNodeIDDic(id(j));
            if ~ismember(lmkNodeId,lmkIDs)
                lmkIDs = [lmkIDs lmkNodeId]; % store unique landmark IDs
            end
            addPointLandmark(pg,p(1:3)',[],currPoseNodeId,lmkNodeId);
        else
            nodePair = addPointLandmark(pg,p(1:3)',[],currPoseNodeId);
            tagToNodeIDDic(id(j)) = nodePair(2);
        end
    end
    
    % Show the image with AprilTag observation markers.
    imshow(I)
    drawnow
            
    lastTform = T;
    lastPoseNodeId = currPoseNodeId;
end

Optimice el gráfico de pose e inspeccione los resultados

Después de construir el gráfico de pose, optimícelo usando la función optimizePoseGraph .

pgUpd = optimizePoseGraph(pg);

Visualice el gráfico de pose inicial y optimizado. El gráfico de pose optimizado muestra estas mejoras:

  • Se ha corregido la posición inicial del robot con respecto a los puntos de referencia.

  • Los puntos de referencia en cada pared están mejor alineados.

  • Se ha corregido la desviación vertical en la trayectoria del robot a lo largo de la dirección z.

figure(Visible="on")
show(pg);
axis equal
xlim([-10.0 5.0])
ylim([-2.0 12.0])
title("Pose Graph Result Before Optimization XY View")

figure(Visible="on")
show(pgUpd);
axis equal
xlim([-10.0 5.0])
ylim([-2.0 12.0])
title("Pose Graph Result After Optimization XY View")

% Vertical drift.
figure(Visible="on")
show(pg);
axis square
xlim([-10.0 5.0])
zlim([-2.0 2.0])
view(0,0)
title("Pose Graph Result Before Optimization XZ View")

show(pgUpd);
axis square
xlim([-10.0 5.0])
zlim([-2.0 2.0])
view(0,0)
title("Pose Graph Result After Optimization XZ View")

Crear gráfico de factores a partir de datos de sensores

Alternativamente, puede crear un gráfico de factores basado en las mediciones de los datos del sensor. El uso de un gráfico de factores hace que sea más fácil incluir sensores adicionales en la optimización y también optimiza más rápido que el gráfico de pose, aunque puede llevar más tiempo configurarlo y construirlo.

Cree el gráfico de factores como un objeto factorGraph .

G = factorGraph;

Cree variables para almacenar la pose anterior y la ID del nodo.

lastTform = [];
lastPoseNodeId = 1;
tagToNodeIDDic = dictionary([],[]);

Repita los datos del sensor y siga estos pasos para agregar factores al gráfico de factores:

  1. Agregue datos de odometría al gráfico de factores como un factor relacionando poses usando el objeto factorTwoPoseSE3 . Calcule las poses relativas entre cada odometría antes de agregarlas al gráfico de factores.

  2. Agregue mediciones de puntos de referencia de las observaciones de AprilTag en las imágenes usando la función readAprilTag . Debido a que la imagen puede contener varias etiquetas, repita todos los ID devueltos. Agregue puntos de referencia relativos al nodo de pose actual utilizando el objeto factorPoseSE3AndPointXYZ .

  3. Muestre la imagen con marcadores alrededor de las observaciones de AprilTag. La imagen se actualiza a medida que recorre los datos.

for i = 1:numel(imageData)
    
    % Add odometry data to factor graph
    T = odomData{i};
    if isempty(lastTform)
        % Add a prior factor to loosely fix the initial node at the origin
        fPrior = factorPoseSE3Prior(1);
        addFactor(G,fPrior);
        newPoseNodeId = lastPoseNodeId + 1;
        % The measurement represents a relative pose in SE(3) between lastPoseNode and newPoseNode.
        fctr = factorTwoPoseSE3([lastPoseNodeId newPoseNodeId],Measurement=[0 0 0 1 0 0 0]);
        addFactor(G,fctr);
    else
        % Calculate relative pose between nodes of lastPoseNodeId and newPoseNodeId
        relPose = exampleHelperComputeRelativePose(lastTform,T);
        newPoseNodeId = G.NumNodes + 1;
        fctr = factorTwoPoseSE3([lastPoseNodeId newPoseNodeId],Measurement=relPose);
        addFactor(G,fctr);
    end
    currPoseNodeId = newPoseNodeId;
    
    % Add landmark measurement based on AprilTag observation in the image.
    I = imageData{i};
    [id,loc,poseRigid3d,detectedFamily] = readAprilTag(I,tagFamily,cameraParams.Intrinsics,tagSize);

    for j = 1:numel(id)
        % Insert observation markers to image.
        markerRadius = 6;
        numCorners = size(loc,1);
        markerPosition = [loc(:,:,j),repmat(markerRadius,numCorners,1)];
        I = insertShape(I,FilledCircle=markerPosition,Color="red",Opacity=1);

        t = poseRigid3d(j).Translation/1000; % change from mm to meter
        po = [t(:); 1];
        p = Tb*Ta*po;
                
        if isKey(tagToNodeIDDic,id(j))
            lmkNodeId = tagToNodeIDDic(id(j));
            % The measurement represents a relative position [dx,dy,dz] between landmark point and current pose node.
            fctr = factorPoseSE3AndPointXYZ([currPoseNodeId lmkNodeId],Measurement=p(1:3)');
            addFactor(G,fctr);
        else
            newPointNodeId = G.NumNodes + 1;
            fctr = factorPoseSE3AndPointXYZ([currPoseNodeId newPointNodeId],Measurement=p(1:3)');
            addFactor(G,fctr);
            tagToNodeIDDic(id(j)) = newPointNodeId;
        end
    end
    
    % Show the image with AprilTag observation markers.
    imshow(I)
    drawnow limitrate
            
    lastTform = T;
    lastPoseNodeId = currPoseNodeId;
end

Optimice el gráfico de factores e inspeccione los resultados

Después de construir el gráfico de factores, optimícelo usando la función de objeto optimize con opciones de solver definidas por un objeto factorGraphSolverOptions . Establezca la propiedad VerbosityLevel en 2 para mostrar más detalles del proceso de optimización.

opt = factorGraphSolverOptions(VerbosityLevel=2);
soln = optimize(G,opt)
iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
   0  4.984516e+02    0.00e+00    5.76e+01   0.00e+00   0.00e+00  1.00e+04        0    2.56e-03    6.04e-03
   1  8.515843e+02   -3.53e+02    5.76e+01   7.53e+01  -7.13e-01  5.00e+03        1    1.94e-02    2.70e-02
   2  8.515843e+02   -3.53e+02    5.76e+01   7.53e+01  -7.13e-01  2.50e+03        0    1.06e-03    3.04e-02
   3  8.515843e+02   -3.53e+02    5.76e+01   7.53e+01  -7.13e-01  1.25e+03        0    6.45e-04    3.26e-02
   4  8.515843e+02   -3.53e+02    5.76e+01   7.53e+01  -7.13e-01  6.25e+02        0    7.57e-04    3.54e-02
   5  8.515843e+02   -3.53e+02    5.76e+01   7.53e+01  -7.13e-01  3.12e+02        0    7.01e-04    3.78e-02
   6  8.515843e+02   -3.53e+02    5.76e+01   7.53e+01  -7.13e-01  1.56e+02        0    6.11e-04    3.96e-02
   7  8.515843e+02   -3.53e+02    5.76e+01   7.53e+01  -7.13e-01  7.81e+01        0    6.51e-04    4.14e-02
   8  5.087687e+02   -1.03e+01    5.76e+01   4.26e+01  -2.13e-02  3.91e+01        0    7.57e-04    4.35e-02
   9  1.365586e+02    3.62e+02    3.27e+01   1.99e+01   7.77e-01  1.17e+02        0    3.05e-03    4.68e-02
  10  4.583660e+02   -3.22e+02    3.27e+01   6.72e+01  -2.40e+00  5.86e+01        1    9.06e-03    5.62e-02
  11  5.329830e+01    8.33e+01    1.94e+01   3.35e+01   6.64e-01  5.86e+01        0    4.06e-03    6.06e-02
  12  5.142921e+01    1.87e+00    1.91e+01   3.05e+01   3.72e-02  2.93e+01        1    1.44e-02    7.54e-02
  13  8.811991e+00    4.26e+01    4.56e+00   1.74e+01   9.59e-01  8.79e+01        1    1.11e-02    8.68e-02
  14  1.032690e+02   -9.45e+01    4.56e+00   5.11e+01  -1.23e+01  4.39e+01        1    1.75e-02    1.05e-01
  15  9.869378e+00   -1.06e+00    4.56e+00   2.55e+01  -1.63e-01  2.20e+01        0    9.05e-04    1.06e-01
  16  3.688774e+00    5.12e+00    2.19e+00   1.28e+01   9.18e-01  6.59e+01        0    2.74e-03    1.09e-01
  17  3.409290e+01   -3.04e+01    2.19e+00   3.84e+01  -1.16e+01  3.30e+01        1    8.06e-03    1.17e-01
  18  4.290375e+00   -6.02e-01    2.19e+00   1.92e+01  -3.01e-01  1.65e+01        0    6.80e-04    1.18e-01
  19  2.344156e+00    1.34e+00    1.79e+00   9.61e+00   8.38e-01  4.94e+01        0    2.68e-03    1.21e-01
  20  1.153071e+01   -9.19e+00    1.79e+00   2.88e+01  -6.83e+00  2.47e+01        1    8.27e-03    1.30e-01
  21  2.301715e+00    4.24e-02    3.62e+00   1.44e+01   4.43e-02  1.24e+01        0    2.67e-03    1.33e-01
  22  1.395292e+00    9.06e-01    1.65e+00   7.21e+00   8.84e-01  3.71e+01        1    1.27e-02    1.46e-01
  23  2.835470e+00   -1.44e+00    1.65e+00   2.17e+01  -1.95e+00  1.85e+01        1    1.05e-02    1.59e-01
  24  1.118728e+00    2.77e-01    1.76e+00   1.08e+01   5.51e-01  1.85e+01        0    3.10e-03    1.64e-01
  25  7.683906e-01    3.50e-01    1.50e+00   1.09e+01   7.36e-01  1.85e+01        1    1.11e-02    1.77e-01
  26  5.116551e-01    2.57e-01    9.30e-01   1.10e+01   7.67e-01  5.56e+01        1    1.31e-02    1.92e-01
  27  3.957137e+00   -3.45e+00    9.30e-01   3.31e+01  -8.79e+00  2.78e+01        1    1.04e-02    2.04e-01
  28  4.955209e-01    1.61e-02    1.77e+00   1.65e+01   5.60e-02  1.39e+01        0    2.89e-03    2.08e-01
  29  2.135810e-01    2.82e-01    7.81e-01   8.27e+00   8.94e-01  4.17e+01        1    1.15e-02    2.21e-01
  30  1.599750e+00   -1.39e+00    7.81e-01   2.17e+01  -9.94e+00  2.09e+01        1    1.26e-02    2.35e-01
  31  2.592216e-01   -4.56e-02    7.81e-01   1.23e+01  -3.75e-01  1.04e+01        0    1.46e-03    2.39e-01
  32  1.358879e-01    7.77e-02    2.36e-01   6.16e+00   8.56e-01  3.13e+01        0    4.65e-03    2.44e-01
  33  8.608814e-01   -7.25e-01    2.36e-01   1.57e+01  -1.11e+01  1.56e+01        1    8.79e-03    2.53e-01
  34  1.788477e-01   -4.30e-02    2.36e-01   9.13e+00  -7.68e-01  7.82e+00        0    5.21e-04    2.54e-01
  35  1.064787e-01    2.94e-02    3.08e-01   4.56e+00   7.69e-01  2.35e+01        0    2.57e-03    2.56e-01
  36  4.251862e-01   -3.19e-01    3.08e-01   1.12e+01  -8.44e+00  1.17e+01        1    1.49e-02    2.71e-01
  37  1.229167e-01   -1.64e-02    3.08e-01   6.74e+00  -4.99e-01  5.87e+00        0    5.38e-04    2.72e-01
  38  8.755732e-02    1.89e-02    1.78e-01   3.37e+00   8.22e-01  1.76e+01        0    4.46e-03    2.77e-01
  39  2.110173e-01   -1.23e-01    1.78e-01   8.08e+00  -6.32e+00  8.80e+00        1    1.31e-02    2.91e-01
  40  9.263937e-02   -5.08e-03    1.78e-01   4.98e+00  -2.96e-01  4.40e+00        0    1.05e-03    2.92e-01
  41  7.771281e-02    9.84e-03    1.22e-01   2.49e+00   8.35e-01  1.32e+01        0    4.50e-03    2.97e-01
  42  1.160680e-01   -3.84e-02    1.22e-01   5.86e+00  -3.85e+00  6.60e+00        1    1.26e-02    3.10e-01
  43  7.727460e-02    4.38e-04    3.67e-01   3.70e+00   4.96e-02  3.30e+00        0    4.57e-03    3.15e-01
  44  6.863371e-02    8.64e-03    1.28e-01   1.90e+00   9.32e-01  9.90e+00        1    1.67e-02    3.32e-01
  45  6.766036e-02    9.73e-04    2.30e-02   1.25e+00   9.83e-01  9.90e+00        1    1.34e-02    3.46e-01
  46  6.764183e-02    1.85e-05    8.05e-05   9.00e-02   1.00e+00  9.90e+00        1    1.26e-02    3.62e-01
Terminating: Function tolerance reached. |cost_change|/cost: 1.078674e-07 <= 1.000000e-06

Solver Summary (v 2.0.0-eigen-(3.3.4)-no_lapack-eigensparse-no_openmp-no_custom_blas)

                                     Original                  Reduced
Parameter blocks                          585                      585
Parameters                               4039                     4039
Effective parameters                     3468                     3468
Residual blocks                           804                      804
Residuals                                4125                     4125

Minimizer                        TRUST_REGION

Sparse linear algebra library    EIGEN_SPARSE
Trust region strategy                  DOGLEG (TRADITIONAL)

                                        Given                     Used
Linear solver          SPARSE_NORMAL_CHOLESKY   SPARSE_NORMAL_CHOLESKY
Threads                                     1                        1
Linear solver ordering              AUTOMATIC                      585

Cost:
Initial                          4.984516e+02
Final                            6.764183e-02
Change                           4.983839e+02

Minimizer iterations                       47
Successful steps                           22
Unsuccessful steps                         25

Time (in seconds):
Preprocessor                         0.003480

  Residual only evaluation           0.021286 (47)
  Jacobian & residual evaluation     0.054238 (22)
  Linear solver                      0.219521 (22)
Minimizer                            0.371506

Postprocessor                        0.000229
Total                                0.375216

Termination:                      CONVERGENCE (Function tolerance reached. |cost_change|/cost: 1.078674e-07 <= 1.000000e-06)
soln = struct with fields:
             InitialCost: 498.4516
               FinalCost: 0.0676
      NumSuccessfulSteps: 22
    NumUnsuccessfulSteps: 25
               TotalTime: 0.3752
         TerminationType: 0
        IsSolutionUsable: 1
        OptimizedNodeIDs: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 … ]
            FixedNodeIDs: [1×0 double]

La información de la solución devuelta indica que la función de objeto optimize ha reducido en gran medida el coste de la ruta, lo que indica que el proceso de optimización ha mejorado la ruta. El valor IsSolutionUsable de 1 también indica que la solución es viable. Si optimize no devuelve resultados convergentes, considere ajustar las opciones del solver , como el número de iteraciones máximas, dependiendo de los cost_change y gradient. Valorespara cada paso.

Recupere los resultados optimizados y visualice la trayectoria del robot en azul y los puntos de referencia en verde.

fgNodeStates = exampleHelperShowFactorGraphResult(G);

Conclusión

Estas imágenes muestran las trayectorias finales del robot, generadas por el gráfico de pose y el gráfico de factores, superpuestas en el plano del área de la oficina mediante una transformación para mostrar la precisión de la trayectoria estimada y las ubicaciones de los puntos de referencia estimados. Si reproduce las imágenes nuevamente, podrá ver una fuerte correlación entre el gráfico de pose y el gráfico de factor. Todos los puntos de referencia verdes están en las paredes o cerca de ellas, y las dos líneas ajustadas basadas en los puntos de referencia detectados se superponen con las paredes del pasillo. Sin embargo, aunque estas dos soluciones son visualmente similares, podemos compararlas numéricamente.

Estas imágenes muestran las posiciones de AprilTags para su referencia. Tenga en cuenta que hay un AprilTag que no es captado por la cámara, marcado con un círculo rojo.

Para comparar numéricamente la diferencia entre los resultados del gráfico de pose y el gráfico de factor, recupere los estados de los nodos del gráfico de pose actualizado y calcule la diferencia absoluta promedio para las posiciones del robot, los ángulos de rotación y las posiciones de los puntos de referencia entre este y el gráfico de factor.

pgUpdNodeStates = nodeEstimates(pgUpd);
[robotPosDiff,robotRotDiff,lmkDiff] = exampleHelperComputeDifference(pgUpdNodeStates,fgNodeStates,lmkIDs)
robotPosDiff = 1×3
10-3 ×

    0.0524    0.0924    0.9312

robotRotDiff = 1×3
10-3 ×

    0.0198    0.1625    0.1355

lmkDiff = 1×3
10-3 ×

    0.0562    0.0865    0.8646

Tenga en cuenta que, para comparar mejor con el resultado del gráfico de pose, especificó una estimación inicial para la pose del nodo inicial del gráfico de factores en el origen utilizando un objeto factorPoseSE3Prior . Sin el factor anterior, el proceso de optimización del gráfico de factores da como resultado un gráfico que tiene poses y posiciones relativas óptimas, pero las poses y posiciones absolutas no son necesariamente correctas. La diferencia absoluta promedio es bastante pequeña, lo que significa que ambos métodos brindan resultados optimizados similares. En comparación con el gráfico de pose, el tiempo de optimización del gráfico de factores es mucho más corto, pero lleva más tiempo configurar y construir el gráfico de factores.