DeepFusionMOT 论文笔记

论文链接:[2202.12100] DeepFusionMOT: A 3D Multi-Object Tracking Framework Based on Camera-LiDAR Fusion with Deep Association

一. 简介

      一般来说,相机可以检测远程目标,并且能够准确获取目标的外观特征,而激光雷达则很难做到这一点;激光雷达可以获取目标的准确的空间信息,深度信息在 3D MOT 中尤为重要,但基于激光雷达的方法只有在物体靠近时才能开始跟踪。

    本文提出一种鲁棒、快速的基于相机-LIDAR 融合的 MOT 方法,实现了精度和速度的良好折中。本文设计了一种有效的深度关联机制,并将其嵌入所提出的 MOT 算法中。当目标距离较远且仅被相机检测到时,在 2D 域对目标进行跟踪,并利用目标出现在 LIDAR 场景中获取的 3D 信息对 2D 轨迹进行更新,实现 2D 和 3D 轨迹的平滑融合。

二. 问题陈述

  • 基于相机的 MOT 方法缺乏 3D 跟踪所需的深度信息;基于 LIDAR 的 MOT 方法由于缺乏像素信息无法准确跟踪远距离目标;现有的基于相机和 LIDAR 融合的跟踪方法设计了复杂度特征提取器,需要在 GPU 上运行,无法轻松实现实时应用。

  • 大多数方法在相机- LiDAR 融合过程中未能充分利用视觉数据与点云数据;通常,基于激光雷达的探测器检测到的物体被投影到图像上以进行信息提取。因此,对于图像中激光雷达传感器未检测到的物体,相应的像素信息会丢失。

三. 系统架构

  • 输入:基于相机的 2D 检测器和基于 LIDAR 的 3D 检测器分别用于获取图像域和 LIDAR 与目标的位置与运动信息,将 LIDAR 中的 3D 边界框投影至图像域变为 2D 边界框,计算与图像 2D 边界框的 IoU,基于此进行两者信息匹配融合;

  • 数据关联:1)LIDAR 和 相机同时检测到的目标优先与现有 3D 轨迹关联;2)第一级关联不匹配的3D轨迹与 LIDAR 检测目标进行关联;3)相机检测目标与 2D 轨迹进行关联;4)3D 轨迹投影到图像域并与 2D 轨迹融合;

四. 所提方法

4.1. 相机-LIDAR 融合

       2D 检测 $D_{2\text{d}}=(x_{\mathrm{c}},y_{\mathrm{c}},w,h)$,3D 检测 $D_{3\text{d}}=(x_{\mathrm{c}},y_{\mathrm{c}},w,h,l,\theta)$$D_{3\text{d}}$ 基于坐标变换投影至图像域获得 $D_{2\text{d}}^{3\text{d}}$。将 $D_{2\text{d}}$ 和 $D_{2\text{d}}^{3\text{d}}$ 基于 IoU 进行匹配得到:$D_{2\text{d}}^{only}$$D_{3\text{d}}^{only}$$D_{2\text{d}-3\text{d}}^{fused}$

4.2. 深度关联

  • 第一级关联 现有 3D 轨迹与 $D_{2\text{d}-3\text{d}}^{fused}$ 关联,使用损失函数如下:

  • 为解决检测与轨迹未重叠时 IoU=0 的问题,引入欧氏距离实现更稳健的数据关联。第一级关联得到结果为:$T_{\mathrm{m1}}^{3\mathrm{d}}$$T_{\mathrm{u1}}^{3\mathrm{d}}$$D_{\mathrm{u1}}^{3\mathrm{d}}$,将 $T_{\mathrm{m1}}^{3\mathrm{d}}$ 进行更新,$D_{\mathrm{u1}}^{3\mathrm{d}}$ 初始化为新的确认态轨迹,将 $T_{\mathrm{u1}}^{3\mathrm{d}}$ 移至下一级关联。

  • 第二级关联 第一级未关联轨迹 $T_{\mathrm{u1}}^{3\mathrm{d}}$$D_{3\text{d}}^{only}$ 进行关联。采用与第一级关联相同的损失函数,未匹配的检测 $D_{u2}^{only\_3\text{d}}$ 被初始化为未确认态轨迹。未匹配的轨迹 $T_{\mathrm{u2}}^{3\mathrm{d}}$ 设定为暂定态轨迹,只有连续 3 帧匹配成功才能转化为确认态。

  • 第三级关联 现有 2D 轨迹和 $D_{2\text{d}}^{only}$ 进行关联。

  • 第四级关联 未匹配的 3D 轨迹(包括二级关联中的 $T_{\mathrm{u2}}^{3\mathrm{d}}$ 以及暂定态轨迹)和第三级中的 2D 轨迹进行关联。将这些 3D 轨迹投影到图像域,得到相应的 2D 边界框,基于 IoU 进行匹配。一旦一个 2D 轨迹成功地与一个 3D 轨迹相关联,然后将这两者融合形成一个新的 3D 轨迹。(将 2D 轨迹的 ID、出现帧数、轨迹状态赋予 3D 轨迹,并删除  2D 轨迹)

