Main Content

Maritime Target Tracking Using Radar Under Rough Sea Conditions

Since R2025a

This example demonstrates how to track targets in a maritime environment under rough sea conditions using a mechanically rotating radar. You will simulate target measurements and sea clutter in these challenging conditions with a statistical radar model. Additionally, you will learn how to define suitable models for specifying boats and marine radar for the tracking algorithm.

Introduction

The sea surface, particularly under rough conditions, reflects radar signals back strongly towards the radar, resulting in a cluttered background that can obscure the detection of actual targets. The intensity of sea clutter is significantly higher at shorter ranges due to lower path loss and relatively larger grazing angles between the sea surface and the radar beam. This clutter can easily be confused as boats with a smaller radar cross section (RCS). By adjusting the radar gain parameters, one can reduce sea clutter during detection, but this is often balanced with the risk of missing the detection of small boats at close ranges. In this example, you will learn how to use a tracker to monitor and track small boats as they approach the radar in rough sea conditions.

Setup Scenario

To simulate a maritime environment, use the radarScenario class to set up a scenario. Set the UpdateRate to 0, allowing the sensors to control the simulation rate.

rng('default'); % Reproducible results

% Create a scenario
scene = radarScenario('UpdateRate',0,'StopTime',200);

Next, add a sea surface with a radius of 5 kilometers is specified around the origin, simulating rough sea conditions with a sea state of 8. For further details on sea surface modeling, refer to the Maritime Radar Sea Clutter Modeling (Radar Toolbox) example.

% Add sea surface
refl = surfaceReflectivitySea('SeaState',8,'Speckle','Weibull');
srf = seaSurface(scene,'RadarReflectivity',refl,'Boundary',[-5e3 5e3;-5e3 5e3]);

Add a platform to the scenario to represent the ownship or host boat, on which the radar is mounted.

ownship = platform(scene);

Define a mechanically rotating radar rotating at 20 revolutions per minute. The radar has an instantaneous field of view of 10 degrees in azimuth and 25 degrees in elevation. The radar is mounted 24 meters above the sea surface, aligned parallel to ownship. Set the FalseAlarmRate to 1e-3 to detect targets even with small signal-to-noise ratio (SNR). Set HasFalseAlarms to false to only simulate false returns from the sea clutter. For more details on the statistical radar model, refer to the algorithms section of radarDataGenerator (Radar Toolbox) class.

radar = radarDataGenerator('SensorIndex', 1, ...
    'UpdateRate', 12, ...
    'ScanMode','Mechanical',...
    'MountingLocation', [0 0 24], ...
    'MountingAngles', [0 0 0], ...
    'FieldOfView', [10 25], ...
    'RangeLimits', [500 5e3],...
    'HasFalseAlarms',false,...
    'HasNoise',true,...
    'HasElevation',false,...
    'HasRangeRate',false,...
    'FalseAlarmRate', 1e-3, ...
    'DetectionProbability',0.98,...
    'ReferenceRCS',10,...
    'ReferenceRange', 3e3, ...
    'DetectionCoordinates', 'Body', ...
    'AzimuthResolution', 4, ...
    'AzimuthBiasFraction',1,...
    'RangeBiasFraction',1,...
    'RangeResolution', 10, ...
    'MaxAzimuthScanRate',360);

Attach the radar to the ownship boat.

ownship.Sensors = radar;

Enable clutter generation from the sea surface using the clutterGenerator (Radar Toolbox) function with the radar. The UseBeam option is set to true to simulate clutter within the radar's field of view, as defined in the previous step.

clt = clutterGenerator(scene,radar,'Resolution',100,'UseBeam',true);

Simulate Clutter Measurements

To observe the clutter measurements from the sea surface reported by the radar, run the scenario for a few seconds without any targets. This helps to understand how the radar interacts with the sea to generate clutter measurements distributed across the entire radar coverage. A helper function, createDisplay, defined in the Supporting Functions section is used to visualize the radar measurements and coverage.

% Create display for plotting
display = createDisplay(scene);
restart(scene);

% Run the scenario for 10 scans
detBuffer = {};
nScans = 10;
while advance(scene) && scene.SimulationTime < (nScans-1)*3
    detections = detect(scene);
    detBuffer = [detBuffer;detections];
    updateDisplay(display, scene, detBuffer);
end

Figure contains an axes object. The axes object with xlabel X (m), ylabel Y (m) contains 6 objects of type line. One or more of the lines displays its values using only markers These objects represent Radar Detections, Ground Truth, Trajectories, Tracks, (history).

