exponenta event banner

Построение карты на основе данных Lidar

В этом примере показано, как обрабатывать данные 3-D лидара от датчика, установленного на транспортном средстве, для постепенного построения карты с помощью показаний инерциального измерительного блока (IMU). Такая карта может облегчить планирование маршрута для навигации транспортного средства или может использоваться для локализации. Для оценки сформированной карты этот пример также показывает, как сравнивать траекторию транспортного средства с записью глобальной системы позиционирования (GPS).

Обзор

Карты высокой четкости (HD) - это картографические сервисы, которые обеспечивают точную геометрию дорог с точностью до нескольких сантиметров. Этот уровень точности делает карты высокой четкости подходящими для автоматизированных рабочих процессов вождения, таких как локализация и навигация. Такие HD-карты генерируются путем построения карты из 3-D лидарных сканирований, в сочетании с высокоточными GPS и или IMU-датчиками и могут использоваться для локализации транспортного средства в пределах нескольких сантиметров. В этом примере реализуется подмножество функций, необходимых для построения такой системы.

В этом примере показано, как:

  • Загрузка, изучение и визуализация записанных данных о вождении

  • Построение карты с помощью сканирования lidar

  • Улучшение карты с помощью показаний IMU

Загрузка и изучение записанных данных о вождении

Данные, используемые в этом примере, получены из этого репозитория GitHub ® и представляют приблизительно 100 секунд данных лидара, GPS и IMU. Данные сохраняются в виде MAT-файлов, каждый из которых содержит timetable. Загрузите MAT-файлы из репозитория и загрузите их в рабочую область MATLAB ®.

Примечание: Загрузка может занять несколько минут.

baseDownloadURL = 'https://github.com/mathworks/udacity-self-driving-data-subset/raw/master/drive_segment_11_18_16/';
dataFolder      = fullfile(tempdir, 'drive_segment_11_18_16', filesep);
options         = weboptions('Timeout', Inf);

lidarFileName = dataFolder + "lidarPointClouds.mat";
imuFileName   = dataFolder + "imuOrientations.mat";
gpsFileName   = dataFolder + "gpsSequence.mat";

folderExists  = exist(dataFolder, 'dir');
matfilesExist = exist(lidarFileName, 'file') && exist(imuFileName, 'file') ...
    && exist(gpsFileName, 'file');

if ~folderExists
    mkdir(dataFolder);
end

if ~matfilesExist
    disp('Downloading lidarPointClouds.mat (613 MB)...')
    websave(lidarFileName, baseDownloadURL + "lidarPointClouds.mat", options);

    disp('Downloading imuOrientations.mat (1.2 MB)...')
    websave(imuFileName, baseDownloadURL + "imuOrientations.mat", options);

    disp('Downloading gpsSequence.mat (3 KB)...')
    websave(gpsFileName, baseDownloadURL + "gpsSequence.mat", options);
end

Во-первых, загрузите данные об облаке пункта, сохраненные от лидара Velodyne® HDL32E. Каждое сканирование данных лидара сохраняется как 3-D облако точек с помощью pointCloud объект. Этот объект внутренне организует данные с использованием древовидной структуры данных K-d для более быстрого поиска. Временная метка, связанная с каждым сканированием лидара, записывается в Time переменная расписания.

% Load lidar data from MAT-file
data = load(lidarFileName);
lidarPointClouds = data.lidarPointClouds;

% Display first few rows of lidar data
head(lidarPointClouds)
ans =

  8×1 timetable

        Time            PointCloud   
    _____________    ________________

    23:46:10.5115    [1×1 pointCloud]
    23:46:10.6115    [1×1 pointCloud]
    23:46:10.7116    [1×1 pointCloud]
    23:46:10.8117    [1×1 pointCloud]
    23:46:10.9118    [1×1 pointCloud]
    23:46:11.0119    [1×1 pointCloud]
    23:46:11.1120    [1×1 pointCloud]
    23:46:11.2120    [1×1 pointCloud]

