使用 GNN 跟踪物体¶
在当今世界,物体跟踪(追踪)任务有着广泛的应用,从自动驾驶汽车和视频监控系统到雷达和传感器监测,不一而足。 这一领域的主要挑战之一是将测量结果与轨迹联系起来,尤其是在不确定、噪声和可能的轨迹交叉等条件下。
我们考虑采用经典的 GNN 跟踪算法。
所介绍的代码包括以下步骤。 1.建立物体沿双曲线轨迹运动的模型。 2.执行卡尔曼滤波器以预测和更新轨迹状态。 3.以动画图表的形式将结果可视化。
主要目的是展示该算法在处理噪声测量和构建运动物体轨迹(本例中为沿双曲线轨迹运动的物体)方面的有效性。
接下来,让我们进入算法,首先声明数据结构和物体跟踪系统的常数。
1.Track - 一个变量结构,用于存储物体状态(坐标、速度)、协方差矩阵(不确定性)、轨迹年龄、错过的测量次数以及可视化状态历史。 2.2. Detection - 用于存储测量数据(坐标和时间)的不可变结构。 H 大小为 2×4 的观测矩阵,用于从状态向量中提取位置分量(x,y)。
这些结构构成了算法实现的基础。
using LinearAlgebra
# Изменяемая структура трека
mutable struct Track
state::Vector{Float64} # Вектор состояния
covariance::Matrix{Float64} # Матрица ковариации
age::Int # Возраст трека (число обновлений)
missed::Int # Число пропущенных измерений
history::Vector{Vector{Float64}} # История состояний для отрисовки
end
# Добавляем history в конструктор Track
Track(state, covariance, age, missed) = Track(state, covariance, age, missed, [copy(state)])
# Измерение: [x, y, timestamp]
struct Detection
position::Vector{Float64}
time::Float64
end
const H = [1 0 0 0; 0 1 0 0]; # Матрица наблюдений
接下来,让我们考虑如何实现卡尔曼滤波器的跟踪功能。
1.预测!根据恒定速度(CV)模型更新轨迹状态,预测新位置并增加不确定性(协方差)。 2.update! 使用卡尔曼增益矩阵 (K) 通过测量修正轨迹状态,减少不确定性并保留历史变化。
# Функция предсказания состояния трека
function predict!(track::Track, dt::Float64)
F = [1 0 dt 0; 0 1 0 dt; 0 0 1 0; 0 0 0 1] # Матрица перехода (CV)
track.state = F * track.state
track.covariance = F * track.covariance * F' + diagm([0.1, 0.1, 0.01, 0.01])
return track
end
# Функция обновления трека на основе измерения
function update!(track::Track, detection::Detection)
y = detection.position - H * track.state
S = H * track.covariance * H' + diagm([0.5, 0.5])
K = track.covariance * H' / S
track.state += K * y
track.covariance = (I - K * H) * track.covariance
track.age += 1
track.missed = 0
push!(track.history, copy(track.state)) # Сохраняем историю
return track
end
下面我们来看看跟踪算法的功能。全局最近邻(GNN)是一种基于贪婪地将测量结果与最近轨迹相匹配的简单方法。在我们的代码中,trackerGNN 函数实现了匹配轨迹和测量值的 GNN 算法。算法本身由以下部分组成。 1.预测:复制并预测所有轨迹的新状态(predict!) 2.距离计算:根据 Mahalanobis 统计距离建立轨迹和测量值之间的距离矩阵。 3. 贪婪匹配:通过检查阈值(门)找到距离最小的轨迹-测量值对,并排除已匹配的轨迹/测量值。 4. 更新和过滤:更新匹配测量值上的轨迹(更新!),并删除缺少 >3 个测量值的轨迹。
function trackerGNN(detections::Vector{Detection}, existing_tracks::Vector{Track}, gate::Float64)
predicted = [predict!(deepcopy(t), 1.0) for t in existing_tracks] # Предсказание состояний
# Матрица расстояний
dist = zeros(length(predicted), length(detections))
for (i,t) in enumerate(predicted), (j,d) in enumerate(detections)
y = d.position - H*t.state
dist[i,j] = y' / (H*t.covariance*H') * y
end
dets_assigned = tracks_assigned = Int[] # Cопоставление
for _ in 1:min(length(predicted), length(detections))
i,j = argmin(dist).I
dist[i,j] > gate && break
push!(tracks_assigned, i)
push!(dets_assigned, j)
dist[i,:] .= dist[:,j] .= Inf
end
updated = deepcopy(predicted) # Обновление и фильтрация
foreach(((i,j),) -> update!(updated[i], detections[j]), zip(tracks_assigned, dets_assigned))
filter!(t -> t.missed < 3, updated)
end
plot_tracks 函数将跟踪过程可视化。该函数创建了一个可视化界面,可将算法性能与动态中的实际值进行比较,其中包括以下逻辑。 1.蓝点(散点图)--当前测量值(检测值)。 2.蓝色虚线--物体(obj1、obj2)的真实轨迹。 3.红色方框--GNN 历史轨迹(实线)。
function plot_tracks(tracks::Vector{Track}, detections::Vector{Detection}, obj1, obj2, step)
p = scatter(getindex.(d.position[1] for d in detections), getindex.(d.position[2] for d in detections), label="Detections", color=:blue, markersize=6)
# Реальные траектории
plot!(getindex.(p[1] for p in obj1[1:step]), getindex.(p[2] for p in obj1[1:step]), label="", color=:blue, linestyle=:dash)
plot!(getindex.(p[1] for p in obj2[1:step]), getindex.(p[2] for p in obj2[1:step]), label="", color=:blue, linestyle=:dash)
# Вспомогательная функция для отрисовки треков
function plot_tracks!(tracks, label, color, marker)
for (i, t) in enumerate(tracks)
scatter!([t.state[1]], [t.state[2]], label=i==1 ? label : "", color=color, markersize=8, marker=marker)
# Отрисовка истории трека
if length(t.history) > 1
plot!(getindex.(h[1] for h in t.history), getindex.(h[2] for h in t.history),
label="", color=color, linestyle=:solid, linewidth=2)
end
end
end
# Треки разных типов с историей
plot_tracks!(tracks, "GNN Track", :red, :square)
plot!(legend=:topleft, title="Шаг: $step", xlims=(-5, 5), ylims=(-5, 5))
end
generate_hyperbola_trajectories 函数生成两个对称双曲线轨迹,即创建一个均匀分布的-3 至 3 的参数 t 范围,然后计算两个双曲线的点坐标。
- 第一个:acosh(t), y = bsinh(t)
- 第二个:-acosh(t), y = bsinh(t)
这个函数非常适合在相交轨迹上测试跟踪算法。它可以创建逼真的测试轨迹,模拟物体沿双曲路径运动。
# Функция для генерации траекторий гипербол
function generate_hyperbola_trajectories(steps, a=1.0, b=1.0)
t = range(-3, 3, length=steps)
[(a*cosh(ti), b*sinh(ti)) for ti in t], [(-a*cosh(ti), b*sinh(ti)) for ti in t]
end
现在,让我们对双曲线轨迹跟踪进行模拟和可视化。
模拟周期包括以下步骤 1 算法每一步都会在实际位置上添加高斯噪声。 2 更新 GNN 跟踪器,并通过 plot_tracks 可视化当前状态。 3.3. 将最后一帧保存为 PNG 图像,并创建整个跟踪过程的 GIF 动画(5fps)。
steps = 50
obj1, obj2 = generate_hyperbola_trajectories(steps)
# Инициализация треков с историей
initial_state1 = [obj1[1][1], obj1[1][2], 0.5, 0.5]
initial_state2 = [obj2[1][1], obj2[1][2], -0.5, 0.5]
tracks_gnn = [Track(initial_state1, Matrix(1.0I, 4, 4), 1, 0),
Track(initial_state2, Matrix(1.0I, 4, 4), 2, 0)]
# Создаем GIF
anim = @animate for step in 1:steps
# Генерируем измерения для текущего шага
noise = 0.3(randn(4))
detections = [Detection([obj1[step][1] + noise[1], obj1[step][2] + noise[2]], step),
Detection([obj2[step][1] + noise[3], obj2[step][2] + noise[4]], step)]
# Обновляем трек
updated_tracks_gnn = trackerGNN(detections, deepcopy(tracks_gnn), 50.0)
# Обновляем исходные треки для следующего шага
global tracks_gnn = updated_tracks_gnn
# Визуализация
plot_tracks(tracks_gnn, detections, obj1, obj2, step)
if step == steps
savefig("last_frame.png")
end
end
# Сохраняем GIF
gif(anim, "tracking_simulation.gif", fps=5)
输出¶
结果:
1.最后一个模拟步骤显示,尽管在测量中增加了噪音,算法仍成功地跟踪了两个物体。
2. 跟踪轨迹(红线)接近于物体的真实轨迹(蓝色虚线),这证明了算法的准确性。同时,我们可以看到最大的误差恰恰出现在轨迹接触的时刻。
3.LIIar: 50
- Mahalonobis 距离的临界值表示算法对误报的稳定性。
using Images
println("Последний шаг моделирования:")
img = load("last_frame.png")
所提出的算法对于非线性轨迹的目标跟踪问题具有很高的效率。其主要优点如下
- 由于采用了卡尔曼滤波器,因此对噪声具有鲁棒性;
- 易于使用 GNN 方法对测量结果进行比较;
- 清晰的可视化便于解释结果。
该算法成功地解决了跟踪问题,可应用于自主系统、视频监控和机器人等领域。为了进一步改进该算法,可以考虑使用更复杂的运动模型(如恒定加速度模型)或 JPDA 或 MHT 等跟踪方法来处理高物体密度问题。