exponenta event banner

Создание кода для фузера дорожек с гетерогенными исходными дорожками

В этом примере показано, как создать код для алгоритма слияния на уровне дорожки в сценарии, где дорожки происходят из разнородных источников с различными определениями состояний. Этот пример основан на примере Track-Level Fusion of Radar и Lidar Data, в котором пространства состояний трасс, генерируемых из лидарных и радиолокационных источников, различны.

Определение путевого фузера для создания кода

Можно создать код для trackFuser с использованием Coder™ MATLAB ®. Для этого необходимо изменить код в соответствии со следующими ограничениями:

Функция ввода генерации кода

Следуйте инструкциям по использованию системных объектов при создании кода MATLAB (кодер MATLAB). Для создания кода сначала необходимо определить функцию начального уровня, в которой определен объект. Кроме того, функция не может использовать массивы объектов в качестве входных или выходных данных. В этом примере функция начального уровня определяется как гетерогенная функция Fuser. Функция должна находиться в пути при создании кода для нее. Поэтому он не может быть частью этого сценария в реальном времени и прилагается в этом примере. Функция принимает локальные дорожки и текущее время в качестве входных и выходных центральных дорожек.

Чтобы сохранить состояние fuser между вызовами функции, вы определяете fuser как persistent переменная. При первом вызове необходимо определить переменную fuser, поскольку она пуста. Остальная часть следующих шагов кода: trackFuser и возвращает слитые дорожки.

function tracks = heterogeneousInputsFuser(localTracks,time)
%#codegen

persistent fuser
if isempty(fuser)
    % Define the radar source configuration
    radarConfig = fuserSourceConfiguration('SourceIndex',1,...
        'IsInitializingCentralTracks',true,...
        'CentralToLocalTransformFcn',@central2local,...
        'LocalToCentralTransformFcn',@local2central);
    
    % Define the lidar source configuration
    lidarConfig = fuserSourceConfiguration('SourceIndex',2,...
        'IsInitializingCentralTracks',true,...
        'CentralToLocalTransformFcn',@central2local,...
        'LocalToCentralTransformFcn',@local2central);
    
    % Create a trackFuser object
    fuser = trackFuser(...
        'MaxNumSources', 2, ...
        'SourceConfigurations',{radarConfig;lidarConfig},...
        'StateTransitionFcn',@helperctcuboid,...
        'StateTransitionJacobianFcn',@helperctcuboidjac,...
        'ProcessNoise',diag([1 3 1]),...
        'HasAdditiveProcessNoise',false,...
        'AssignmentThreshold',[250 inf],...
        'ConfirmationThreshold',[3 5],...
        'DeletionThreshold',[5 5],...
        'StateFusion','Custom',...
        'CustomStateFusionFcn',@helperRadarLidarFusionFcn);
end

tracks = fuser(localTracks, time);
end

Однородные конфигурации источника

В этом примере конфигурации радара и источника лидара определяются иначе, чем в оригинальном примере объединения радаров и данных лидара. В исходном примере CentralToLocalTransformFcn и LocalToCentralTransformFcn свойства двух конфигураций источника различны, поскольку они используют разные дескрипторы функций. Это делает исходные конфигурации гетерогенным массивом ячеек. Такое определение является правильным и действительным при выполнении в MATLAB. Однако при создании кода во всех конфигурациях источника должны использоваться одни и те же дескрипторы функций. Чтобы избежать различных дескрипторов функций, необходимо определить одну функцию для преобразования дорожек из центрального (fuser) определения в локальное (source) определение и одну функцию для преобразования из локального в центральное. Каждая из этих функций переключается между функциями преобразования, определенными для отдельных источников в исходном примере. Обе функции являются частью гетерогенной функции Fuser.

Вот код для local2central , которая использует SourceIndex для определения правильной используемой функции. Поскольку два типа локальных дорожек преобразуются в одно и то же определение центральной дорожки, нет необходимости предопределять центральную дорожку.

function centralTrack = local2central(localTrack)
switch localTrack.SourceIndex
    case 1 % radar
        centralTrack = radar2central(localTrack);
    otherwise % lidar
        centralTrack = lidar2central(localTrack);
end
end

Функция central2local преобразует центральную дорожку в радиолокационную, если SourceIndex равно 1 или в лидарную дорожку, если SourceIndex равно 2. Поскольку два трека имеют разное определение State, StateCovariance, и TrackLogicState, необходимо предварительно определить выходные данные. Вот фрагмент кода для функции:

function localTrack = central2local(centralTrack)
state = 0;
stateCov = 1;
coder.varsize('state', [10, 1], [1 0]);
coder.varsize('stateCov', [10 10], [1 1]);
localTrack = objectTrack('State', state, 'StateCovariance', stateCov);