Загрузите данные GPS из MAT-файла. Latitude, Longitude, и Altitude переменные timetable используются для хранения географических координат, записанных устройством GPS на транспортном средстве.

% Load GPS sequence from MAT-file
data = load(gpsFileName);
gpsSequence = data.gpsSequence;

% Display first few rows of GPS data
head(gpsSequence)
ans =

  8×3 timetable

        Time         Latitude    Longitude    Altitude
    _____________    ________    _________    ________

    23:46:11.4563      37.4       -122.11      -42.5  
    23:46:12.4563      37.4       -122.11      -42.5  
    23:46:13.4565      37.4       -122.11      -42.5  
    23:46:14.4455      37.4       -122.11      -42.5  
    23:46:15.4455      37.4       -122.11      -42.5  
    23:46:16.4567      37.4       -122.11      -42.5  
    23:46:17.4573      37.4       -122.11      -42.5  
    23:46:18.4656      37.4       -122.11      -42.5  

Загрузите данные IMU из MAT-файла. IMU обычно состоит из отдельных датчиков, которые сообщают информацию о движении транспортного средства. Они объединяют несколько датчиков, включая акселерометры, гироскопы и магнитометры. Orientation переменная сохраняет сообщенную ориентацию датчика IMU. Эти показания представлены в виде кватернионов. Каждое считывание задается как вектор 1 на 4, содержащий четыре части кватерниона. Преобразование вектора 1 на 4 в quaternion объект.

% Load IMU recordings from MAT-file
data = load(imuFileName);
imuOrientations = data.imuOrientations;

% Convert IMU recordings to quaternion type
imuOrientations = convertvars(imuOrientations, 'Orientation', 'quaternion');

% Display first few rows of IMU data
head(imuOrientations)
ans =

  8×1 timetable

        Time           Orientation   
    _____________    ________________

    23:46:11.4570    [1×1 quaternion]
    23:46:11.4605    [1×1 quaternion]
    23:46:11.4620    [1×1 quaternion]
    23:46:11.4655    [1×1 quaternion]
    23:46:11.4670    [1×1 quaternion]
    23:46:11.4705    [1×1 quaternion]
    23:46:11.4720    [1×1 quaternion]
    23:46:11.4755    [1×1 quaternion]

Чтобы понять, как поступают показания датчика, для каждого датчика вычислите приблизительный кадр duration.

lidarFrameDuration = median(diff(lidarPointClouds.Time));
gpsFrameDuration   = median(diff(gpsSequence.Time));
imuFrameDuration   = median(diff(imuOrientations.Time));

% Adjust display format to seconds
lidarFrameDuration.Format = 's';
gpsFrameDuration.Format   = 's';
imuFrameDuration.Format   = 's';

% Compute frame rates
lidarRate = 1/seconds(lidarFrameDuration);
gpsRate   = 1/seconds(gpsFrameDuration);
imuRate   = 1/seconds(imuFrameDuration);

% Display frame durations and rates
fprintf('Lidar: %s, %3.1f Hz\n', char(lidarFrameDuration), lidarRate);
fprintf('GPS  : %s, %3.1f Hz\n', char(gpsFrameDuration), gpsRate);
fprintf('IMU  : %s, %3.1f Hz\n', char(imuFrameDuration), imuRate);
Lidar: 0.10008 sec, 10.0 Hz
GPS  : 1.0001 sec, 1.0 Hz
IMU  : 0.002493 sec, 401.1 Hz

GPS-датчик - самый медленный, работает со скоростью, близкой к 1 Гц. Лидар следующий самый медленный, работает со скоростью, близкой к 10 Гц, за ним следует IMU со скоростью почти 400 Гц.

Визуализация данных о вождении

Чтобы понять, что содержит сцена, визуализируйте записанные данные с помощью потоковых проигрывателей. Для визуализации показаний GPS используйте geoplayer. Визуализация показаний лидара с помощью pcplayer.

