Engee 文档
Notebook

使用GNN进行对象跟踪

在现代世界中,目标跟踪任务具有广泛的应用,从自动驾驶汽车和视频监控系统到雷达和传感器监控。
该领域的关键问题之一是测量与轨迹的关联,特别是在不确定性,噪声和轨迹可能交叉的条件下。

我们将考虑经典GNN跟踪算法的实现。

呈现的代码包括以下步骤。

  1. 建模对象沿双曲线轨迹的运动。
  2. 用于预测和更新轨道状态的卡尔曼滤波器的实现。
  3. 以动画图形的形式可视化结果。

其主要目的是证明处理噪声测量和构建运动物体轨迹的算法的有效性,在这种情况下,物体沿着双曲线轨迹运动。

接下来,让我们继续讨论算法,并从声明对象跟踪系统的数据结构和常量开始。

  1. 轨迹是一种可修改的结构,用于存储对象的状态(坐标,速度),协方差矩阵(不确定性),轨迹的年龄,错过的测量次数以及用于可视化的状态历史。
  2. 检测是用于存储测量(坐标和时间)的不可变结构。
  3. 从状态向量中提取位置分量(x,y)的2×4观察矩阵H

这些结构构成了算法实现的基础。

In [ ]:
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. *更新!*通过使用卡尔曼增益矩阵(K)进行测量来调整轨道状态,减少不确定性并保持变化的历史记录。
In [ ]:
# Функция предсказания состояния трека
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
[ Info: Starting sender/receiver loops
Out[0]:
update! (generic function with 1 method)

让我们继续讨论跟踪算法函数。 全局最近邻(GNN)是一种基于贪婪地将测量结果匹配到附近轨道的简单方法。 在我们的代码中,trackerGNN函数实现了用于匹配轨道和测量的GNN算法。 算法本身由以下部分组成。

  1. 预测:复制和预测所有曲目的新状态(预测!).
  2. 距离计算:基于Mahalanobis统计距离构建轨道和测量之间的距离矩阵。
  3. 贪婪匹配:通过检查阈值来查找具有最小距离的轨道维对,并排除已经匹配的轨道/维。
  4. 更新和过滤:按映射维度更新轨道(更新!),并且还删除错过>3维的轨道。
In [ ]:
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
Out[0]:
trackerGNN (generic function with 1 method)

Plot_tracks函数可视化跟踪过程。 该函数创建一个可视化,允许您将算法的操作与动力学中的实际值进行比较,并包含以下逻辑。

  1. 蓝点(散射)-电流测量(检测)。
  2. 蓝色虚线表示对象(obj1,obj2)的真实轨迹。
  3. 红色方块代表具有历史记录(实线)的GNN轨道。
In [ ]:
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
Out[0]:
plot_tracks (generic function with 1 method)

Generate_hyperbola_trajections函数生成两个对称双曲线轨迹,即它创建一个均匀分布的参数t从-3到3的范围,之后它计算两个双曲线的点的坐标。
第一:acosh(t),y=bsinh(t)
第二:-acosh(t),y=b
sinh(t)

此功能非常适合在相交轨迹上测试跟踪算法。 它创建真实的测试轨迹,模拟对象沿双曲线路径的运动。

In [ ]:
# Функция для генерации траекторий гипербол
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
Out[0]:
generate_hyperbola_trajectories (generic function with 3 methods)

现在让我们模拟和可视化双曲线轨迹跟踪。

仿真周期包括以下步骤。

  1. 在每一步将高斯噪声添加到实位置的算法。
  2. 更新GNN跟踪器并通过plot_tracks可视化当前状态。
  3. 将最后一帧保存为PNG图像,并创建整个跟踪过程的动画GIF(5帧/秒)。
In [ ]:
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)
Out[0]:
No description has been provided for this image

结论

结果:

  1. 仿真的最后一步表明,该算法成功地跟踪了两个对象,尽管测量中增加了噪声。
  2. 轨迹(红线)的轨迹接近物体的真实轨迹(蓝色虚线),这证实了算法的准确性。 与此同时,我们看到最大的不准确之处正是在轨迹接触的那一刻。
  3. 价值 LIIar: 50 -Mahalonobis距离的阈值表示算法对误报的抵抗力。
In [ ]:
using Images
println("Последний шаг моделирования:")
img = load("last_frame.png")
Последний шаг моделирования:
Out[0]:
No description has been provided for this image

所提出的算法证明了非线性轨迹跟踪对象的高效性. 主要优点是:
*卡尔曼滤波器的抗噪声性;
*易于实施GNN方法进行测量比较;
*可视化,可以很容易地解释结果。

该算法成功地解决了跟踪问题,可应用于自主系统、视频监控和机器人等领域。 为了进一步改进算法,我们可以考虑使用更复杂的运动模型(例如,恒定加速度模型)或JPDA或MHT等跟踪方法在高密度环境下工作。