引言
boxmot由mikel brostrom开发,用于目标检测,分割和姿态估计模型的SOTA(state of art)跟踪模块,现已加入python第三方库 PYPI,可用pip包管理器进行安装。
boxmot所支持的跟踪器采用外观特征识别方法,如重型ReID(CLIRdID)和轻型ReID(LightMBN, OSNet等),来识别不同图像帧中同一个目标。这些ReID权文件在运行boxmot时自动下载,无需事先下载checkpoint文件。
boxmot目前支持的对象检测模型有:yolov8, yolo-NAS和YOLOX。支持的跟踪器:BoTSORT, DeepOCSORT, OCSORT, HybridSORT, ByteTrack, StrongSORT。之前常用的DeepSort由增强型的StrongSORT取代。
boxmot可以看作一个软件封装器,将多种对象检测模型与不同的目标跟踪器组合,实现多目标跟踪。
boxmot安装
安装以linux系统为例,假定该系统已经安装有python >=3.8,且建立好虚拟环境。
将boxmot安装到yolo_tracking目录:
git clone https://github.com/mikel-brostrom/yolo_tracking.git
cd yolo_tracking
pip install -v -e .
以上命令操作过程,即基本完成所需运行的python语言,pytorch模块运行环境。需要说明,这里还需要安装ultralytics相关模块。不过,在yolo_tracking/examples/track.py中,程序自动检查是否安装ultralytics。若没有,则自动安装mikel-brosrom github目录中保存的ultralytics v8.0.146到虚拟环境。需要注意:boxmot v10.0.43仅与ultralytics v8.0.146兼容,若安装ultralytics最新版本将出现莫名错误。
利用examples/track.py安装ultralytics与boxmot现有版本兼容。这里有个问题,track程序将ultralytics安装到虚拟环境,而不在工作目录yolo_tracking下,这样调试程序时,不能跟踪到ultralytics相关的程序模块。当然可以对python调试器做一些相应的配置,就更沟跟踪到虚拟环境的模块,但是操作很麻烦。
把ultralytics安装到yolo_tracking的目录,操作如下:
先删除虚拟环境下和系统中可能安装的ultralytics模块:
pip uninstall ultralytics
克隆ultralytics v8.0.146
git clone https://github.com/mikel-brostrom/ultralytics.git
此操作在home目录下产生ultralytics目录。我们需要将ultralytics二级目录:~/ultralytics/ultralytics移动到yolo_tracking目录下,完成安装ultralytics到工作目录yolo_tracking,这样在python程序调试时,可以跟踪到ultralytics模块。为了防止混淆,将examples/track.py中安装ultralytics语句注释掉:
#__tr = TestRequirements()
#__tr.check_packages(('ultralytics @ git+https://github.com/mikel-brostrom/ultralytics.git', )) # install ultralytics
运行示例
运行yolov8s+strongsort对输入视频进行车辆跟踪示例:
python examples/track.py \
--yolo-model yolov8s \
--reid-mode osnet_x0_25_market1501.pt \
--source ~/yolo_tracking/MOT16-13-h264.mp4 \
--save \
--show \
--classes 2 \
--tracking-method strongsort
运行 yolo_nas + strongsort 示例:
python examples/track.py \
--yolo-model yolo_nas_s \
--reid-mode osnet_x0_25_market1501.pt \
--source ~/yolo_tracking/MOT16-13-h264.mp4 \
--save \
--show \
--classes 2 \
--tracking-method strongsort
track.py程序运行解析
examples/track.py中,经繁琐的模型初始化后,每帧图像的检测-跟踪处理开始于循环:
for frame_idx, r in enumerate(results)
即,每帧产生一个results,对每帧results写入MOT跟踪文件,而实际的处理看跟踪results的跳转。这里跳转至
ultralytics.engine.predictor.py 中的 stream_inference()函数,略过某些初始化,看函数中的循环:
for batch in self.dataset
其中重要的处理在:
# Preprocess
with profilers[0]:
im = self.preprocess(im0s) #1 图像格式处理,提取帧内容到数组。
# Inference
with profilers[1]
preds = self.inference(im, *args, **kwargs) #2 对象检测器,提取帧内目标。
# Postprocess
with profilers[2]: # 3 对所提取目标的后处理,即对跟踪器的目标输入,进行某种处理。
if isinstance(self.model, AutoBackend):
self.results = self.postprocess(preds, im, im0s) # ultralytics内定义的检测器处理方法, 如 yolov8
else:
self.results = self.model.postprocess(path, preds, im, im0s) # ultralytics之外的检测器处理, 如 yolo_nas, yolox
with profilers[3]:
self.run_callbacks('on_predict_postprocess_end') # 4 构造跟踪器 tracker, 对检测目标的跟踪
- preprocess, 输入图像处理。
- inference, 推理,目标检测器提取图像帧中的目标。
- postprocess, 目标检测器输出preds的处理, yolov8用 non_max_suppression(); 非ultralytics的目标检测器(yolo_nas, yolox)则貌似没有处理。
- postprocess_end,跟踪器处理,输出跟踪结果results。
yolov8以及之前的版本,yolov3, yolov5等采用non_max_suppression(preds, …)来去除不可信的多余目标,且可根据classes来筛选需要保留的目标类别,从而减少目标数量,为后续的跟踪器创造较好的工作环境,减少跟踪器运行时间。因为跟踪器按照目标数量逐一迭代产生跟踪轨迹tracks,迭代为串行运算,目标越多跟踪时间越长。Postprocess执行non_max_supperssion。
对于yolo-nas,由Deci AI公司开发,目前尚未公开,boxmot无法筛选目标类别,所以,postprocess对preds基本没有处理,直接送到跟踪器tracker。
最后,on_predict_postprocess_end执行跟踪器操作,输入preds,输出results。
图1 yolov8 postprocess,经non_max_suppression(),筛选出小车对象(classes=2)。
图2 yolo_nas postprocess, boxmot目前不能通过筛选classes目标,跟踪输出多个类型目标。
ultralytics.trackers.track.py执行函数 on_predict_postprocess_end, boxmot所支持的跟踪器定义在 boxmot.trackers目录中。
完成 Preprocess, Inference, Postprocess后,得到跟踪输出results,进入可视化过程,在输入视频上绘制跟踪框;标注目标id、类型、置信度等参数等。
由此,完成一帧图像的检测-跟踪-可视化。predictor.py函数stream_inference对一帧图像处理,track.py中的循环体(for frame_idx, r in enumerate(results))对输入视频逐帧处理。
examples目录下还有两个可执行文件val.py, evolve.py
val.py 根据选定的benchmark(MOT16, MOT17, MOT20),对检测器+跟踪器组合做性能评估,跟踪类型为persons。
以下benchmark = MOT17, yolov8s + strongsort的评估结果如下:
看来MOT-17指标与mikel brostrom提供的评估指标还有相当差距,原因不明。此外,本人运行得到的MOT-16各项指标与MOT-17倒是相差很小。
evolve.py 对跟踪器进行配置参数优化,就是在val.py过程提供评估指标,采用NSGA-II多目标遗传算法调整跟踪器配置参数,迭代运行,实现配置参数优化。显然,这是耗时较多的计算过程,尚未尝试。
跟踪框可视化
此处记录跟踪框可视化过程,便于对跟踪框所标注的目标信息进行修改显示。对此部分内容不感兴趣的读者可忽略。
从一帧图像的处理着手,见predictor.py 中函数stream_inference(), 其中看注释Visualize, save, write results下面的循环:
for i in range(n):
if self.args.verbose or self.args.save or self.args.save_txt or self.args.show: ###############
s += self.write_results(i, self.results, (p, im, im0)) # 此处进入跟踪框可视化
函数write_results部分:
def write_results(self, idx, results, batch):
if self.args.save or self.args.show: # Add bbox to image
plot_args = {
'line_width': self.args.line_width,
'boxes': self.args.boxes,
'conf': self.args.show_conf,
'labels': self.args.show_labels}
if not self.args.retina_masks:
plot_args['im_gpu'] = im[idx]
self.plotted_img = result.plot(**plot_args) ####进入plot
result.plot在 ultralytics.engine.results.py定义,模块引用
from ultralytics.utils.plotting import Annotator
下面是result.plot函数使用类Anootator
annotator = Annotator( #定义实例annotator
deepcopy(self.orig_img if img is None else img),
line_width,
font_size,
font,
pil or (pred_probs is not None and show_probs), # Classify tasks default to pil=True
example=names)
...
# Plot Detect results
if pred_boxes and show_boxes:
for d in reversed(pred_boxes):
c, conf, id = int(d.cls), float(d.conf) if conf else None, None if d.id is None else int(d.id.item())
name = ('' if id is None else f' {id} ')
label = (f'{name} {conf:.2f}' if conf else name) if labels else None
annotator.box_label(d.xyxy.squeeze(), label, color=colors(c, True)) # 进入annotator.box_label , 绘制bbox框。
实际的跟踪框bbox绘制在函数 annotator.box_label(), 这个可视化与mikel brostrom之前的版本(v3, v4, v5, v6)一致,都用ultralytics.utils.plotting.py。找到这个annotator.box_label,可以更改跟踪框显示方式,因为默认的label参数太多(id, class_name, conf),当图像帧中目标很多时,显示label一团糟,根本看不清。找到修改地方就主动。见图1,图2的简洁label显示。