% Create a geoplayer to visualize streaming geographic coordinates
latCenter = gpsSequence.Latitude(1);
lonCenter = gpsSequence.Longitude(1);
zoomLevel = 17;

gpsPlayer = geoplayer(latCenter, lonCenter, zoomLevel);

% Plot the full route
plotRoute(gpsPlayer, gpsSequence.Latitude, gpsSequence.Longitude);

% Determine limits for the player
xlimits = [-45 45]; % meters
ylimits = [-45 45];
zlimits = [-10 20];

% Create a pcplayer to visualize streaming point clouds from lidar sensor
lidarPlayer = pcplayer(xlimits, ylimits, zlimits);

% Customize player axes labels
xlabel(lidarPlayer.Axes, 'X (m)')
ylabel(lidarPlayer.Axes, 'Y (m)')
zlabel(lidarPlayer.Axes, 'Z (m)')

title(lidarPlayer.Axes, 'Lidar Sensor Data')

% Align players on screen
helperAlignPlayers({gpsPlayer, lidarPlayer});

% Outer loop over GPS readings (slower signal)
for g = 1 : height(gpsSequence)-1

    % Extract geographic coordinates from timetable
    latitude  = gpsSequence.Latitude(g);
    longitude = gpsSequence.Longitude(g);

    % Update current position in GPS display
    plotPosition(gpsPlayer, latitude, longitude);

    % Compute the time span between the current and next GPS reading
    timeSpan = timerange(gpsSequence.Time(g), gpsSequence.Time(g+1));

    % Extract the lidar frames recorded during this time span
    lidarFrames = lidarPointClouds(timeSpan, :);

    % Inner loop over lidar readings (faster signal)
    for l = 1 : height(lidarFrames)

        % Extract point cloud
        ptCloud = lidarFrames.PointCloud(l);

        % Update lidar display
        view(lidarPlayer, ptCloud);

        % Pause to slow down the display
        pause(0.01)
    end
end

Использование записанных данных лидара для построения карты

Лидары - это мощные датчики, которые можно использовать для восприятия в сложных средах, где другие датчики не полезны. Они обеспечивают детальный, полный 360-градусный обзор окружающей среды транспортного средства.

% Hide players
hide(gpsPlayer)
hide(lidarPlayer)

% Select a frame of lidar data to demonstrate registration workflow
frameNum = 600;
ptCloud = lidarPointClouds.PointCloud(frameNum);

% Display and rotate ego view to show lidar data
helperVisualizeEgoView(ptCloud);

Лидары можно использовать для построения сантиметровых точных HD-карт, включая HD-карты целых городов. Эти карты впоследствии могут использоваться для локализации в транспортном средстве. Типичным подходом к построению такой карты является выравнивание последовательных лидарных сканирований, полученных от движущегося транспортного средства, и их объединение в одно большое облако точек. В остальном примере рассматривается этот подход к построению карты.

  1. Align lidar scans: выравнивание последовательных lidar scans с использованием метода регистрации облака точек, такого как итерационный алгоритм ближайшей точки (ICP) или алгоритм преобразования нормальных распределений (NDT). Посмотрите pcregistericp и pcregisterndt для получения более подробной информации о каждом алгоритме. В этом примере используется неразрушающий контроль, поскольку он обычно более точен, особенно при рассмотрении вращений. pcregisterndt функция возвращает жесткое преобразование, которое выравнивает движущееся облако точек относительно облака опорных точек. Последовательно составляя эти преобразования, каждое облако точек преобразуется обратно в опорную рамку первого облака точек.

  2. Объединение выровненных сканирований: как только новое сканирование облака точек зарегистрировано и преобразовано обратно в опорную рамку первого облака точек, облако точек может быть объединено с первым облаком точек с помощью pcmerge.

Начните с двух точечных облаков, соответствующих ближайшим сканированиям лидара. Чтобы ускорить обработку и накопить достаточно движения между сканированиями, используйте каждое десятое сканирование.