Notice in the image above that there is heavy clutter near the radar and the rest of the field of view seems uniformly cluttered.

Clutter Distribution

Next, quantitatively inspect this distribution using the simulated sensor data recorded in the first 10 scans of the radar.

% Compute azimuth and range of measurements
x = cellfun(@(x)x.Measurement(1),detBuffer);
y = cellfun(@(x)x.Measurement(2),detBuffer);
r = sqrt(x.^2 + y.^2);
az = atan2d(y, x);

Plot the distribution of clutter measurements as a function of its range. Notice that the distribution confirms the visual interpretation that the number of clutter measurements reduce with range.

% Plot histogram of range measurements
figure;

% Range bins to evaluate distribution
rBins = 500:100:5000;

% Number of false alarms per bin
nFalse = histcounts(r, rBins);

% Per scan
nFalse = nFalse/nScans;

% Plot distribution
histogram('BinCounts',nFalse,'BinEdges',rBins);
xlabel('Range bins (m)');
ylabel('Number of false alarms');

Figure contains an axes object. The axes object with xlabel Range bins (m), ylabel Number of false alarms contains an object of type histogram.

Next, you fit a function to define the clutter density of the radar as a function of range. Clutter density is defined as the number of false alarms per unit measurement volume. Clutter density is an important parameter used by multi-object trackers to compute data association between potential targets and measurements from the radar. In the context of radars reporting azimuth and range measurements, the volume of the sensor is defined in azimuth-range space with units of deg-meters. Clutter density is often assumed uniform or constant in the entire measurement space, however, it can also be defined as a function of measurement to inform the tracker about high or low clutter regions. The fitted function will be reused in the next section, when you define the sensor specification for tracking. In practice, this clutter map can be obtained from prior information about the scenario or can be estimated online by collecting sensor data over a few scans.

% Compute density of clutter
binVolume = diff(rBins)*360; % Volume in azimuth-range = dR*dAz

% Clutter density from the recorded data
Kc = nFalse./binVolume; % Density = Number per unit volume

% Range at middle of bins
meanRange = rBins(1:end-1)/2 + rBins(2:end)/2;

% Fit a function for Kc(r) = C0 + C1/r^5;
C0 = mean(Kc(end-20:end));
C1 = meanRange(1)^5*(Kc(1) - C0);

% Actual density
plot(meanRange, Kc, 'LineWidth',2);
hold on;

% Fitted density
Kcest = C0 + C1./meanRange.^5;
plot(meanRange, Kcest, 'LineWidth',2);
xlabel('Range (m)');
ylabel('Clutter intensity (K_c)');
legend('Recording','Fitted');

Figure contains an axes object. The axes object with xlabel Range (m), ylabel Clutter intensity (K indexOf c baseline ) contains 2 objects of type line. These objects represent Recording, Fitted.

Set Up the Tracker

In this section, set up a task-oriented joint probabilistic data association tracker to track targets in the presence of non-uniform clutter in the radar scan. For more details on how to use the task-oriented tracker, refer to the documentation for the JIPDATracker.

Define target specification

Use the trackerTargetSpec to create a specification for the target used by the tracker.

targetSpec = trackerTargetSpec('custom');
disp(targetSpec);
  CustomTarget with properties:

    StateTransitionModel: []
           SurvivalModel: []

Choose a constant-velocity motion model to define the motion of the targets using the targetStateTransitionModel. Set the number of motion dimensions to 2 to estimate the position, and velocity of the boats in 2-D. Modify the velocity and acceleration variance to tune the constant-velocity model assuming maximum speed of 25 m/s and max acceleration of 5 m/s^2 for the boats.

targetSpec.StateTransitionModel = targetStateTransitionModel('constant-velocity');
targetSpec.StateTransitionModel.NumMotionDimensions = 2;
targetSpec.StateTransitionModel.VelocityVariance = 25^2/3*eye(2); % maxSpeed^2/3
targetSpec.StateTransitionModel.AccelerationVariance = 3^2/3*eye(2); % maxAccel^2/3

To define the survival model, assume that the targets have uniform probability to survive within the field of view between radar observations.

targetSpec.SurvivalModel = targetSurvivalModel('uniform-survival-rate');
disp(targetSpec);
  CustomTarget with properties:

    StateTransitionModel: [1×1 ConstantVelocityModel]
           SurvivalModel: [1×1 UniformSurvivalRateModel]

Define sensor specification

Use the trackerSensorSpec function to create a specification for the radar used in this example. Set the maximum number of measurements to 75 based on the average number of false alarms produced per step.