4.3. 轨迹管理

     本文采用了[ 2 ]中的轨迹管理方法,但不同的是增加了一个新的轨迹状态- -重现。具体来说,当一条确认的轨迹被遮挡,进而无法与若干帧的任何检测相关联时,它就被视为一个再现的轨迹。如果该轨迹在后续连续帧(大于某一阈值)中不能关联,则认为该轨迹在传感器FOV中消失,该轨迹变为死轨迹。因此,在本文中,一条轨迹可能有四种状态,包括死亡、暂定、确认和重现。

五. 实验

5.1. 实验设置

  • 数据集:KITTI、nuScences;

  • 基线方法:BeyondPixels [7], mmMOT [1], FANTrack [28], AB3DMOT [23], JRMOT [3], MOTSFusion [31], GNN3DMOT [5], JMODT [26], Quasi-Dense [32], EagerMOT [8], LGM [12], DEFT [33] 和 QD-3DT [34];

  • 目标检测器

    1)KITTI:RRC —— 2D 检测;PointRCNN —— 3D 检测。此类检测用于 BeyondPixels [7], MOTSFusion [31], EagerMOT [8], AB3DMOT [23] 和 GNN3DMOT [5];

    2)nuScenens:Cascade RRC —— 2D 检测;CenterPoint —— 3D 检测

  • 评价指标:2D MOT 指标:CLEAR;HOTA;3D MOT 指标:AMOTA、sAMOTA;

5.2. 实验结果

  • 定量评估

  • 定性评估

  • 消融实验

    1)外观信息的影响:使用 DeepSORT 中的特征提取器提取外观信息,使用 VeRi 数据集[ 41 ] 进行训练。

    2)目标检测器的影响: 3D 检测器采用 PointRCNNPoint-GNN 进行比较,2D 检测器采用 RRCYolov3 进行比较。

六. 总结与展望

    改进之处:

  • LiDAR和视觉外观特征以及运动特征将被纳入到损失函数中,以增强关联鲁棒性;

  • 受[ 44 ]的启发,将GPS / IMU数据纳入其中,以补偿自车运动对目标的预测误差,并加入适当的检测滤波和细化,以提高检测的置信度;

  • 基于[ 44 ]中提出的概念,研究和设计更有效的轨迹管理机制。