skipFrames = 10;
frameNum   = 100;

fixed  = lidarPointClouds.PointCloud(frameNum);
moving = lidarPointClouds.PointCloud(frameNum + skipFrames);

Перед регистрацией обработайте облако точек, чтобы сохранить отличительные структуры в облаке точек. Эти этапы предварительной обработки включают в себя следующее: * Обнаружение и удаление наземной плоскости * Обнаружение и удаление эго-транспортного средства

Эти шаги более подробно описаны в примере «Наземная плоскость и обнаружение препятствий с помощью Лидара». В этом примере helperProcessPointCloud функция помощника выполняет эти шаги.

fixedProcessed  = helperProcessPointCloud(fixed);
movingProcessed = helperProcessPointCloud(moving);

Отображение необработанных и обработанных облаков точек на виде сверху. Пурпурные точки были удалены во время обработки. Эти точки соответствуют наземной плоскости и эго-транспортному средству.

hFigFixed = figure;
pcshowpair(fixed, fixedProcessed)
view(2);                            % Adjust view to show top-view

helperMakeFigurePublishFriendly(hFigFixed);

% Downsample the point clouds prior to registration. Downsampling improves
% both registration accuracy and algorithm speed.
downsamplePercent = 0.1;
fixedDownsampled  = pcdownsample(fixedProcessed, 'random', downsamplePercent);
movingDownsampled = pcdownsample(movingProcessed, 'random', downsamplePercent);

Предварительно обработав облака точек, зарегистрируйте их с помощью неразрушающего контроля. Визуализация выравнивания до и после регистрации.

regGridStep = 5;
tform = pcregisterndt(movingDownsampled, fixedDownsampled, regGridStep);

movingReg = pctransform(movingProcessed, tform);

% Visualize alignment in top-view before and after registration
hFigAlign = figure;

subplot(121)
pcshowpair(movingProcessed, fixedProcessed)
title('Before Registration')
view(2)

subplot(122)
pcshowpair(movingReg, fixedProcessed)
title('After Registration')
view(2)

helperMakeFigurePublishFriendly(hFigAlign);

Обратите внимание, что облака точек хорошо выровнены после регистрации. Несмотря на то, что облака точек тесно выровнены, выравнивание все еще не идеально.

Затем объедините облака точек с помощью pcmerge.

mergeGridStep = 0.5;
ptCloudAccum = pcmerge(fixedProcessed, movingReg, mergeGridStep);

hFigAccum = figure;
pcshow(ptCloudAccum)
title('Accumulated Point Cloud')
view(2)

helperMakeFigurePublishFriendly(hFigAccum);

Теперь, когда конвейер обработки для одной пары точечных облаков хорошо понятен, сложите это в цикл по всей последовательности записанных данных. helperLidarMapBuilder класс складывает все это вместе. updateMap метод класса принимает в новом облаке точек и проходит через шаги, описанные ранее:

  • Обработка облака точек путем удаления наземной плоскости и эго-транспортного средства с помощью processPointCloud способ.

  • Понижение дискретизации облака точек.

  • Оценка жесткого преобразования, необходимого для объединения предыдущего облака точек с текущим облаком точек.

  • Преобразование облака точек обратно в первый кадр.

  • Объединение облака точек с накопленной картой облака точек.

Кроме того, updateMap способ также принимает начальную оценку преобразования, которая используется для инициализации регистрации. Хорошая инициализация может значительно улучшить результаты регистрации. И наоборот, плохая инициализация может отрицательно сказаться на регистрации. Обеспечение хорошей инициализации также может улучшить время выполнения алгоритма.

Общий подход к предоставлению начальной оценки для регистрации заключается в использовании предположения о постоянной скорости. Используйте преобразование из предыдущей итерации в качестве начальной оценки.

updateDisplay метод дополнительно создает и обновляет 2-й вид сверху, текущий показ облака пункта.