switch centralTrack.SourceIndex
    case 1
        localTrack = central2radar(centralTrack);
    case 2
        localTrack = central2lidar(centralTrack);
    otherwise
        % This branch is never reached but is necessary to force code 
        % generation to use the predefined localTrack.
end
end

Функции radar2central и central2radar те же, что и в исходном примере, но перемещены из реального сценария в гетерогенную функцию FuserFuser. Вы также добавляете lidar2central и central2lidar функции к гетерогенной функции Fuser. Эти две функции преобразуются из определения дорожки, используемого фьюзером, в определение дорожки лидара.

Выполните пример в MATLAB

Перед созданием кода убедитесь, что пример по-прежнему выполняется после всех изменений, внесенных в fuser. Файл lidarRadarData.mat содержит тот же сценарий, что и в исходном примере. Он также содержит набор радиолокационных и лидарных дорожек, записанных на каждом этапе этого примера. Аналогичный экран также используется для визуализации примера и его определения. trackGOSPAMetric объекты для оценки производительности отслеживания.

% Load the scenario and recorded local tracks
load('lidarRadarData.mat','scenario','localTracksCollection')
display = helperTrackFusionCodegenDisplay('FollowActorID',3);
showLegend(display,scenario);

% Radar GOSPA
gospaRadar = trackGOSPAMetric('Distance','custom',...
    'DistanceFcn',@helperRadarDistance,...
    'CutoffDistance',25);

% Lidar GOSPA
gospaLidar = trackGOSPAMetric('Distance','custom',...
    'DistanceFcn',@helperLidarDistance,...
    'CutoffDistance',25);

% Central/Fused GOSPA
gospaCentral = trackGOSPAMetric('Distance','custom',...
    'DistanceFcn',@helperLidarDistance,... % State space is same as lidar
    'CutoffDistance',25);


gospa = zeros(3,0);
missedTargets = zeros(3,0);
falseTracks = zeros(3,0);
% Ground truth for metrics. This variable updates every time step
% automatically, because it is a handle to the actors.
groundTruth = scenario.Actors(2:end);

fuserStepped = false;
fusedTracks = objectTrack.empty;
idx = 1;
clear heterogeneousInputsFuser
while advance(scenario)
    time = scenario.SimulationTime;
    localTracks = localTracksCollection{idx};
    
    if ~isempty(localTracks) || fuserStepped
        fusedTracks = heterogeneousInputsFuser(localTracks,time);
        fuserStepped = true;
    end
    
    radarTracks = localTracks([localTracks.SourceIndex]==1);
    lidarTracks = localTracks([localTracks.SourceIndex]==2);
    
    % Capture GOSPA and its components for all trackers
    [gospa(1,idx),~,~,~,missedTargets(1,idx),falseTracks(1,idx)] = gospaRadar(radarTracks, groundTruth);
    [gospa(2,idx),~,~,~,missedTargets(2,idx),falseTracks(2,idx)] = gospaLidar(lidarTracks, groundTruth);
    [gospa(3,idx),~,~,~,missedTargets(3,idx),falseTracks(3,idx)] = gospaCentral(fusedTracks, groundTruth);
    
    % Update the display
    display(scenario,[],[], radarTracks,...
        [],[],[],[], lidarTracks, fusedTracks);
    
    idx = idx + 1;
end

Создание кода для путевого фузера

Для создания кода необходимо определить типы ввода как для радиолокационной, так и для лидарной дорожек и метки времени. Как в оригинальном сценарии, так и в предыдущем разделе радиолокационные и лидарные дорожки определяются как массивы objectTrack объекты. При создании кода функция начального уровня не может использовать массив объектов. Вместо этого определяется массив структур.

Используется структура oneLocalTrack определить входы, поступающие с радиолокационных и лидарных путей. При создании кода конкретные типы данных каждого поля в структуре должны быть определены точно так же, как типы, определенные для соответствующих свойств в записанных дорожках. Кроме того, размер каждого поля должен быть определен правильно. Вы используете coder.typeof (Кодер MATLAB) для указания полей с переменным размером: State, StateCovariance, и TrackLogicState. Вы определяете localTracks ввод с помощью oneLocalTrack структура и coder.typeof функция, поскольку количество входных дорожек изменяется от нуля до восьми на каждом шаге. Функция используется codegen (Кодер MATLAB) для создания кода.