% Create a sensor spec
sensorSpec = trackerSensorSpec('custom');
sensorSpec.MaxNumMeasurements = 75;

disp(numel(detBuffer)/nScans);
   58.6000
disp(sensorSpec);
  CustomSensor with properties:

    MaxNumMeasurements: 75

      MeasurementModel: []
    DetectabilityModel: []
          ClutterModel: []
            BirthModel: []

          UpdateModels: 0

Next, create all the models required by the sensor specification for this sensor.

Use a azimuth-range measurement model for the sensor. In the azimuth-range space, the measurement uncertainty or noise of the detections can be specified as a constant. Use the resolution of the radar to define the uncertainty in the measurement.

% By default, the radar is mounted at the origin.
sensorSpec.MeasurementModel = sensorMeasurementModel('azimuth-range');
sensorSpec.MeasurementModel.AzimuthVariance = radar.AzimuthResolution^2;
sensorSpec.MeasurementModel.RangeVariance = radar.RangeResolution^2;

To create the detectability model, assume that the radar can detect targets with uniform probability of 0.98 in the entire field of view.

sensorSpec.DetectabilityModel = sensorDetectabilityModel('uniform');
sensorSpec.DetectabilityModel.DetectionProbability = radar.DetectionProbability;

To create the clutter model, use a non-uniform clutter model. This model allows to specify variable clutter density as a function of measurement. The ModelData allows to use any additional information that is required for computing the clutter density. For this example, use the coefficients of the fitted model from the Clutter Distribution section as ModelData to be used during computation of clutter density.

sensorSpec.ClutterModel = sensorClutterModel('nonuniform-poisson');
sensorSpec.ClutterModel.ClutterDensityFcn = @computeClutterDensity;
sensorSpec.ClutterModel.ModelData = struct('Coefficients',[C0 C1]);

function Kc = computeClutterDensity(z, modelData)
    % z is the measurement. 
    % z = [azimuth;range];
    % Kc is the clutter density
    r = z(2);
    Co = modelData.Coefficients(1);
    C1 = modelData.Coefficients(2);
    Kc = Co + C1/r^5;
end

Similar to clutter density, the tracker also needs a birth density to define the likelihood of new targets appearing in the field of view. To account for a high clutter density inside the 1 Km radius and the assuming that new targets don't get detected first time inside that region, set the birth density low enough to avoid creating new tracks inside that region. This is required to maintain the computational and memory requirements of a JIPDA tracker.

% Create uniform birth model
sensorSpec.BirthModel = sensorBirthModel('uniform-poisson');

% Detection probability of the sensor
Pd = sensorSpec.DetectabilityModel.DetectionProbability;

% Clutter density at r=1000
Kc1000 = C0 + C1/1000^5;

% 1/10 of clutter density
beta = 0.1*Kc1000;

% Set the constant birth density
sensorSpec.BirthModel.BirthDensity = beta;

disp(sensorSpec);
  CustomSensor with properties:

    MaxNumMeasurements: 75

      MeasurementModel: [1×1 AzimuthRangeModel]
    DetectabilityModel: [1×1 UniformDetectabilityModel]
          ClutterModel: [1×1 NonUniformPoissonModel]
            BirthModel: [1×1 UniformPoissonModel]

          UpdateModels: 0

Define the JIPDA tracker

In this section, configure the JIPDA tracker to integrate target and sensor specifications defined in the previous step. The tracker uses the modeling information from the target and sensor specifications for common tasks such as prediction and data association.

tracker = multiSensorTargetTracker(targetSpec, sensorSpec, 'jipda');
tracker.MaxMahalanobisDistance = 8;
tracker.ConfirmationExistenceProbability = 0.98;
disp(tracker)
  fusion.tracker.JIPDATracker with properties:

                TargetSpecifications: {[1×1 CustomTarget]}
                SensorSpecifications: {[1×1 CustomSensor]}
              MaxMahalanobisDistance: 8
    ConfirmationExistenceProbability: 0.9800
        DeletionExistenceProbability: 0.1000

Understand sensor data format

Use the trackerSensorSpec function to understand the format of the data required by the tracker under the defined configuration. Notice that the tracker only needs the measurements as 2-element vector defined according the the azimuth-range measurement model. In addition, it also needs the timestamp of the observations.

sensorData = dataFormat(sensorSpec);
disp(sensorData)
    MeasurementTime: 0
       Measurements: [2×75 double]

Run Scenario

In this section, run the scenario with three boats added to the scenario. Simulate radar measurements in the presence of heavy sea clutter, and send those measurements to the tracker to track boats. The tracker is responsible to filter out clutter based on the clutter density and birth density provided by the sensor specification.