% Create a map builder object
mapBuilder = helperLidarMapBuilder('DownsamplePercent', downsamplePercent);

% Set random number seed
rng(0);

closeDisplay = false;
numFrames    = height(lidarPointClouds);

tform = rigid3d;
for n = 1 : skipFrames : numFrames - skipFrames

    % Get the nth point cloud
    ptCloud = lidarPointClouds.PointCloud(n);

    % Use transformation from previous iteration as initial estimate for
    % current iteration of point cloud registration. (constant velocity)
    initTform = tform;

    % Update map using the point cloud
    tform = updateMap(mapBuilder, ptCloud, initTform);

    % Update map display
    updateDisplay(mapBuilder, closeDisplay);
end

Регистрация облака точек сама по себе создает карту среды, пересекаемой транспортным средством. Хотя карта может казаться локально согласованной, она могла иметь значительный дрейф по всей последовательности.

Для визуальной оценки качества построенной карты используйте записанные GPS-показания в качестве наземной истинной траектории. Сначала преобразуйте показания GPS (широта, долгота, высота) в локальную систему координат. Выберите локальную систему координат, совпадающую с началом первого облака точек в последовательности. Это преобразование вычисляется с использованием двух преобразований:

  1. Преобразование координат GPS в локальные декартовы координаты Восток-Север-Вверх с помощью latlon2local функция. Местоположение GPS от начала траектории используется в качестве опорной точки и определяет начало локальной системы координат x, y, z.

  2. Поверните декартовы координаты так, чтобы локальная система координат была выровнена с координатами первого лидарного датчика. Поскольку точная конфигурация установки лидара и GPS на транспортном средстве неизвестна, они оцениваются.

% Select reference point as first GPS reading
origin = [gpsSequence.Latitude(1), gpsSequence.Longitude(1), gpsSequence.Altitude(1)];

% Convert GPS readings to a local East-North-Up coordinate system
[xEast, yNorth, zUp] = latlon2local(gpsSequence.Latitude, gpsSequence.Longitude, ...
    gpsSequence.Altitude, origin);

% Estimate rough orientation at start of trajectory to align local ENU
% system with lidar coordinate system
theta = median(atan2d(yNorth(1:15), xEast(1:15)));

R = [ cosd(90-theta) sind(90-theta) 0;
     -sind(90-theta) cosd(90-theta) 0;
     0               0              1];

% Rotate ENU coordinates to align with lidar coordinate system
groundTruthTrajectory = [xEast, yNorth, zUp] * R;

Наложите траекторию истинности земли на построенную карту.

hold(mapBuilder.Axes, 'on')
scatter(mapBuilder.Axes, groundTruthTrajectory(:,1), groundTruthTrajectory(:,2), ...
    'green','filled');

helperAddLegend(mapBuilder.Axes, ...
    {'Map Points', 'Estimated Trajectory', 'Ground Truth Trajectory'});

После первоначального разворота расчётная траектория значительно отклоняется от наземной истинной траектории. Траектория, оцененная только с помощью регистрации облака точек, может дрейфовать по ряду причин:

  • Шумное сканирование датчика без достаточного перекрытия

  • Отсутствие достаточно сильных элементов, например, вблизи протяженных дорог

  • Неточное начальное преобразование, особенно когда поворот значителен.

% Close map display
updateDisplay(mapBuilder, true);

Использование ориентации IMU для улучшения построенной карты

IMU - электронное устройство, установленное на платформе. IMU содержат множество датчиков, которые сообщают различную информацию о движении транспортного средства. Типичные IMU включают акселерометры, гироскопы и магнитометры. IMU может обеспечить надежную меру ориентации.

Используйте показания IMU, чтобы получить лучшую первоначальную оценку для регистрации. Показания датчика, представленные IMU, использованные в этом примере, уже отфильтрованы на устройстве.

% Reset the map builder to clear previously built map
reset(mapBuilder);

% Set random number seed
rng(0);