Примечания:

  1. Если входные дорожки используют различные типы для State и StateCovariance необходимо выбрать тип для использования, двойной или одиночный. В этом примере все дорожки используют двойную точность, и нет необходимости в этом шаге.

  2. Если входные дорожки используют различные определения StateParameters, сначала необходимо создать суперсеть из всех StateParameters и использовать этот суперсет в StateParameters поле. Аналогичный процесс должен быть выполнен для ObjectAttributes поле. В этом примере все дорожки используют одно и то же определение StateParameters и ObjectAttributes.

% Define the inputs to fuserHeterogeneousInputs for code generation
oneLocalTrack = struct(...
    'TrackID', uint32(0), ...
    'BranchID', uint32(0), ...
    'SourceIndex', uint32(0), ...
    'UpdateTime', double(0), ...
    'Age', uint32(0), ...
    'State', coder.typeof(1, [10 1], [1 0]), ...
    'StateCovariance', coder.typeof(1, [10 10], [1 1]), ...
    'StateParameters', struct, ...
    'ObjectClassID', double(0), ...
    'TrackLogic', 'History', ...
    'TrackLogicState', coder.typeof(false, [1 10], [0 1]), ...
    'IsConfirmed', false, ...
    'IsCoasted', false, ...
    'IsSelfReported', false, ...
    'ObjectAttributes', struct);

localTracks = coder.typeof(oneLocalTrack, [8 1], [1 0]);
fuserInputArguments = {localTracks, time};

codegen heterogeneousInputsFuser -args fuserInputArguments;

Выполнение примера с сгенерированным кодом

Созданный код выполняется так же, как и код MATLAB, но сначала необходимо повторно инициализировать сценарий, объекты GOSPA и просмотр.

Вы используете toStruct функция объекта для преобразования входных дорожек в массивы структур.

Примечания:

  1. Если входные дорожки используют различные типы данных для State и StateCovariance свойства, убедитесь, что State и StateCovariance всех дорожек к типу данных, выбранному при определении oneLocalTrack структура выше.

  2. Если для входных дорожек требуется структура супернабора для полей StateParameters или ObjectAttributesперед вызовом mex файл.

Вы используете gospaCG переменная для сохранения метрик GOSPA для этого прогона, чтобы можно было сравнить их со значениями GOSPA из прогона MATLAB.

% Rerun the scenario with the generated code
fuserStepped = false;
fusedTracks = objectTrack.empty;
gospaCG = zeros(3,0);
missedTargetsCG = zeros(3,0);
falseTracksCG = zeros(3,0);

idx = 1;
clear heterogeneousInputsFuser_mex
reset(display);
reset(gospaRadar);
reset(gospaLidar);
reset(gospaCentral);
restart(scenario);
while advance(scenario)
    time = scenario.SimulationTime;
    localTracks = localTracksCollection{idx};
    
    if ~isempty(localTracks) || fuserStepped
        fusedTracks = heterogeneousInputsFuser_mex(toStruct(localTracks),time);
        fuserStepped = true;
    end
    
    radarTracks = localTracks([localTracks.SourceIndex]==1);
    lidarTracks = localTracks([localTracks.SourceIndex]==2);
    
    % Capture GOSPA and its components for all trackers
    [gospaCG(1,idx),~,~,~,missedTargetsCG(1,idx),falseTracksCG(1,idx)] = gospaRadar(radarTracks, groundTruth);
    [gospaCG(2,idx),~,~,~,missedTargetsCG(2,idx),falseTracksCG(2,idx)] = gospaLidar(lidarTracks, groundTruth);
    [gospaCG(3,idx),~,~,~,missedTargetsCG(3,idx),falseTracksCG(3,idx)] = gospaCentral(fusedTracks, groundTruth);
    
    % Update the display
    display(scenario,[],[], radarTracks,...
        [],[],[],[], lidarTracks, fusedTracks);
    
    idx = idx + 1;
end

В конце прогона необходимо проверить, что созданный код дал те же результаты, что и MATLAB код. Используя метрики GOSPA, собранные в обоих прогонах, можно сравнить результаты на высоком уровне. Из-за численных округлений могут быть небольшие различия в результатах сгенерированного кода относительно MATLAB код. Чтобы сравнить результаты, используйте абсолютные различия между значениями GOSPA и проверьте, все ли они меньше 1e-10. Результаты показывают, что различия очень малы.

% Compare the GOSPA values from MATLAB run and generated code
areGOSPAValuesEqual = all(abs(gospa-gospaCG)<1e-10,'all');
disp("Are GOSPA values equal up to the 10th decimal (true/false)? " + string(areGOSPAValuesEqual))
Are GOSPA values equal up to the 10th decimal (true/false)? true

Резюме

