使用GNN进行对象跟踪
在现代世界中,目标跟踪任务具有广泛的应用,从自动驾驶汽车和视频监控系统到雷达和传感器监控。
该领域的关键问题之一是测量与轨迹的关联,特别是在不确定性,噪声和轨迹可能交叉的条件下。
我们将考虑经典GNN跟踪算法的实现。
呈现的代码包括以下步骤。
- 建模对象沿双曲线轨迹的运动。
- 用于预测和更新轨道状态的卡尔曼滤波器的实现。
- 以动画图形的形式可视化结果。
其主要目的是证明处理噪声测量和构建运动物体轨迹的算法的有效性,在这种情况下,物体沿着双曲线轨迹运动。
接下来,让我们继续讨论算法,并从声明对象跟踪系统的数据结构和常量开始。
- 轨迹是一种可修改的结构,用于存储对象的状态(坐标,速度),协方差矩阵(不确定性),轨迹的年龄,错过的测量次数以及用于可视化的状态历史。
- 检测是用于存储测量(坐标和时间)的不可变结构。
- 从状态向量中提取位置分量(x,y)的2×4观察矩阵H。
这些结构构成了算法实现的基础。
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]; # Матрица наблюдений
接下来,我们来看看卡尔曼滤波器用于跟踪的实现。
- *预测!*基于恒定速度(CV)模型更新轨道状态,预测新位置并增加不确定性(协方差)。
- *更新!*通过使用卡尔曼增益矩阵(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算法。 算法本身由以下部分组成。
- 预测:复制和预测所有曲目的新状态(预测!).
- 距离计算:基于Mahalanobis统计距离构建轨道和测量之间的距离矩阵。
- 贪婪匹配:通过检查阈值来查找具有最小距离的轨道维对,并排除已经匹配的轨道/维。
- 更新和过滤:按映射维度更新轨道(更新!),并且还删除错过>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函数可视化跟踪过程。 该函数创建一个可视化,允许您将算法的操作与动力学中的实际值进行比较,并包含以下逻辑。
- 蓝点(散射)-电流测量(检测)。
- 蓝色虚线表示对象(obj1,obj2)的真实轨迹。
- 红色方块代表具有历史记录(实线)的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_trajections函数生成两个对称双曲线轨迹,即它创建一个均匀分布的参数t从-3到3的范围,之后它计算两个双曲线的点的坐标。
第一: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
现在让我们模拟和可视化双曲线轨迹跟踪。
仿真周期包括以下步骤。
- 在每一步将高斯噪声添加到实位置的算法。
- 更新GNN跟踪器并通过plot_tracks可视化当前状态。
- 将最后一帧保存为PNG图像,并创建整个跟踪过程的动画GIF(5帧/秒)。
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)
结论
结果:
- 仿真的最后一步表明,该算法成功地跟踪了两个对象,尽管测量中增加了噪声。
- 轨迹(红线)的轨迹接近物体的真实轨迹(蓝色虚线),这证实了算法的准确性。 与此同时,我们看到最大的不准确之处正是在轨迹接触的那一刻。
- 价值
LIIar: 50-Mahalonobis距离的阈值表示算法对误报的抵抗力。
using Images
println("Последний шаг моделирования:")
img = load("last_frame.png")
所提出的算法证明了非线性轨迹跟踪对象的高效性. 主要优点是:
*卡尔曼滤波器的抗噪声性;
*易于实施GNN方法进行测量比较;
*可视化,可以很容易地解释结果。
该算法成功地解决了跟踪问题,可应用于自主系统、视频监控和机器人等领域。 为了进一步改进算法,我们可以考虑使用更复杂的运动模型(例如,恒定加速度模型)或JPDA或MHT等跟踪方法在高密度环境下工作。