initTform = rigid3d;
for n = 1 : skipFrames : numFrames - skipFrames

    % Get the nth point cloud
    ptCloud = lidarPointClouds.PointCloud(n);

    if n > 1
        % Since IMU sensor reports readings at a much faster rate, gather
        % IMU readings reported since the last lidar scan.
        prevTime = lidarPointClouds.Time(n - skipFrames);
        currTime = lidarPointClouds.Time(n);
        timeSinceScan = timerange(prevTime, currTime);

        imuReadings = imuOrientations(timeSinceScan, 'Orientation');

        % Form an initial estimate using IMU readings
        initTform = helperComputeInitialEstimateFromIMU(imuReadings, tform);
    end

    % Update map using the point cloud
    tform = updateMap(mapBuilder, ptCloud, initTform);

    % Update map display
    updateDisplay(mapBuilder, closeDisplay);
end


% Superimpose ground truth trajectory on new map
hold(mapBuilder.Axes, 'on')
scatter(mapBuilder.Axes, groundTruthTrajectory(:,1), groundTruthTrajectory(:,2), ...
    'green','filled');

helperAddLegend(mapBuilder.Axes, ...
    {'Map Points', 'Estimated Trajectory', 'Ground Truth Trajectory'});

% Capture snapshot for publishing
snapnow;

% Close open figures
close([hFigFixed, hFigAlign, hFigAccum]);
updateDisplay(mapBuilder, true);

Использование оценки ориентации от IMU значительно улучшило регистрацию, что привело к гораздо более близкой траектории с меньшим дрейфом.

Вспомогательные функции

helperAlignPlayers выравнивает массив ячеек потоковых проигрывателей таким образом, чтобы они располагались слева направо на экране.

function helperAlignPlayers(players)

validateattributes(players, {'cell'}, {'vector'});

hasAxes = cellfun(@(p)isprop(p,'Axes'),players,'UniformOutput', true);
if ~all(hasAxes)
    error('Expected all viewers to have an Axes property');
end

screenSize   = get(groot, 'ScreenSize');
screenMargin = [50, 100];

playerSizes = cellfun(@getPlayerSize, players, 'UniformOutput', false);
playerSizes = cell2mat(playerSizes);

maxHeightInSet = max(playerSizes(1:3:end));

% Arrange players vertically so that the tallest player is 100 pixels from
% the top.
location = round([screenMargin(1), screenSize(4)-screenMargin(2)-maxHeightInSet]);
for n = 1 : numel(players)
    player = players{n};

    hFig = ancestor(player.Axes, 'figure');
    hFig.OuterPosition(1:2) = location;

    % Set up next location by going right
    location = location + [50+hFig.OuterPosition(3), 0];
end

    function sz = getPlayerSize(viewer)

        % Get the parent figure container
        h = ancestor(viewer.Axes, 'figure');

        sz = h.OuterPosition(3:4);
    end
end

helperVisualizeEgoView визуализирует данные облака точек в эго-перспективе путем поворота вокруг центра.

function player = helperVisualizeEgoView(ptCloud)

% Create a pcplayer object
xlimits = ptCloud.XLimits;
ylimits = ptCloud.YLimits;
zlimits = ptCloud.ZLimits;

player = pcplayer(xlimits, ylimits, zlimits);

% Turn off axes lines
axis(player.Axes, 'off');

% Set up camera to show ego view
camproj(player.Axes, 'perspective');
camva(player.Axes, 90);
campos(player.Axes, [0 0 0]);
camtarget(player.Axes, [-1 0 0]);

% Set up a transformation to rotate by 5 degrees
theta = 5;
R = [ cosd(theta) sind(theta) 0 0
     -sind(theta) cosd(theta) 0 0
      0          0            1 0
      0          0            0 1];
rotateByTheta = rigid3d(R);

for n = 0 : theta : 359
    % Rotate point cloud by theta
    ptCloud = pctransform(ptCloud, rotateByTheta);

    % Display point cloud
    view(player, ptCloud);

    pause(0.05)