% Restart the scenario and add targets now
restart(scene);
addTargets(scene);

% Recreate display
display = createDisplay(scene);

% Initialize detection buffer for each scan
detBuffer = {};

% Run the scenario
while advance(scene)
    % Detections in current field of view
    [dets, config] = detect(scene);

    % Run the tracker when the sensor completes one scan
    if config.IsScanDone
        % Assemble sensor data from detection buffer
        sensorData = assembleData(sensorData, detBuffer);

        % Update tracker
        tracks = tracker(sensorData);

        % Display
        updateDisplay(display, scene, detBuffer, tracks);

        % Empty buffer
        detBuffer = {};
    else
        % Add to the buffer
        detBuffer = [detBuffer;dets];
        
        updateDisplay(display, scene, detBuffer);
    end
    
    if abs(scene.SimulationTime - 119) < 0.05
        snap = zoomAndSnap(display);
    end
end

Figure contains an axes object. The axes object with xlabel X (m), ylabel Y (m) contains 9 objects of type line, text. One or more of the lines displays its values using only markers These objects represent Radar Detections, Ground Truth, Trajectories, Tracks, (history).

Results

In the image above, you can visualize the results of the tracker after the scenario has ended. Notice that the tracker is able to maintain a track on all three targets. In addition, the tracker was able to filter out all the sea clutter and only confirmed one false track during the scenario. Notice, when the boat reaches near the clutter region, the data association between measurements and target estimate is significantly more ambiguous than outside the clutter region. The JIPDA tracker handles this ambiguity by considering multiple possible data associations and representing the uncertainty in data association in the estimate of the target state.

Notice in the image below zoomed in at 1 kilometer radius that the track had a higher uncertainty in the position estimate in the clutter region, represented by the orange ellipse around the track.

figure;
imshow(snap.cdata);

Figure contains an axes object. The hidden axes object contains an object of type image.

Summary

In this example, you learned how to simulate radar clutter from sea surfaces in rough sea conditions using a statistical radar model and observed the challenges of tracking under such conditions. Additionally, you learned how to configure a JIPDA tracker to process the radar data and use appropriate clutter modeling to assist the tracker in filtering out false alarms and maintaining tracks under ambiguous situations close to heavy clutter.

Supporting Functions

assembleData

function sensorData = assembleData(sensorData, detBuffer)
% This function converts the objectDetection cell array to sensor data
% format required by the tracker.
x = cellfun(@(x)x.Measurement(1),detBuffer);
y = cellfun(@(x)x.Measurement(2),detBuffer);
az = atan2d(y,x);
r = sqrt(x.^2 + y.^2);
sensorData.MeasurementTime = detBuffer{end}.Time; % Time of report after the scan is complete
sensorData.Measurements = [az';r']; % Measurements
end

addTargets

function addTargets(scene)
% Create first midsize boat with RCs = 10
Boat = platform(scene);
Boat.Trajectory = waypointTrajectory( ...
    [1502 1951 0;1285 1480 0;1126 973 0;1126 350 0;981 -390 0;-454 -1390 0], ...
    'GroundSpeed', 20*ones(6,1));
Boat.Signatures{1} = rcsSignature(Azimuth=[-180 180],Elevation=[-90;90],Pattern=10*ones(2));

% Create second large boat with RCS = 25
Boat = platform(scene);
Boat.Trajectory = waypointTrajectory( ...
    [-2536 4062 0;-1573 4312 0;1324 4167 0;1831 3636 0], ...
    'GroundSpeed', [20;20;20;20]);
Boat.Signatures{1} = rcsSignature(Azimuth=[-180 180],Elevation=[-90;90],Pattern=25*ones(2));

% Create third small boat that reaches close with RCS = 0
Boat = platform(scene,'ClassID',4);
Boat.Trajectory = waypointTrajectory( ...
    [1699 -1364 0;724 -1051 0;-354 -616 0;-2408 -616 0;-3817 0 0], ...
    'GroundSpeed', 20*ones(5,1));
Boat.Signatures{1} = rcsSignature(Azimuth=[-180 180],Elevation=[-90;90],Pattern= 0*ones(2));

end

createDisplay

function display = createDisplay(scene)
% This function creates the display for visualizing tracks, detections, and
% ground truth. 

% Color order for plots
clrs = [1.0000    1.0000    0.0667
        0.0745    0.6235    1.0000
        1.0000    0.4118    0.1608
        0.3922    0.8314    0.0745
        0.7176    0.2745    1.0000
        0.0588    1.0000    1.0000
        1.0000    0.0745    0.6510];

