OpenTrafficMonitoringPlus车辆检测微调过程
一、概述
opentrafficmonitoringplus是一个车辆检测的工作,可以根据视频或图像输入识别出车辆。
该项目是基于 Detectron2 的实现,在此基础上补充了无人机视角的航拍图像进行的车辆位置检测、追踪与估计。
项目来源:GitHub - fkthi/OpenTrafficMonitoringPlus
以下是项目来源提供的运行环境参考(非本教程使用环境):
Detectron2, Python 3.6 or 3.7 and packages listed in
Tested on:requirements.txt
- Ubuntu 18.04, Python 3.6.9, torch 1.4.0, torchvision 0.5.0, CUDA 10.2, Nvidia 440.x drivers
- Intel Xeon E-2176G, 32 GB RAM, GeForce RTX 2080 Ti, M.2 SDD
二、快速开始
运行环境
类别 | 详细信息 |
---|---|
CPU | 6 vCPU Intel(R) Xeon(R) Gold 6130 CPU @ 2.10GHz |
CPU 核心数 | 16核心 |
GPU | V100 |
GPU 显存 | 32 GB |
CUDA 版本 | 10.2 |
操作系统 | Linux 5.19.0-50-generic x86_64 |
Python 版本 | 3.7 |
PyTorch 版本 | 1.8.0 |
源码下载
wget https://mirrors.aheadai.cn/scripts/OpenTrafficMonitoringPlus.zip
unzip OpenTrafficMonitoringPlus.zip
cd OpenTrafficMonitoringPlus-master
conda环境创建
conda create --name opentraffic python==3.7
conda activate opentraffic
pytorch与cuda下载
conda install pytorch==1.8.0 torchvision==0.9.0 torchaudio==0.8.0 cudatoolkit=10.2 -c pytorch
detectron2安装
这里不能使用conda安装,因为这个库未被conda收录
可根据自己不同的cuda版本参考文档:Installation — detectron2 0.6 documentation
python -m pip install detectron2 -f \
https://dl.fbaipublicfiles.com/detectron2/wheels/cu102/torch1.8/index.html
环境配置
由于github自带的环境本身就存在冲突,并且有一些包名也已经更改。
因此打开requirements.txt文件,将里面的内容修改如下:
opencv-python==3.4.8.29
opencv-contrib-python==3.4.8.29
tqdm
shapely
scikit-learn<=0.23.2
运行命令:
pip install -r requirements.txt
下载初始权重
wget https://mirrors.aheadai.cn/scripts/model_final_FHD_50kIt_bs2_noAug_790img.pth
mv model_final_FHD_50kIt_bs2_noAug_790img.pth ./src/maskrcnn
cd src
尝试推理
用文件夹中已存在的视频文件进行推理测试:
python main.py --videos=./videos/:./out/
运行过程中可以看到输出结果:
**********************************该帧所包含掩码个数: 1 ****************************************
Predicting Masks : 72%|██████████████████████████████████████████████████████████████████████████████▏ | 201/280 [00:24<00:09, 8.38it/s]**********************************该帧所包含掩码个数: 1 ****************************************
Predicting Masks : 72%|██████████████████████████████████████████████████████████████████████████████▋ | 202/280 [00:24<00:09, 8.40it/s]**********************************该帧所包含掩码个数: 1 ****************************************
Predicting Masks : 72%|███████████████████████████████████████████████████████████████████████████████ | 203/280 [00:24<00:09, 8.39it/s] Predicting Masks : 72%|███████████████████████████████████████████████████████████████████████████████ | 203/280 [00:24<00:09, 8.21it/s]
三、微调教程
下载微调训练数据:
wget https://mirrors.aheadai.cn/data/opentraffic_dataset_train.zip
mkdir dataset_train
unzip opentraffic_dataset_train.zip -d dataset_train/
如果只是试一下预训练的效果,可以跳过 “使用自己的数据集” 这一步,这里提供的数据集就是经过下面的各个步骤所得到的
使用自己的数据集:
如果有自己的数据集要进行微调也可以尝试,对应的cocojson标签的生成过程:
1、打开网站https://www.robots.ox.ac.uk/~vgg/software/via,打标签后生成VIAjson标签via_export_coco.json
2、通过以下脚本将via_export_coco.json转化为coco格式的json标签文件coco_format_output.json:
import json
import numpy as np
import pycocotools.mask as mask
# 读取VIA格式的JSON文件
via_file = 'via_export_coco.json' # 替换为你的VIA文件路径
with open(via_file, 'r') as f:
via_data = json.load(f)
# 创建一个空的COCO格式数据
coco_format = {
"images": [],
"annotations": [],
"categories": []
}
# 假设只有一个类别 "car",并且类别的ID为1
categories = [{"id": 1, "name": "car"}]
coco_format["categories"] = categories
# 初始化image_id和annotation_id
image_id = 1
annotation_id = 1
# 遍历所有图像和其对应的标注信息
for fid, file_info in via_data["file"].items():
file_name = file_info["fname"]
# 假设图片的尺寸(你可以根据实际情况调整这些值)
height = 3840 # 假设图片高度
width = 2160 # 假设图片宽度
# 创建COCO格式的图像信息
image_metadata = {
"id": image_id,
"file_name": file_name,
"height": height,
"width": width,
}
# 添加图像信息到COCO格式
coco_format["images"].append(image_metadata)
# 提取标注信息
for annotation_key, annotation_value in via_data["metadata"].items():
if annotation_value["vid"] == str(image_id): # 匹配图像ID
# 获取标注框信息(x, y, width, height)
x, y, box_width, box_height = annotation_value["xy"][1:5]
# 计算四个角的坐标,形成矩形多边形
segmentation = [
x, y, # 左上角
x + box_width, y, # 右上角
x + box_width, y + box_height, # 右下角
x, y + box_height # 左下角
]
# 创建COCO格式的标注信息
coco_format["annotations"].append({
"id": annotation_id,
"image_id": image_id,
"category_id": 1, # 只有一个类别“car”
"segmentation": segmentation, # 直接用多边形坐标列表
"area": box_width * box_height, # 标注区域的面积
"bbox": [x, y, box_width, box_height], # 计算框的坐标和尺寸
"iscrowd": 0
})
annotation_id += 1
# 增加图像ID
image_id += 1
# 将COCO格式数据保存为JSON文件
with open('coco_format_output.json', 'w') as f:
json.dump(coco_format, f)
print("转换完成,COCO格式数据已保存为 'coco_format_output.json'")
dataset_train/via2coco_json
python via2coco.py
cd ../..
3、调用vgg_to_coco生成detectron2 coco格式标签文件transformed_annotations.json:
python vgg_to_coco.py
4、将生成的文件transformed_annotations.json移动到数据集图片同一目录下
mv transformed_annotations.json ./dataset_train/drone_cars/
最后得到大致的数据集文件结构如下:
├── dataset_train
│ └── drone_cars
│ ├── frame_0000.jpg
│ ├── frame_0001.jpg
│ ├── frame_0002.jpg
│ ├── frame_0003.jpg
│ ├── frame_0004.jpg
│ ├── frame_0005.jpg
│ ├── frame_0006.jpg
...
│ │ ├── frame_0098.jpg
│ │ └── transformed_annotations.json
│ └── via2coco_json
│ ├── coco_format_output.json
│ ├── via2coco.py
│ └── via_export_coco.json
微调指令:
python train.py --dataset_train=./dataset_train/drone_cars/ --weights=./maskrcnn/model_final_FHD_50kIt_bs2_noAug_790img.pth
命令行参数如下:
--weights
默认值:detectron2://COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x/137849600/model_final_f10217.pkl
用途:指定预训练模型的权重路径,可以使用 Detectron2 提供的预训练模型,也可以加载自己训练的模型权重。
--dataset_train
默认值:./custom_data/train/
用途:指定训练数据集的路径,该路径下需要包含图像文件和 COCO 格式的标注文件(如 transformed_annotations.json
)。
--dataset_eval
默认值:./custom_data/eval/
用途:指定验证数据集的路径,用于训练过程中评估模型性能。该路径同样需要包含图像文件和标注文件。
--config
默认值:./maskrcnn/mask_rcnn_R_50_FPN_1x.yaml
用途:指定模型的配置文件路径,包含网络结构和训练参数等详细信息。
--workers
默认值:0
用途:设置 PyTorch 数据加载器的工作线程数量,0 表示只使用主进程加载数据。可以根据硬件性能调整此值。
--batch_size
默认值:2
用途:每批次训练的数据样本数量。建议根据显存大小调整。
--lr
默认值:0.001
用途:模型的初始学习率,需根据任务和 batch size 调整。
--max_iter
默认值:30000
用途:最大训练迭代次数,控制训练总时长。
--eval_interval
默认值:10000
用途:训练过程中每隔多少步在验证集上运行评估。
--gamma
默认值:0.3
用途:学习率调整时的衰减系数,在指定步数后按该比例降低学习率。
--steps
默认值:(20000, 25000)
用途:指定学习率调整的步数,例如在 20000 和 25000 步时按 --gamma
值降低学习率。
--save_interval
默认值:5000
用途:训练时每隔指定步数保存模型权重,便于中断后恢复或选择最佳模型。
--warmup_iters
默认值:2000
用途:学习率预热的步数,在初始阶段逐步增加学习率,防止训练不稳定。
--freeze_at
默认值:2
用途:冻结网络的前几层(如 backbone 的前几个阶段),用于加速训练或减少过拟合。
--output_dir
默认值:./model_output/
用途:训练过程中模型权重和日志文件的保存路径。
--num_classes
默认值:1
用途:设置数据集中的目标类别数量(不包括背景类)。例如,如果只有一个类别(如"车"),则设为 1。
--resume
默认值:False
用途:是否从上一次训练的中断点恢复。如果设置为 True
,会从 --output_dir
中加载最新的模型权重。
输出结果:
运行成功得到以下输出:
[12/10 17:00:04 d2.utils.events]: eta: 14:04:41 iter: 179 total_loss: 1.409 loss_cls: 0.2494 loss_box_reg: 0.6586 loss_mask: 0.3173 loss_rpn_cls: 0.02784 loss_rpn_loc: 0.1113 time: 1.7044 data_time: 1.2705 lr: 9.0411e-05 max_mem: 4870M
[12/10 17:00:38 d2.utils.events]: eta: 14:03:57 iter: 199 total_loss: 1.364 loss_cls: 0.2543 loss_box_reg: 0.6459 loss_mask: 0.316 loss_rpn_cls: 0.0218 loss_rpn_loc: 0.1052 time: 1.7038 data_time: 1.2667 lr: 0.0001004 max_mem: 4870M
[12/10 17:01:12 d2.utils.events]: eta: 14:03:23 iter: 219 total_loss: 1.383 loss_cls: 0.2582 loss_box_reg: 0.6651 loss_mask: 0.3172 loss_rpn_cls: 0.01913 loss_rpn_loc: 0.1162 time: 1.7036 data_time: 1.2680 lr: 0.00011039 max_mem: 4870M
[12/10 17:01:46 d2.utils.events]: eta: 14:02:49 iter: 239 total_loss: 1.307 loss_cls: 0.244 loss_box_reg: 0.6247 loss_mask: 0.303 loss_rpn_cls: 0.01778 loss_rpn_loc: 0.1063 time: 1.7031 data_time: 1.2652 lr: 0.00012038 max_mem: 4870M
[12/10 17:02:20 d2.utils.events]: eta: 14:02:12 iter: 259 total_loss: 1.324 loss_cls: 0.2399 loss_box_reg: 0.618 loss_mask: 0.3044 loss_rpn_cls: 0.01611 loss_rpn_loc: 0.1092 time: 1.7032 data_time: 1.2705 lr: 0.00013037 max_mem: 4870M
四、吞吐量指标输出教程
运行指令:
conda install subprocess
conda install re
找到python3.7目录下第三方库site-packages中的detectron2中的train_loop.py脚本,将其TrainerBase类进行修改,可以得到相应的指标输出,具体修改内容如下:
1、构造函数的修改:
def __init__(self) -> None:
self._hooks: List[HookBase] = []
self.iter: int = 0
self.start_iter: int = 0
self.max_iter: int
self.storage: EventStorage
self.step_times: List[float] = [] # 记录每步的时间
self.throughputs: List[float] = [] # 记录吞吐量
self.epoch_times: List[float] = [] # 记录每个epoch的时间
self.gpu_memory_usages: List[float] = [] # 记录显存使用率
self.powers: List[float] = [] # 记录每步的功耗
self.device_id = torch.cuda.current_device() # 当前GPU编号
_log_api_usage("trainer." + self.__class__.__name__)
2、train函数的修改:
def train(self, start_iter: int, max_iter: int):
"""
Args:
start_iter, max_iter (int): See docs above
"""
logger = logging.getLogger(__name__)
logger.info("Starting training from iteration {}".format(start_iter))
self.iter = self.start_iter = start_iter
self.max_iter = max_iter
with EventStorage(start_iter) as self.storage:
try:
self.before_train()
epoch_start_time = time.time()
for self.iter in range(start_iter, max_iter):
step_start_time = time.time()
# 查询显存和功率
gpu_memory = self.get_gpu_memory() # 调用独立函数获取显存
power = self.get_gpu_power() # 调用独立函数获取功率
self.gpu_memory_usages.append(gpu_memory)
self.powers.append(power)
self.before_step()
self.run_step()
self.after_step()
step_time = time.time() - step_start_time
self.step_times.append(step_time)
# 计算吞吐量
throughput = self.calculate_throughput(step_time)
self.throughputs.append(throughput)
# 打印每步的指标
self.print_metrics(step_time, throughput, gpu_memory, power)
# 计算并输出每个epoch的时间
epoch_time = time.time() - epoch_start_time
self.epoch_times.append(epoch_time)
# 最后输出每个指标的平均值
self.print_average_metrics()
# self.iter == max_iter can be used by `after_train` to
# tell whether the training successfully finished or failed
# due to exceptions.
self.iter += 1
except Exception:
logger.exception("Exception during training:")
raise
finally:
self.after_train()
3、添加新的方法:
def calculate_throughput(self, step_time: float) -> float:
"""
计算吞吐量,即每秒处理的样本数(Samples per Second)。
假设每次训练步骤处理一个batch。
"""
samples_per_step = 4 # 假设每次训练步骤处理2个样本
return samples_per_step / step_time
def print_metrics(self, step_time: float, throughput: float, gpu_memory: float, power: float) -> None:
"""
打印当前step的训练指标。
"""
print(f"Step Time: {step_time:.4f} s, Throughput: {throughput:.2f} samples/s, "
f"GPU Memory: {gpu_memory:.2f} MB, Power: {power:.2f} W")
def print_average_metrics(self) -> None:
"""
打印训练结束后的平均指标。
"""
avg_step_time = mean(self.step_times)
avg_throughput = mean(self.throughputs)
avg_epoch_time = mean(self.epoch_times)
avg_gpu_memory = mean(self.gpu_memory_usages)
avg_power = mean(self.powers)
print(f"Average Step Time: {avg_step_time:.4f} s")
print(f"Average Throughput: {avg_throughput:.2f} samples/s")
print(f"Average Epoch Time: {avg_epoch_time:.2f} s")
print(f"Average GPU Memory Usage: {avg_gpu_memory:.2f} MB")
print(f"Average Power: {avg_power:.2f} W")
# 将结果输出到log.txt文件
with open("log.txt", "a") as log_file:
log_file.write(f"Average Step Time: {avg_step_time:.4f} s\n")
log_file.write(f"Average Throughput: {avg_throughput:.2f} samples/s\n")
log_file.write(f"Average Epoch Time: {avg_epoch_time:.2f} s\n")
log_file.write(f"Average GPU Memory Usage: {avg_gpu_memory:.2f} MB\n")
log_file.write(f"Average Power: {avg_power:.2f} W\n")
log_file.write("\n")
def get_gpu_power(self) -> float:
"""
使用 nvidia-smi 获取当前 GPU 的功耗(瓦特)。
"""
import subprocess
import re
# 使用 nvidia-smi 查询功率
result = subprocess.run(
["nvidia-smi", "--query-gpu=power.draw", "--format=csv,noheader,nounits"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
if result.returncode != 0:
raise RuntimeError(f"nvidia-smi error: {result.stderr}")
power_draws = [float(re.search(r"[\d.]+", line).group()) for line in result.stdout.strip().split("\n")]
return power_draws[self.device_id] # 当前设备的功率(W)
def get_gpu_memory(self) -> float:
"""
使用 nvidia-smi 获取当前 GPU 的显存使用情况(单位:MiB)。
"""
import subprocess
import re
# 使用 nvidia-smi 查询显存
result = subprocess.run(
["nvidia-smi", "--query-gpu=memory.used", "--format=csv,noheader,nounits"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
if result.returncode != 0:
raise RuntimeError(f"nvidia-smi error: {result.stderr}")
# 解析显存信息
gpu_memories = [int(re.search(r"\d+", line).group()) for line in result.stdout.strip().split("\n")]
return gpu_memories[self.device_id] # 返回当前设备显存使用(MiB)
修改成功后可以在运行时在终端看到以下输出:
(batch_size = 4,修改batch_size时calculate_throughput方法也要修改)
python train.py --dataset_train=./dataset_train/drone_cars/ --weights=./maskrcnn/model_final_FHD_50kIt_bs2_noAug_790img.pth --batch_size 4 --save_interval 1000
[12/12 14:22:13 d2.utils.events]: eta: 1 day, 1:25:49 iter: 3479 total_loss: 0.4996 loss_cls: 0.08558 loss_box_reg: 0.2394 loss_mask: 0.1362 loss_rpn_cls: 0.001144 loss_rpn_loc: 0.03836 time: 3.4450 data_time: 2.4815 lr: 0.001 max_mem: 8925M
Step Time: 3.7019 s, Throughput: 1.08 samples/s, GPU Memory: 10638.00 MB, Power: 40.77 W
Step Time: 3.6123 s, Throughput: 1.11 samples/s, GPU Memory: 10638.00 MB, Power: 40.58 W
Step Time: 3.7199 s, Throughput: 1.08 samples/s, GPU Memory: 10638.00 MB, Power: 40.66 W
Step Time: 3.6326 s, Throughput: 1.10 samples/s, GPU Memory: 10638.00 MB, Power: 40.58 W
Step Time: 3.6540 s, Throughput: 1.09 samples/s, GPU Memory: 10638.00 MB, Power: 40.57 W
Step Time: 3.6430 s, Throughput: 1.10 samples/s, GPU Memory: 10638.00 MB, Power: 40.57 W
Step Time: 3.7828 s, Throughput: 1.06 samples/s, GPU Memory: 10638.00 MB, Power: 40.67 W
^C[12/12 14:22:37 d2.engine.hooks]: Overall training speed: 1484 iterations in 1:25:13 (3.4459 s / it)
[12/12 14:22:37 d2.engine.hooks]: Total training time: 1:31:39 (0:06:25 on hooks)
wget https://mirrors.aheadai.cn/log/opentraffic_metrics.json #日志输出
评论