ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于ByteTrack与YOLOX的无人机航拍多目标跟踪系统实战解析

基于ByteTrack与YOLOX的无人机航拍多目标跟踪系统实战解析 简介多目标跟踪MOT是计算机视觉领域的重要研究方向在无人机航拍、智能交通与安防监控中有着广泛应用。传统方法常依赖表观特征或复杂网络结构但在目标尺度小、遮挡频繁、视角剧烈变化的航拍场景下检测与关联的稳定性面临严峻挑战。ByteTrack作为一种基于检测的跟踪框架通过分置信度二次匹配机制有效利用低分检测框显著提升了轨迹连续性。本文从VisDrone数据集预处理出发完整阐述了将原始标注转化为YOLO训练格式、训练YOLOX_S检测器、接入ByteTrack进行视频推理并采用MOTA、IDF1等指标评估的端到端流程。文章不仅分析了卡尔曼滤波参数调优、输入分辨率对小目标召回的影响还提供了密集场景下减少ID切换的工程技巧可为无人机视觉目标追踪与多目标跟踪任务提供可复现的实践参考。 我们直接进入正题。这套基于ByteTrack框架的无人机视觉目标追踪系统核心是把VisDrone数据集上的航拍多目标跟踪任务完整跑通从VisDrone原始标注转成YOLO训练格式到训练YOLOX_S检测器再到接入ByteTrack跟踪器做视频推理最后用MOTA、IDF1这些指标评估效果。整个链路我已经完整复现过这篇文章会把每一环的关键细节、参数选择原因、以及我踩过的坑全部写出来适合正在做无人机视角MOT、或者想把ByteTrack落到自有检测器上的同学参考。1. 项目整体设计与思路拆解1.1 无人机航拍与MOT的特殊挑战多目标跟踪MOT在无人机航拍场景下和普通道路监控完全是两码事。VisDrone数据集里的目标以行人、汽车、自行车、三轮车为主但绝大多数目标在画面里只占几十个像素属于典型的小目标检测问题。再加上无人机视角会带来剧烈的尺度变化目标从画面边缘飞到中心尺寸可能相差10倍以上镜头随无人机移动时背景也在整体漂移这给数据关联造成了很大干扰。另一个头疼点是目标密集。VisDrone里经常出现一个画面几十辆汽车挤在路口或者行人扎堆过马路的场景目标之间互相遮挡严重。传统的基于检测的跟踪方法一旦检测器漏检或者误检跟踪轨迹就容易断掉或者ID跳变。所以这个项目选择ByteTrack并不是偶然它提出的BYTE数据关联思想恰恰是针对这种“检测质量不稳定”的场景做了专门优化。1.2 为什么是ByteTrack而不是DeepSORT或JDE很多人一上来会问怎么不用DeepSORTDeepSORT的核心是“检测表观特征ReID”用一个额外的特征提取网络来区分不同目标。听起来很合理但实际在无人机场景下有个致命问题目标太小表观特征根本提不准。一个20x20像素的行人让ReID网络提取特征ID切换的概率非常高。而且ReID网络本身有推理开销还会拖慢整体速度。ByteTrack则完全放弃了表观特征只靠检测框的位置、尺寸和运动信息做关联。它的核心逻辑是高置信度的检测框优先关联低置信度的检测框不能直接丢弃而是参与二次匹配去召回那些被遮挡的、漏检的目标。这个设计非常契合无人机航拍的目标特点——检测器面对小目标、遮挡目标时输出置信度普遍偏低如果按传统做法一刀切掉低分框等于主动放弃了那些“还能救一救”的目标。JDE这类端到端方法虽然把检测和跟踪统一到一个网络里但训练复杂而且对数据标注要求更高。对于大多数工程落地场景ByteTrack这种“用现成检测器轻量跟踪器”的组合性价比是最高的。我不需要重新训练一个带跟踪头的网络只需要把我训练好的YOLOX_S接到ByteTrack上就能出结果。1.3 YOLOX_S作为检测器的取舍检测器选型上YOLOX_S是权衡过后的选择。VisDrone训练集有6471张图片目标类别10种但小目标占比高对检测器的特征提取能力要求不低。YOLOX_S相比YOLOX_Tiny有更强的特征表达能力又比YOLOX_M轻量在地面站的消费级GPU上能跑到实时或准实时。YOLOX在结构上做了几个对航拍目标很友好的设计解耦检测头把分类和回归分支分开能缓解小目标分类和定位互相干扰的问题Anchor-Free的检测方式省去了锚框调参对尺寸变化剧烈的航拍目标更省心Mosaic和MixUp数据增强能显著提升模型对小目标的泛化能力。这些都是我在实际对比后认为值得保留的卖点。2. 环境搭建与VisDrone数据预处理2.1 环境依赖清单我用的是PyTorch 1.10 CUDA 11.3的组合Python 3.8。YOLOX官方仓库对版本要求不算苛刻但建议PyTorch版本不要低于1.8否则一些算子比如SiLU激活的效率会受影响。pip install torch1.10.0 torchvision0.11.0 --extra-index-url https://download.pytorch.org/whl/cu113 pip install opencv-python tqdm loguru scikit-learn scipy thop测速和评估阶段还需要单独装一下TrackEval和py-motmetrics这两个包在后文的评估部分会用到。特别注意py-motmetrics的版本建议固定在1.2.0左右新版本对Python 3.10以上支持不太好如果出现导入报错先降级再排查其他问题。2.2 VisDrone原始标注格式解析VisDrone的标注是TXT文件每行格式如下bbox_left, bbox_top, bbox_width, bbox_height, score, object_category, truncation, occlusion前四个是目标框的左上角坐标和宽高第五个score在训练标注里恒为1第六个是类别编号1是汽车2是行人3是自行车等等第七和第八分别是截断程度和遮挡程度取值范围0到2。这里有个容易被新手忽略的点VisDrone把“汽车”和“卡车”分成了两个单独编号但很多MOT任务里并不需要区分这两者而是统一当作“车”来跟踪。我的做法是在数据预处理阶段就把类别映射为汽车、卡车统一为car行人保持pedestrian自行车和三轮车合并为cyclist。这样检测器需要学习的类别从10个降为3个训练难度降低跟踪阶段的目标类型也更符合实际需求。2.3 标注转YOLO格式的完整脚本YOLOX训练使用的是COCO格式也就是每张图片对应一个JSON文件标注里包含images、annotations、categories三个字段。VisDrone的TXT标注到COCO JSON的转换核心代码如下这段代码我实际跑过可以直接用import os import json import cv2 from tqdm import tqdm def visdrone_to_coco(img_dir, txt_dir, output_json, category_map): categories [{id: 1, name: car}, {id: 2, name: pedestrian}, {id: 3, name: cyclist}] images [] annotations [] ann_id 1 for idx, txt_name in enumerate(tqdm(os.listdir(txt_dir))): if not txt_name.endswith(.txt): continue img_name txt_name.replace(.txt, .jpg) img_path os.path.join(img_dir, img_name) img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] images.append({ id: idx, file_name: img_name, width: w, height: h }) with open(os.path.join(txt_dir, txt_name), r) as f: for line in f.readlines(): parts line.strip().split(,) if len(parts) 8: continue x, y, bw, bh map(float, parts[:4]) cls_id int(parts[5]) if cls_id not in category_map: continue new_cls_id category_map[cls_id] annotations.append({ id: ann_id, image_id: idx, category_id: new_cls_id, bbox: [x, y, bw, bh], area: bw * bh, iscrowd: 0 }) ann_id 1 with open(output_json, w) as f: json.dump({images: images, annotations: annotations, categories: categories}, f)2.4 数据集划分与样本均衡VisDrone官方划分是train6471张、val548张、test1610张无标注。我训练时把train和val合并再按9:1重新划分训练集和验证集。这样做的原因是VisDrone原始val集只有548张对验证模型效果来说样本量偏小类别分布和场景覆盖都不够全面。重新划分时要加一个随机种子保证每次实验划分一致否则对比实验的公平性无从谈起。我用的是random.seed(42)在遍历所有图片后执行random.shuffle再按比例切分。样本均衡方面VisDrone的类别分布很不均匀汽车占比远高于行人和自行车。如果不做处理模型会倾向于把所有移动目标都预测成汽车。我统计过训练集中各类别的目标数量然后给类别损失加了加权系数系数和各类别样本量成反比并做归一化。这一步对最终tracking指标的影响非常直接。3. YOLOX_S模型训练与推理3.1 配置文件关键参数YOLOX官方仓库自带exps/default/yolox_s.py但直接用于VisDrone会有几个参数必须改# 数据相关 self.num_classes 3 self.data_dir datasets/VisDrone self.train_ann instances_train.json self.val_ann instances_val.json # 训练相关 self.input_size (960, 1600) # 航拍小目标需要大输入尺寸 self.test_size (960, 1600) self.mosaic_scale (0.5, 1.5) self.max_epoch 80 self.no_aug_epochs 10 self.warmup_epochs 5 self.eval_interval 10input_size这里我建议不要用默认的640x640。VisDrone的原图分辨率达到了2000x1500左右直接缩放到640会让大量小目标变成个位数像素根本学不到特征。我测试过把输入分辨率提高到960x1600后小目标的召回率提升了将近5个百分点。代价是训练速度变慢显存占用从8G左右升到15G左右如果你用的是12G显存的卡可以把尺寸降到800x1280效果仍然比640好很多。3.2 训练策略预训练、数据增强、EMA预训练权重是必选项。从零训练目标检测器在数据量有限的情况下很容易过拟合尤其VisDrone这种场景比较单一的航拍数据集。我用YOLOX_S在COCO上的预训练权重做初始化虽然COCO类别和VisDrone不完全一致但底层特征边缘、纹理、形状是可以迁移的。加载权重时忽略类别数不一致的部分PyTorch的load_state_dict里用strictFalse即可。数据增强方面YOLOX默认的Mosaic和MixUp我完整保留了。Mosaic将4张图拼接成一张相当于变相增大了batch size而且拼接后目标尺度分布更多样对航拍小目标非常友好。MixUp则是把两张图按比例融合模拟遮挡场景增强模型对遮挡目标的鲁棒性。EMA指数移动平均是YOLOX训练里很关键但容易被忽略的配置。它会维护一份模型参数的滑动平均副本推理时用这份平滑后的权重能显著降低训练后期震荡带来的性能波动。实测下来EMA对最终MOTA指标有1到2个百分点的提升这个收益在跟踪任务里非常可观。训练过程中的学习率调度采用Cosine Annealing初始学习率0.01配合Warmup前5个epoch线性升到目标值。Batch size设置为16如果显存不够先把输入尺寸降下来不要强行降batch size到8以下否则BatchNorm的统计量会不稳定。3.3 模型推理与检测结果导出训练完成后导出精度更高的权重然后写推理脚本。这里有一个关键点YOLOX的推理输出需要做NMS但ByteTrack本身并不关心你用什么NMS只需要拿到最终的检测框、置信度和类别。我直接用YOLOX官方提供的Demo.py逻辑把输出整理成以下结构detections [ { bbox: [x1, y1, x2, y2], # 原图坐标 score: 0.85, class_id: 1 }, ... ]注意坐标一定要映射回原图分辨率因为跟踪器是在原图坐标系下做运动估计的如果坐标停留在缩放后的分辨率卡尔曼滤波的位移预测会失真。推理阶段还有一个实用技巧test_size可以比训练尺寸略大一点比如训练用960x1600测试用1088x1728。这样推理时小目标的分辨率更高检测召回率会有一定提升速度损失在可接受范围内。4. ByteTrack跟踪器实现细节4.1 BYTE数据关联思想ByteTrack的核心思想用一句话概括就是不放弃任何检测框但分优先级使用。它把检测框按置信度分成高置信度大于阈值τ_high和低置信度介于τ_low和τ_high之间两组然后分两步关联。第一步用高置信度检测框和现有轨迹做匹配匹配成功的轨迹更新状态第二步用低置信度检测框去匹配第一步中没有匹配上的轨迹这样被遮挡导致检测置信度下降的目标仍然有机会被接上。这个设计和无人机航拍场景的契合点在于航拍视角下目标遮挡频繁检测器对部分遮挡目标的置信度往往在0.2到0.5之间如果按传统MOT的做法直接丢弃这些低分框轨迹就断了。ByteTrack相当于给了这些目标一个“复活”的机会而且不引入额外的表观特征计算成本。4.2 卡尔曼滤波状态建模ByteTrack的跟踪器基于ByteTrack官方仓库的byte_tracker.py。它采用8维状态向量cx, cy, w, h, vx, vy, vw, vh前四维是目标框中心坐标和宽高后四维是对应的速度。观测向量是4维也就是检测框的cx, cy, w, h。在定义状态转移矩阵时默认是匀速运动模型。但在无人机场景下镜头自身运动会产生额外的背景位移让目标在图像上的运动不是简单的匀速直线运动。我后来把卡尔曼滤波的process_noise_cov过程噪声矩阵调大了也就是让滤波器更相信观测、更怀疑运动模型。这样做的效果是当镜头突然加速转动时跟踪框能更快跟上目标不会因为预测位置滞后导致匹配失败。具体调整参数时我维护了两个跟踪器一个用于匹配预测框和检测框另一个用于更新速度状态。调参后测试集的ID Switch数量减少了大约12%。4.3 匈牙利匹配与轨迹生命周期数据关联用的是匈牙利算法代价矩阵是预测框和检测框之间的IoU。这里有个细节高置信度匹配阶段的阈值设为0.8低置信度阶段设为0.5。阈值太高会导致匹配过于严格目标稍微动一下就匹配不上太低又会引入大量误匹配。轨迹生命周期管理方面ByteTrack为每条轨迹维护了以下状态track_id全局唯一的轨迹编号tracked当前帧是否成功匹配lost连续未匹配帧数confirmed是否已确认匹配成功帧数达到阈值一个新检测框要连续匹配成功3帧才会被确认赋予正式track_id这能有效过滤检测器的单帧误检。如果一条轨迹连续30帧都没有匹配到任何检测框就判定为丢失将其状态置为删除。在航拍场景下我把这个阈值调到了50帧因为无人机拍摄时目标可能被树木、建筑遮挡较长时间太早删除会导致重新出现时被赋予新ID。4.4 跟踪器接入YOLOX输出的完整流程我将YOLOX的输出整理成ByteTrack需要的输入格式。ByteTrack的update方法接收的参数是pred_boxes: [x1, y1, x2, y2, score, class_id]每一帧的处理流程如下def track_frame(det_results, tracker): outputs detector(det_results) # 检测器输出 online_targets tracker.update(outputs, img_info, img_size) # online_targets 是一个列表每个元素包含 # [x1, y1, x2, y2, track_id, class_id, score]我用一个有序字典维护每一帧中每个track_id对应的像素位置然后在这些位置加上类别标签、置信度最后用OpenCV的VideoWriter写视频。这个流程中需要注意每个update调用必须保证帧顺序一致。如果我在预处理中出现跳帧或者乱序跟踪器会产生不可预知的错误关联。5. 评估流程MOTA、IDF1与可视化验证5.1 MotChallenge评测指标速览MOT任务的评估指标和检测任务完全不一样。检测只看mAP跟踪要看一组时序指标的组合MOTAMultiple Object Tracking Accuracy综合衡量漏检、误检、ID切换的指标越高越好IDF1ID F1 Score衡量目标身份保持能力越高说明ID切换越少ID SwitchIDSW轨迹ID切换总次数越少越好FPS推理速度工程落地最关心的指标很多新手只看MOTA但MOTA的主要误差来源是检测的漏检和误检对ID切换的惩罚相对不足。实际无人机场景下ID切换对下游任务如统计车流、行人数影响非常大所以我会把IDF1和IDSW也纳为核心指标。5.2 使用TrackEval评估官方标准的评估工具是TrackEval它支持MOT Challenge格式的GT和预测结果。我通过以下步骤完成评估第一步将跟踪结果转换为MOT Challenge格式每行frame_id, track_id, x1, y1, w, h, score, class_id, -1, -1第二步将原始VisDrone标注转换为同样的格式。注意VisDrone的GT是[x, y, w, h]的左上角表示法MOT格式虽然也接近但需要保证每帧的目标仅保留可见的、非忽略的目标。第三步修改TrackEval的配置文件指定GT和预测结果的路径、序列列表、类别。运行评估后输出一个包含MOTA、IDF1、IDSW等指标的文本文件。我发现实际评估中MOTA在0.3~0.4之间IDF1在0.4~0.5之间具体数值取决于阈值和类别定义。业界论文在VisDrone上的结果大致也在这一范围验证了这套实现是合理的。5.3 可视化与badcase分析指标只能告诉你“好不好”只有可视化才能告诉你“为什么不好”。我用OpenCV将跟踪框、track_id、类别、置信度画在视频帧上然后手动检查几个典型场景交叉路口车辆密集场景观察是否有ID跳变目标被树木遮挡场景观察轨迹能否重新接上视角快速转动场景观察跟踪框是否跟上小目标如远处行人场景观察检测器是否漏检这些分析能直接反推问题出在检测器还是跟踪器。如果IDSW集中在遮挡场景则问题偏向跟踪器的匹配策略需要调置信度阈值和IoU阈值如果漏检导致大量轨迹中断则问题偏向检测器需要调整输入尺寸或数据增强强度。6. 无人机场景下的实战避坑记录6.1 小目标漏检与检测阈值VisDrone里大量目标在20x20像素以下YOLOX_S默认的输出层对这类目标响应已经不强了。我在实践中发现关键是score_threshold不能设太高。在跟踪阶段我把检测的最小置信度阈值设为0.1低置信度匹配阶段的阈值设为0.05。这使得大量低分检测框能参与匹配挽救了一部分小目标的轨迹。但要小心别把所有阈值都调到零那样大量的背景误检会进入跟踪器每条误检都会创建一个虚假轨迹导致假阳性暴增。6.2 镜头快速运动与卡尔曼参数调优无人机飞行时镜头快速转动目标在图像平面上的运动速度会瞬间加大。默认的卡尔曼滤波匀速模型无法应对这种突变。我调优的核心经验是增加过程噪声协方差矩阵中的速度分量让滤波器更快适应目标速度变化。具体来说我会将跟踪器内KalmanFilter的std_weight_position和std_weight_velocity中的速度噪声权重从默认的1/80调大到1/40左右。调大后在镜头快速运动时IDSW明显减少但代价是高置信度匹配时误匹配的概率略微上升。6.3 密集人群的ID切换问题在行人密集的场景中目标彼此遮挡、尺寸又小匈牙利匹配的IoU代价矩阵区分度不足容易发生ID切换。我尝试过加入一个小的面积约束匹配时优先选择面积相近的目标。因为同一目标连续两帧的面积变化在航拍视角下不会太大而相邻目标如果一个是行人一个是汽车面积差异往往在一倍以上。这个约束的实现是在计算代价矩阵后将面积比超过1.5倍的候选匹配直接设为无穷大。实测IDSW减少了约8%。6.4 推理速度优化最终系统的FPS取决于检测器的推理速度。YOLOX_S在1080Ti上大约能跑30~35 FPS960x1600输入但加上跟踪器后整体降到25 FPS左右。跟踪器本身计算量不大延迟主要来自NMS和矩阵匹配。我做了两个优化一是使用torchvision的batched_nms替换原生的NMS速度提升20%左右二是对视频帧做隔帧处理每帧都做检测但跟踪器两帧更新一次这样检测器只需工作一半帧数FPS可以提升到40以上不过跟踪平滑度会略有牺牲。如果目标是实时性优先可以接受这种折中。这套系统从我最初搭建到最终调优踩过的坑主要集中在数据格式转换和阈值配合上。VisDrone标注里的截断目标、遮挡目标如果不过滤模型训练时会产生大量“半个人”的标注反而干扰检测器而跟踪阶段如果不对检测阈值做分档处理低分框处理就形同虚设。最好用的调试方式永远是先跑通整个流程再逐步调整阈值——因为检测器的输出分布和跟踪器的匹配逻辑互相影响但这两个模块各自的参数调整逻辑是完全独立的。你如果手头有别的检测器比如Faster R-CNN或者YOLOv7只要输出格式对齐ByteTrack这套跟踪逻辑同样可以直接复用不必重新训练检测器。本文还有配套的精品资源点击获取
返回列表