1. 环境准备与项目理解嘿朋友们我是老张在AI和智能硬件这块摸爬滚打了十来年。今天咱们不聊那些虚头巴脑的理论直接上手干。我注意到很多朋友想用YOLOXByteTrack这套组合拳来做自己的目标检测和跟踪比如数人头、统计车流或者监控生产线上的产品。想法很好但第一步就卡住了怎么用自己的图片和标注来训练模型网上资料要么太零碎要么就是直接跑官方Demo一换自己的数据就各种报错。别急这篇帖子就是为你准备的“从零避坑指南”我会把我踩过的坑、改过的代码一步步拆给你看保证你跟着做就能跑起来。首先你得知道YOLOX和ByteTrack分别是干嘛的。简单说YOLOX是个非常高效、精度也相当不错的目标检测模型它负责在一张图片里找出目标并框出来告诉你“这里有个东西它是人还是车”。而ByteTrack是一个多目标跟踪器它的任务是把这些框在视频的连续帧之间关联起来形成一条条轨迹告诉你“这个人在第一帧出现第二帧走到了这里第三帧……”。我们训练的目的就是让YOLOX能准确地识别出你关心的目标比如你工厂里的特定零件然后ByteTrack才能稳定地追踪它们。在动手之前你得把“战场”准备好。我强烈建议在Linux系统下操作比如Ubuntu 20.04Windows下各种路径和依赖问题会让你头疼加倍。你需要准备以下东西Python环境我用的Python 3.8比较稳定。建议使用conda或venv创建一个独立的虚拟环境避免包版本冲突。PyTorch去PyTorch官网根据你的CUDA版本如果你有NVIDIA显卡的话安装对应的版本。没有显卡就用CPU版本但训练会慢很多。克隆项目把ByteTrack的官方代码仓库克隆下来。这里有个小坑ByteTrack项目里其实已经集成了YOLOX所以你不需要单独去克隆YOLOX的仓库。git clone https://github.com/ifzhang/ByteTrack.git cd ByteTrack安装依赖进入项目根目录安装需要的Python包。官方通常会给一个requirements.txt文件。pip install -r requirements.txt如果安装过程中有某个包版本问题别慌根据错误提示去搜一下通常指定一个低一点或高一点的版本就能解决。比如opencv-python有时候会有兼容性问题。除了代码你还需要一个预训练模型权重。ByteTrack官方提供了几个基于COCO数据集预训练的YOLOX模型如YOLOX-S, M, L, X。对于自定义数据集训练我强烈建议从一个预训练模型开始“微调”这比从零训练快得多效果也更好。你可以从项目的README或者Model Zoo部分找到下载链接把yolox_m.pth或者你选择的其他尺寸的权重下载下来放到项目里新建的一个pretrained文件夹里备用。2. 数据集准备从VOC到COCO的格式转换好了环境搞定接下来是最关键也最容易出错的一步准备你的数据集。我猜很多朋友跟我一开始一样用labelImg这类工具标注生成的是Pascal VOC格式的XML文件。一个文件夹里放图片一个文件夹里放对应的XML结构清晰。但ByteTrack的训练代码默认是读取COCO格式的标注文件一个巨大的.json文件。所以我们必须进行格式转换。为什么非得是COCO格式因为ByteTrack的代码里数据加载器dataloader是照着COCO的标注结构写的。COCO格式的标注文件是一个JSON里面包含了images图片信息、annotations标注框信息、categories类别信息这几个主要部分。而VOC的XML是每张图片独立一个文件。转换的核心就是把一堆XML里的信息提取、重组写进一个大的JSON文件里。我写了一个简单的Python脚本来做这个转换你可以参考。假设你的数据集结构是这样的your_dataset/ ├── images/ # 存放所有图片如 000001.jpg, 000002.jpg ... ├── annotations/ # 存放所有VOC格式的XML文件如 000001.xml, 000002.xml ... └── labels.txt # 你的类别列表每行一个类别名如 “person”, “car”转换脚本的核心思路如下这里只展示关键逻辑完整脚本需要你补充文件读取和错误处理import json import os import xml.etree.ElementTree as ET # 1. 初始化COCO JSON结构 coco_format { images: [], annotations: [], categories: [] } # 2. 构建 categories with open(labels.txt, r) as f: classes [line.strip() for line in f.readlines()] for i, cls in enumerate(classes, 1): # COCO的id通常从1开始 coco_format[categories].append({id: i, name: cls, supercategory: none}) # 3. 遍历所有XML文件 image_id 1 ann_id 1 for xml_file in os.listdir(annotations): tree ET.parse(os.path.join(annotations, xml_file)) root tree.getroot() # 提取图片信息 filename root.find(filename).text # 假设图片在images文件夹下 img_path os.path.join(images, filename) # 这里你需要用PIL或OpenCV获取图片宽高假设为img_w, img_h coco_format[images].append({ id: image_id, file_name: filename, height: img_h, width: img_w }) # 提取标注框信息 for obj in root.findall(object): cls_name obj.find(name).text cls_id classes.index(cls_name) 1 # 对应上面categories的id bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) width xmax - xmin height ymax - ymin coco_format[annotations].append({ id: ann_id, image_id: image_id, category_id: cls_id, bbox: [xmin, ymin, width, height], # COCO格式是 [x, y, width, height] area: width * height, segmentation: [], # 目标检测不需要分割留空列表 iscrowd: 0 }) ann_id 1 image_id 1 # 4. 保存为JSON文件 with open(train_coco.json, w) as f: json.dump(coco_format, f)运行这个脚本后你会得到一个train_coco.json文件。非常重要的一步用文本编辑器打开这个JSON文件仔细看看它的结构。特别是images列表里的每个字典看看file_name字段是不是正确的图片文件名annotations列表里的image_id是否和images里的id对应上了category_id是否正确。很多后续的报错根源都在这儿。接下来在ByteTrack项目里我建议你新建一个文件夹来存放你的数据比如在项目根目录创建datasets/mot_101名字随便起但最好有意义。然后把你的images文件夹和train_coco.json文件放进去。结构如下ByteTrack/ ├── datasets/ │ └── mot_101/ │ ├── images/ # 你的所有图片 │ └── train_coco.json # 你生成的COCO格式标注 ├── exps/ └── ...这样做的好处是路径清晰修改配置文件时不容易乱。3. 核心配置文件修改详解数据集准备好了现在要告诉训练代码去哪里找数据、你的数据长什么样。这就是修改配置文件。很多教程就一句话“修改your_exp_file.py”但具体改哪里、为什么改却没说清楚导致大家一头雾水。我们来彻底拆解。首先找到配置文件模板。进入ByteTrack/exps/example/mot/目录你会看到一堆yolox_x_abc.py这样的文件。它们对应不同场景和模型大小的配置。我以yolox_x_ch.py为例这是针对MOT17数据集的一个配置复制一份重命名为你自己的名字比如my_custom_exp.py。记住永远在副本上修改保留原文件作为参考。用编辑器打开my_custom_exp.py我们主要关注Exp这个类里的__init__方法。你需要修改以下几个关键参数class Exp(MyExp): def __init__(self): super(Exp, self).__init__() self.num_classes 3 # 改成你的类别数比如你只检测人、车、自行车就是3 self.depth 1.0 # 控制模型深度对应YOLOX的缩放因子一般用默认值 self.width 1.0 # 控制模型宽度对应YOLOX的缩放因子一般用默认值 self.exp_name os.path.split(os.path.realpath(__file__))[1].split(.)[0] # 实验名自动取文件名 # 训练集和验证集的标注文件路径 self.train_ann datasets/mot_101/train_coco.json # 指向你刚才生成的JSON文件 self.val_ann datasets/mot_101/train_coco.json # 如果没单独验证集可以先指向同一个 # 训练集和验证集的图片根目录 self.data_dir datasets/mot_101 self.train_img_dir images # 相对于data_dir的路径 self.val_img_dir images # 相对于data_dir的路径 # 其他训练参数可以根据自己情况调整 self.max_epoch 80 # 训练总轮数自定义数据集可以少一些比如50-80 self.data_num_workers 4 # 数据加载的进程数根据你CPU核心数调整 self.warmup_epochs 1 # 学习率热身轮数 self.basic_lr_per_img 0.001 / 64.0 # 基础学习率一般不动 self.input_size (800, 1440) # 输入图片尺寸高宽根据你的图片和显存调整可以先用(608, 1088)改完这些还没完。同一个文件里往下翻找到get_data_loader和get_eval_loader这两个函数。它们定义了数据如何被加载。你需要把里面关于数据集路径的硬编码改成你自己的。比如原来可能是def get_data_loader(self, batch_size, is_distributed, no_augFalse): from yolox.data import TrainTransform, MOTDataset dataset MOTDataset( data_dirself.data_dir, json_fileself.train_ann, # 这里已经用了我们上面定义的self.train_ann img_sizeself.input_size, preprocTrainTransform(...), ... ) ...检查一下确保MOTDataset初始化时传入的data_dir和json_file参数是正确的。通常我们上面修改的self.train_ann和self.data_dir会被用在这里所以一般不需要再改。但务必确认4. 修改数据加载代码以适配自定义标注这是整个流程里最“坑”的一个环节也是原帖作者重点提到的。因为不同的标注工具生成的COCO格式JSON其字段名可能略有差异。ByteTrack源码里的MOTDataset类位于yolox/data/datasets/mot.py在解析JSON时对字段名有固定期望。如果你的JSON字段名对不上就会报KeyError。你需要打开yolox/data/datasets/mot.py文件找到__getitem__方法里加载标注的部分大概在60-90行。原代码可能是这样的# 假设原代码片段 img_info self.coco.loadImgs(ids[img_id])[0] frame_id img_info[frame_id] video_id img_info[video_id] ... ann_info self.coco.loadAnns(ann_idsann_ids) for ann in ann_info: bbox ann[bbox] track_id ann[track_id]现在打开你生成的train_coco.json看看images数组里的每个字典有什么键key。比如你的图片信息里可能没有frame_id而是id这是COCO的标准格式。你的标注信息annotations里可能根本没有track_id因为静态图片标注通常没有轨迹ID。那么你就需要根据你的JSON结构来修改这段代码。例如# 修改后的代码片段 img_info self.coco.loadImgs(ids[img_id])[0] frame_id img_info[id] # 我的json里图片的唯一标识是‘id’不是‘frame_id’ # video_id img_info[video_id] # 我的json里没有‘video_id’这个字段所以注释掉 ... ann_info self.coco.loadAnns(ann_idsann_ids) for ann in ann_info: bbox ann[bbox] # track_id ann[track_id] # 我的标注里也没有‘track_id’注释掉 # 如果需要可以给一个默认的track_id比如-1 track_id -1核心原则代码里用什么键去字典里取值你的JSON字典里就必须有这个键否则就会报错。你必须像一个侦探一样仔细对比你的JSON结构和代码期望的结构然后进行“适配”。这步没有通用解完全取决于你的数据来源。这也是为什么我一开始让你仔细看自己生成的JSON文件内容。5. 启动训练与参数调试所有代码都改好后激动人心的训练时刻就到了。回到ByteTrack项目的根目录打开终端。训练命令的基本格式如下python tools/train.py -f exps/example/mot/my_custom_exp.py -d 0 -b 8 --fp16 -o -c pretrained/yolox_m.pth我来解释一下每个参数-f: 指定你的配置文件路径就是我们刚才修改的my_custom_exp.py。-d: 指定使用的GPU设备ID。-d 0表示使用第一块GPU。如果你有多块可以用-d 0,1,2。用CPU训练是-d -1但非常慢。-b: 总的批次大小batch size。这个值需要根据你的GPU显存来调整。显存小比如8G就设小一点比如-b 4或-b 8。显存大比如24G可以设到-b 16或更大。如果遇到CUDA out of memory错误首先就是减小这个值。--fp16: 启用混合精度训练。这能显著减少显存占用并加快训练速度现代GPU如Volta架构及以后都支持。除非你遇到数值不稳定问题否则建议一直开着。-o: 在训练开始前先清空优化器状态。通常建议加上。-c: 指定预训练权重的路径。这里指向我们之前下载的yolox_m.pth。执行命令后如果一切顺利你会看到终端开始输出日志显示当前epoch、迭代次数、损失值等。损失值特别是loss_iou,loss_obj,loss_cls会随着训练逐渐下降并趋于平稳。训练过程中可能会遇到的一些“坑”及解决办法CUDA out of memory (OOM)这是最常见的。首先尝试减小-b参数。其次可以尝试在配置文件中减小self.input_size比如从(800,1440)降到(608,1088)。如果还不行可以尝试关闭--fp16但显存占用会更大。Loss为NaN可能是学习率太高或者数据有问题比如标注框的坐标超出了图片范围。检查你的数据转换脚本确保生成的bbox坐标[x, y, width, height]都是合理的正数且xwidth img_w,yheight img_h。也可以尝试在配置文件中稍微调低self.basic_lr_per_img。训练速度极慢检查self.data_num_workers是否设置得太小比如0可以适当增大如4或8让CPU多进程加载数据不阻塞GPU。同时确认是否使用了GPU-d参数正确。训练完成后模型权重会默认保存在YOLOX_outputs/my_custom_exp以你的实验名命名的文件夹下的latest_ckpt.pth或按epoch保存的ckpt.pth文件中。6. 模型验证与推理测试训练完了怎么知道模型好不好用我们需要用验证集如果你有的话评估一下或者直接拿几张新图片测试一下。验证评估如果你在配置文件中指定了self.val_ann验证集标注可以使用评估脚本。但更常见的是我们直接进行推理测试。ByteTrack项目里通常有tools/demo_track.py这样的脚本。你需要准备一个简短的配置文件来告诉推理脚本用哪个模型、哪些参数。你可以创建一个简单的Python脚本进行快速测试import cv2 from yolox.data.data_augment import ValTransform from yolox.exp import get_exp from yolox.utils import postprocess import torch from loguru import logger # 1. 加载实验配置 exp_file exps/example/mot/my_custom_exp.py # 你的配置文件 exp get_exp(exp_file, None) exp.test_size (640, 640) # 推理尺寸可以调整 # 2. 加载模型 model exp.get_model() # 加载我们训练好的权重 ckpt_file YOLOX_outputs/my_custom_exp/latest_ckpt.pth ckpt torch.load(ckpt_file, map_locationcpu) model.load_state_dict(ckpt[model]) model.eval() model model.cuda() if torch.cuda.is_available() else model # 3. 预处理图片 image_path test_image.jpg img_origin cv2.imread(image_path) img, _ ValTransform()(img_origin, None, exp.test_size) img torch.from_numpy(img).unsqueeze(0).float() if torch.cuda.is_available(): img img.cuda() # 4. 推理 with torch.no_grad(): outputs model(img) # 后处理将输出转换为检测框 outputs postprocess( outputs, exp.num_classes, exp.test_conf, exp.nmsthre ) # 5. 可视化结果 if outputs[0] is not None: bboxes outputs[0][:, :4].cpu().numpy() scores outputs[0][:, 4].cpu().numpy() cls_ids outputs[0][:, 5].cpu.numpy() for bbox, score, cls_id in zip(bboxes, scores, cls_ids): x1, y1, x2, y2 bbox.astype(int) cv2.rectangle(img_origin, (x1, y1), (x2, y2), (0, 255, 0), 2) label f{exp.class_names[int(cls_id)]}: {score:.2f} cv2.putText(img_origin, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) cv2.imwrite(result.jpg, img_origin) logger.info(Detection done, result saved to result.jpg)这个脚本只做了检测。要结合ByteTrack进行跟踪你需要去研究demo_track.py原理类似只是多了将连续帧的检测结果关联起来的步骤。你需要确保跟踪部分的数据接口比如要求的track_id和你修改后的数据格式能对上。最后我想说自定义数据集训练就是一个不断调试和适配的过程。第一次成功跑通带来的成就感是巨大的。如果中间遇到任何报错别急着放弃仔细阅读错误信息它通常会告诉你哪一行代码、哪一个变量出了问题。对照你的数据和代码修改耐心排查。这套流程我走过很多遍虽然每次数据不同都会有点小波折但整体路径是清晰的。希望这份超详细的拆解能帮你把YOLOXByteTrack用起来解决你实际项目中的问题。