В этом примере вы научились создавать код для алгоритма слияния на уровне дорожки, когда входные дорожки неоднородны. Вы научились определять trackFuser и его SourceConfigurations свойство для поддержки разнородных источников. Также вы научились определять входные данные во время компиляции и передавать их в файл mex во время выполнения.

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

Следующие функции используются метрикой GOSPA.

helperLidarDistance

Функция для вычисления нормированного расстояния между оценкой пути в радиолокационном состоянии-пространстве и назначенной истинностью земли.

function dist = helperLidarDistance(track, truth)

% Calculate the actual values of the states estimated by the tracker

% Center is different than origin and the trackers estimate the center
rOriginToCenter = -truth.OriginOffset(:) + [0;0;truth.Height/2];
rot = quaternion([truth.Yaw truth.Pitch truth.Roll],'eulerd','ZYX','frame');
actPos = truth.Position(:) + rotatepoint(rot,rOriginToCenter')';

% Actual speed and z-rate
actVel = [norm(truth.Velocity(1:2));truth.Velocity(3)];

% Actual yaw
actYaw = truth.Yaw;

% Actual dimensions.
actDim = [truth.Length;truth.Width;truth.Height];

% Actual yaw rate
actYawRate = truth.AngularVelocity(3);

% Calculate error in each estimate weighted by the "requirements" of the
% system. The distance specified using Mahalanobis distance in each aspect
% of the estimate, where covariance is defined by the "requirements". This
% helps to avoid skewed distances when tracks under/over report their
% uncertainty because of inaccuracies in state/measurement models.

% Positional error. 
estPos = track.State([1 2 6]);
reqPosCov = 0.1*eye(3);
e = estPos - actPos;
d1 = sqrt(e'/reqPosCov*e);

% Velocity error
estVel = track.State([3 7]);
reqVelCov = 5*eye(2);
e = estVel - actVel;
d2 = sqrt(e'/reqVelCov*e);

% Yaw error
estYaw = track.State(4);
reqYawCov = 5;
e = estYaw - actYaw;
d3 = sqrt(e'/reqYawCov*e);

% Yaw-rate error
estYawRate = track.State(5);
reqYawRateCov = 1;
e = estYawRate - actYawRate;
d4 = sqrt(e'/reqYawRateCov*e);

% Dimension error
estDim = track.State([8 9 10]);
reqDimCov = eye(3);
e = estDim - actDim;
d5 = sqrt(e'/reqDimCov*e);

% Total distance
dist = d1 + d2 + d3 + d4 + d5;
end

helperRadarDistance

Функция для вычисления нормированного расстояния между оценкой пути в радиолокационном состоянии-пространстве и назначенной истинностью земли.

function dist = helperRadarDistance(track, truth)
% Calculate the actual values of the states estimated by the tracker

% Center is different than origin and the trackers estimate the center
rOriginToCenter = -truth.OriginOffset(:) + [0;0;truth.Height/2];
rot = quaternion([truth.Yaw truth.Pitch truth.Roll],'eulerd','ZYX','frame');
actPos = truth.Position(:) + rotatepoint(rot,rOriginToCenter')';
actPos = actPos(1:2); % Only 2-D

% Actual speed
actVel = norm(truth.Velocity(1:2));

% Actual yaw
actYaw = truth.Yaw;

% Actual dimensions. Only 2-D for radar
actDim = [truth.Length;truth.Width];

% Actual yaw rate
actYawRate = truth.AngularVelocity(3);

% Calculate error in each estimate weighted by the "requirements" of the
% system. The distance specified using Mahalanobis distance in each aspect
% of the estimate, where covariance is defined by the "requirements". This
% helps to avoid skewed distances when tracks under/over report their
% uncertainty because of inaccuracies in state/measurement models.

% Positional error
estPos = track.State([1 2]);
reqPosCov = 0.1*eye(2);
e = estPos - actPos;
d1 = sqrt(e'/reqPosCov*e);

% Speed error
estVel = track.State(3);
reqVelCov = 5;
e = estVel - actVel;
d2 = sqrt(e'/reqVelCov*e);

% Yaw error
estYaw = track.State(4);
reqYawCov = 5;
e = estYaw - actYaw;
d3 = sqrt(e'/reqYawCov*e);

% Yaw-rate error
estYawRate = track.State(5);
reqYawRateCov = 1;
e = estYawRate - actYawRate;
d4 = sqrt(e'/reqYawRateCov*e);

% Dimension error
estDim = track.State([6 7]);
reqDimCov = eye(2);
e = estDim - actDim;
d5 = sqrt(e'/reqDimCov*e);

% Total distance
dist = d1 + d2 + d3 + d4 + d5;

% A constant penalty for not measuring 3-D state 
dist = dist + 3;
end