end
end

helperProcessPointCloud обрабатывает облако точек путем удаления точек, принадлежащих наземной плоскости или эго-транспортному средству.

function ptCloudProcessed = helperProcessPointCloud(ptCloud)

% Check if the point cloud is organized
isOrganized = ~ismatrix(ptCloud.Location);

% If the point cloud is organized, use range-based flood fill algorithm
% (segmentGroundFromLidarData). Otherwise, use plane fitting.
groundSegmentationMethods = ["planefit", "rangefloodfill"];
method = groundSegmentationMethods(isOrganized+1);

if method == "planefit"
    % Segment ground as the dominant plane, with reference normal vector
    % pointing in positive z-direction, using pcfitplane. For organized
    % point clouds, consider using segmentGroundFromLidarData instead.
    maxDistance    = 0.4;       % meters
    maxAngDistance = 5;         % degrees
    refVector      = [0, 0, 1]; % z-direction

    [~,groundIndices] = pcfitplane(ptCloud, maxDistance, refVector, maxAngDistance);
elseif method == "rangefloodfill"
    % Segment ground using range-based flood fill.
    groundIndices = segmentGroundFromLidarData(ptCloud);
else
    error("Expected method to be 'planefit' or 'rangefloodfill'")
end

% Segment ego vehicle as points within a given radius of sensor
sensorLocation = [0, 0, 0];
radius         = 3.5;

egoIndices  = findNeighborsInRadius(ptCloud, sensorLocation, radius);

% Remove points belonging to ground or ego vehicle
ptsToKeep = true(ptCloud.Count, 1);
ptsToKeep(groundIndices) = false;
ptsToKeep(egoIndices)    = false;

% If the point cloud is organized, retain organized structure
if isOrganized
    ptCloudProcessed = select(ptCloud, find(ptsToKeep), 'OutputSize', 'full');
else
    ptCloudProcessed = select(ptCloud, find(ptsToKeep));
end
end

helperComputeInitialEstimateFromIMU оценивает начальное преобразование для NDT с использованием показаний ориентации IMU и предварительно оцененного преобразования.

function tform = helperComputeInitialEstimateFromIMU(imuReadings, prevTform)

% Initialize transformation using previously estimated transform
tform = prevTform;

% If no IMU readings are available, return
if height(imuReadings) <= 1
    return;
end

% IMU orientation readings are reported as quaternions representing the
% rotational offset to the body frame. Compute the orientation change
% between the first and last reported IMU orientations during the interval
% of the lidar scan.
q1 = imuReadings.Orientation(1);
q2 = imuReadings.Orientation(end);

% Compute rotational offset between first and last IMU reading by
%   - Rotating from q2 frame to body frame
%   - Rotating from body frame to q1 frame
q = q1 * conj(q2);

% Convert to Euler angles
yawPitchRoll = euler(q, 'ZYX', 'point');

% Discard pitch and roll angle estimates. Use only heading angle estimate
% from IMU orientation.
yawPitchRoll(2:3) = 0;

% Convert back to rotation matrix
q = quaternion(yawPitchRoll, 'euler', 'ZYX', 'point');
R = rotmat(q, 'point');

% Use computed rotation
tform.T(1:3, 1:3) = R';
end

helperAddLegend добавляет легенду к осям.

function helperAddLegend(hAx, labels)

% Add a legend to the axes
hLegend = legend(hAx, labels{:});

% Set text color and font weight
hLegend.TextColor  = [1 1 1];
hLegend.FontWeight = 'bold';
end

helperMakeFigurePublishFriendly корректирует рисунки таким образом, чтобы снимок экрана, снятый при публикации, был правильным.

function helperMakeFigurePublishFriendly(hFig)

if ~isempty(hFig) && isvalid(hFig)
    hFig.HandleVisibility = 'callback';
end

end

См. также

Функции

Объекты

Связанные темы

Внешние веб-сайты