% Create theater plot
f = figure('Units','normalized',...
           'Position',[0.1 0.1 0.6 0.8],...
           'Visible','on');

ax = axes(f);
display.TheaterPlot = theaterPlot('XLimits',[-5e3 5e3],...
                                  'YLimits',[-5e3 5e3],...
                                  'ZLimits',[-5e3 5e3],...
                                  'Parent',ax);

% Create a detection plotter
display.DetectionPlotter = detectionPlotter(display.TheaterPlot,...
    'DisplayName','Radar Detections',...
    'MarkerFaceColor',clrs(4,:),...
    'MarkerEdgeColor',clrs(1,:),...
    'Marker','o');

% Create a platform plotter for targets
display.PlatformPlotter = platformPlotter(display.TheaterPlot,...
    'DisplayName','Ground Truth',...
    'MarkerFaceColor',clrs(2,:));

% Create a trajectory plotter for targets and plot their trajectories
trp = trajectoryPlotter(display.TheaterPlot,...
    'DisplayName','Trajectories',...
    'Color',clrs(2,:),...
    'LineStyle','-',...
    'LineWidth',2);

% Plot trajectories during construction
t = linspace(0,scene.StopTime,100);
pos = cell(numel(scene.Platforms)-1,1);
for i = 1:numel(pos)
    pos{i} = lookupPose(scene.Platforms{i+1}.Trajectory,t);
end
trp.plotTrajectory(pos);

% Create a coverage plotter
display.CoveragePlotter = coveragePlotter(display.TheaterPlot,...
    'DisplayName','',...
    'Color',clrs(4,:),...
    'Alpha',[0.1 0]);

% Create a track plotter
display.TrackPlotter = trackPlotter(display.TheaterPlot,...
    'DisplayName','Tracks',...
    'MarkerFaceColor',clrs(3,:),...
    'MarkerEdgeColor',clrs(3,:),...
    'ConnectHistory','on');

ax = display.TheaterPlot.Parent;

hold on;

% Plot range lines on the PPI
az = linspace(0,360,100)';
r = 1000:1000:5000;
X = zeros(0,1);
Y = zeros(0,1);
for i = 1:numel(r)
    x = r(i).*cosd(az);
    y = r(i).*sind(az);
    X = [X;nan;x]; %#ok<AGROW>
    Y = [Y;nan;y]; %#ok<AGROW>
end

plot(ax,X,Y,'g-');

% Use dark theme colors
ax = display.TheaterPlot.Parent;
ax.Parent.Color = 0.157*[1 1 1];
ax.Color = [0 0 0];
grid(ax,'on');
ax.GridColor = 0.68*[1 1 1];
ax.GridAlpha = 0.4;
axis(ax,'equal');
ax.XColor = [1 1 1]*0.68;
ax.YColor = [1 1 1]*0.68;
ax.ZColor = [1 1 1]*0.68;

l = legend(ax);
l.Orientation = 'horizontal';
l.Location = 'northoutside';
end

function updateDisplay(display, scene, detections, tracks)
% This function updates the display for plotting ground truth, detections,
% and tracks. 

% Plot ground truth
poses = platformPoses(scene);
display.PlatformPlotter.plotPlatform(vertcat(poses.Position));

% Plot coverage
cvg = coverageConfig(scene);
display.CoveragePlotter.plotCoverage(cvg);

% Plot detections
if nargin > 2 && ~isempty(detections)
    x = cellfun(@(x)x.Measurement(1),detections);
    y = cellfun(@(x)x.Measurement(2),detections);
    z = zeros(size(x));
    display.DetectionPlotter.plotDetection([x y z]);
end

% Plot tracks
if nargin > 3 && ~isempty(tracks)
    for tr = 1:numel(tracks)
        tracks(tr).State = [tracks(tr).State;0;0];
        tracks(tr).StateCovariance = blkdiag(tracks(tr).StateCovariance,1,1);
    end
    [trkPos,trkPosCov] = getTrackPositions(tracks,'constvel');
    display.TrackPlotter.plotTrack(trkPos,trkPosCov,string([tracks.TrackID]));
end

drawnow;

end

function snap = zoomAndSnap(display)
display.TheaterPlot.Parent.XLim = [-1e3 1e3];
display.TheaterPlot.Parent.YLim = [-1e3 1e3];
snap = getframe(display.TheaterPlot.Parent.Parent);
display.TheaterPlot.Parent.XLim = [-5e3 5e3];
display.TheaterPlot.Parent.YLim = [-5e3 5e3];
end