import time import os import sys from media.sensor import * from media.display import * from media.media import * from time import ticks_ms from machine import FPIOA from machine import Pin from machine import UART sensor = None # PID控制器类 - 参数优化以提高响应速度 class PIDController: def __init__(self, kp, ki, kd, setpoint): self.kp = kp self.ki = ki self.kd = kd self.setpoint = setpoint self.prev_error = 0 self.integral = 0 self.last_output = 0 def compute(self, current_value): error = self.setpoint - current_value # P项 - 增加比例增益 p_term = self.kp * error # I项 - 限制积分项防止过冲 self.integral += error i_term = self.ki * self.integral # 积分限幅 i_term = max(-50, min(50, i_term)) # D项 - 增加微分增益 derivative = error - self.prev_error d_term = self.kd * derivative # 更新前次误差 self.prev_error = error # 计算输出 output = p_term + i_term + d_term # 限制输出范围 output = max(-100, min(100, output)) # 应用输出变化率限制 max_change = 30 # 最大输出变化率 if abs(output - self.last_output) > max_change: if output > self.last_output: output = self.last_output + max_change else: output = self.last_output - max_change self.last_output = output return output # 通信协议定义 HEADER = 0xAA FOOTER = 0x55 # 创建步进电机控制命令 def create_stepper_command(axis, direction, steps): """ 创建步进电机控制命令 axis: 0-X轴, 1-Y轴 direction: 0-正转, 1-反转 steps: 步数 (0-65535) """ cmd = bytearray(6) cmd[0] = HEADER cmd[1] = 0x01 # 命令类型: 步进电机控制 cmd[2] = (axis << 4) | direction cmd[3] = (steps >> 8) & 0xFF # 高字节 cmd[4] = steps & 0xFF # 低字节 cmd[5] = FOOTER return cmd # 发送云台控制命令 - 增加响应速度 def send_stepper_command(uart, axis, direction, steps): # 限制最小步数,避免微小的抖动 if steps < 5: return cmd = create_stepper_command(axis, direction, steps) uart.write(cmd) # print(f"发送命令: X={axis}, 方向={direction}, 步数={steps}") try: # 1. 初始化显示和媒体管理器 print("初始化显示...") Display.init(Display.ST7701, to_ide=True, width=800, height=480) MediaManager.init() print("显示初始化完成") # 2. 初始化UART通信 print("初始化UART...") fpioa = FPIOA() fpioa.set_function(11, FPIOA.UART2_TXD) fpioa.set_function(12, FPIOA.UART2_RXD) uart = UART(UART.UART2, 115200, 8, 0, 1, timeout=1000) print("UART初始化完成") # 3. 初始化摄像头 print("初始化摄像头...") sensor = Sensor(width=640, height=480) sensor.reset() sensor.set_framesize(width=640, height=480) sensor.set_pixformat(Sensor.RGB565) # 4. 启动摄像头 print("启动摄像头...") sensor.run() print("摄像头启动完成") # 5. 设置窗口 - 增大窗口尺寸 window_roi = (0, 0, 640, 480) clock = time.clock() # 颜色阈值 thr_write = (60, 100, -20, 20, -20, 20) # 白色区域阈值 thr_black = (0, 35, -20, 20, -20, 20) # 黑色边框阈值 # 状态变量 target_offset = [-1, -1] # 目标补偿量 target = [320, 240] # 目标坐标 (初始化为中心) prev_target = [320, 240] # 上一次目标坐标 view_offset = [0, 0] # 视角偏移补偿量 deviation = [0, 0] # 最终输出结果 # 创建PID控制器 - 参数优化 pid_x = PIDController(kp=0.8, ki=0.005, kd=0.2, setpoint=320) # 增加Kp, 减少Ki pid_y = PIDController(kp=0.8, ki=0.005, kd=0.2, setpoint=240) # 增加Kp, 减少Ki # 目标丢失计数器 target_lost_count = 0 MAX_TARGET_LOST = 10 # 允许连续丢失的最大帧数 # 绘制UI元素 def draw_ui(img): """绘制用户界面元素""" # 绘制中心十字线 img.draw_line(0, 240, 640, 240, color=(0, 255, 0)) img.draw_line(320, 0, 320, 480, color=(0, 255, 0)) # 绘制目标点 img.draw_cross(int(target[0]), int(target[1]), color=(255, 0, 0), size=20, thickness=2) # 绘制偏差指示器 img.draw_circle(320 + int(deviation[0]), 240 + int(deviation[1]), 5, color=(255, 0, 0), thickness=2, fill=True) # 显示帧率 fps = clock.fps() img.draw_string(10, 10, f"FPS: {fps:.1f}", color=(255, 0, 0)) # 显示控制信息 img.draw_string(10, 30, f"Dev X: {deviation[0]:.1f}, Y: {deviation[1]:.1f}", color=(255, 0, 0)) # 显示通信状态 img.draw_string(10, 50, "MSPM0G3507 Connected", color=(0, 255, 0)) # 显示目标状态 status = "Target Found" if target_lost_count < MAX_TARGET_LOST else "Target Lost" color = (0, 255, 0) if status == "Target Found" else (255, 0, 0) img.draw_string(10, 70, f"Status: {status}", color=color) print("进入主循环...") # 主循环 while True: clock.tick() os.exitpoint() start_time = ticks_ms() # 获取图像 img = sensor.snapshot(chn=CAM_CHN_ID_0) # 使用完整图像,不再裁剪 # img = img.copy(roi=window_roi) # 寻找黑色边框 - 优化搜索参数 black_blobs = img.find_blobs([thr_black], pixels_threshold=200, area_threshold=200) target_found = False if black_blobs: # 如果找到黑色边框 for single_black_blob in black_blobs: # 检查黑色区域的填充率 fill_ratio = single_black_blob.pixels() / (single_black_blob.w() * single_black_blob.h()) if fill_ratio < 0.3: # 填充率低的区域可能是边框 # 在黑色区域内寻找白色区域 write_blobs = img.find_blobs([thr_write], roi=single_black_blob.rect(), pixels_threshold=50, area_threshold=2) if write_blobs: # 如果找到白色区域 # 找到最大的白色区域 largest_white = max(write_blobs, key=lambda b: b.area()) # 检查白色区域与黑色区域的关系 white_black_ratio = largest_white.pixels() / single_black_blob.pixels() center_diff_x = abs(largest_white.cx() - single_black_blob.cx()) center_diff_y = abs(largest_white.cy() - single_black_blob.cy()) # 验证是否满足目标条件 if (2 < white_black_ratio < 4) and center_diff_x < 20 and center_diff_y < 20: # 计算目标位置 target[0] = largest_white.cx() + target_offset[0] target[1] = largest_white.cy() + target_offset[1] target_found = True target_lost_count = 0 # 重置丢失计数器 # 在目标周围绘制矩形 img.draw_rectangle(single_black_blob.rect(), color=(0, 255, 0), thickness=2) img.draw_rectangle(largest_white.rect(), color=(255, 0, 0), thickness=2) break # 找到目标后退出循环 # 目标丢失处理 if not target_found: target_lost_count += 1 if target_lost_count > MAX_TARGET_LOST: # 长时间丢失目标,重置位置到中心 target[0] = 320 target[1] = 240 # 重置PID积分项 pid_x.integral = 0 pid_y.integral = 0 else: # 短期丢失,使用上一次的位置 target[0] = prev_target[0] target[1] = prev_target[1] else: prev_target[0] = target[0] prev_target[1] = target[1] # 更新视角偏移 - 增加更新速度 view_offset[0] += round((target[0] - 320) * 0.4) # 从0.2增加到0.4 view_offset[1] += round((target[1] - 240) * 0.4) # 从0.2增加到0.4 # 限制视角偏移范围 view_offset[0] = min(300, max(0, view_offset[0])) view_offset[1] = min(200, max(0, view_offset[1])) # 计算最终偏差 deviation[0] = target[0] + view_offset[0] - 320 deviation[1] = target[1] + view_offset[1] - 240 # 使用PID计算云台控制量 control_x = pid_x.compute(target[0]) control_y = pid_y.compute(target[1]) # 转换为步进电机控制命令 # 减小死区阈值,增加灵敏度 if abs(control_x) > 3: # 死区控制 (从5减小到3) direction_x = 0 if control_x > 0 else 1 steps_x = int(abs(control_x) * 8) # 增加比例因子 (从5增加到8) send_stepper_command(uart, axis=0, direction=direction_x, steps=steps_x) if abs(control_y) > 3: # 死区控制 (从5减小到3) direction_y = 0 if control_y > 0 else 1 steps_y = int(abs(control_y) * 8) # 增加比例因子 (从5增加到8) send_stepper_command(uart, axis=1, direction=direction_y, steps=steps_y) # 更新窗口位置 # new_x = 320 + view_offset[0] # new_y = 240 + view_offset[1] # window_roi = (new_x, new_y, 240, 240) # 绘制UI draw_ui(img) # 显示图像 - 简化显示流程 try: # 压缩图像用于显示 img.compress_for_ide() # 显示图像 Display.show_image(img, x=(800-img.width())//2, y=(480-img.height())//2) except Exception as e: print(f"显示错误: {e}") # 尝试重新初始化显示 try: Display.deinit() Display.init(Display.ST7701, to_ide=True, width=800, height=480) print("显示重新初始化成功") except Exception as e2: print(f"显示重新初始化失败: {e2}") # 计算处理时间 process_time = ticks_ms() - start_time fps = clock.fps() if process_time > 0: real_fps = 1000 / process_time print(f"用时: {process_time}ms, 实时帧速: {real_fps:.1f}fps, 平均帧速: {fps:.1f}fps") else: print(f"用时: {process_time}ms, 平均帧速: {fps:.1f}fps") # 接收来自MSPM0G3507的响应 if uart.any(): response = uart.read() print(f"收到响应: {response}") except KeyboardInterrupt as e: print("用户停止: ", e) except Exception as e: print(f"异常: {e}") # 打印异常详细信息 import sys sys.print_exception(e) finally: print("清理资源...") if 'sensor' in globals() and isinstance(sensor, Sensor): try: sensor.stop() except: pass if 'Display' in globals(): try: Display.deinit() except: pass os.exitpoint(os.EXITPOINT_ENABLE_SLEEP) time.sleep_ms(100) if 'MediaManager' in globals(): try: MediaManager.deinit() except: pass print("程序退出") 这是K230的代码,但他的反应速度太慢了,刚开始的帧率很低,帮我修改解决一下
08-01
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值