一个独立、通用的目标检测工程工具包,基于 Ultralytics YOLO。不依赖任何特定项目,把 tools/ 目录放进你的工程,用 from tools import ... 按需调用。覆盖从视频抽帧 → 标注格式转换 → 标签治理 → 数据集划分 → 训练 → 验证 → 推理 → 模型导出的完整链路。
Python ≥ 3.9(开发环境为 3.12 / 3.13)。
pip install ultralytics opencv-python numpy pillow tqdm lxml| 依赖 | 用途 | 涉及模块 |
|---|---|---|
ultralytics |
YOLO / YOLOWorld 训练、验证、推理、导出、COCO 转换 | train val predict export convert |
opencv-python |
图像读写、画框、显示、视频解码 | predict video |
numpy |
坐标数组运算 | predict convert |
pillow |
中文文本绘制、ICC 配置处理、图片收集 | predict preprocess |
tqdm |
进度条 | 全部模块 |
lxml |
VOC XML 解析 | convert |
ObjectDetection/
├── main.py # 入口壳(仅 from tools import *,预留)
└── tools/ # 全部功能所在
├── __init__.py # 统一 re-export + __all__
├── convert.py # 标注格式转换:COCO / 百度 COCO / VOC → YOLO txt
├── preprocess.py # 图像与标签预处理、标签类别治理、类别计数
├── dataset.py # 图标签配对检查、数据集划分、分块收集
├── train.py # YOLO 训练封装
├── val.py # 模型验证
├── predict.py # 推理(内置绘图 / 自绘中文标签两套)
├── export.py # 模型导出(ONNX / TensorRT / OpenVINO …)
├── model_utils.py # 训练产物定位(best.pt / last.pt)
├── systools.py # 通用文件操作(建目录、复制、删除、批量重命名)
└── video.py # 视频抽帧(单文件 / 批量 / 递归)
| 函数 | 作用 |
|---|---|
extract_frames |
主入口:批量抽帧,含配置打印、逐视频报告与汇总统计 |
process_video |
抽取单个视频的帧 |
collect_videos |
从文件或文件夹收集视频 |
| 函数 | 作用 |
|---|---|
coco_to_txt |
标准 COCO json → YOLO txt(官方转换器,按 cls91to80 映射类别) |
coco_txt_BaiDu |
百度风格 COCO json → YOLO txt(自解析,不做类别映射,跳过 iscrowd) |
voc_to_txt |
VOC XML → YOLO txt(需提供类别名→ID 映射字典) |
| 函数 | 作用 |
|---|---|
count_labels |
统计各类别标注数量,支持 YOLO txt + VOC xml,可配 names 显示中文类名 |
replace_labels_with_target |
把标签文件里指定类别的 ID 改成目标类别(origin_id="all" 为全量替换) |
batch_replace_labels |
上者的文件夹批量版,返回改动统计 |
fill_empty_labels |
为缺失标签的图片生成空标签文件(YOLO 背景图),支持 dry_run 预览 |
normalize_labels |
把标签坐标夹紧到 1.0 以内 |
rm_icc_profile |
删除 PNG 的 ICC 色彩配置(部分标注工具对带 ICC 的图兼容性差) |
collect_images |
收集目录下所有图片 |
IMAGE_EXTS |
模块级常见图片扩展名集合 |
| 函数 | 作用 |
|---|---|
check_and_copy_missing_files |
检查图/标签配对,找出「有图无标签」「有标签无图」的落单样本 |
spilt_train_test |
划分 train/val(可选 test)并复制落盘,支持负样本混入与固定随机种子 |
split_meta |
把目录按 split_num 平均切成若干份(分布式标注 / 分批处理用) |
collcet_meta |
收集 split_meta 各子目录的文件,可另存一份 |
copy_files |
复制文件,同名跳过,可安全重跑 |
rm_files |
删除指定文件及其同名标签(保证配对不被破坏) |
| 模块 | 函数 | 作用 |
|---|---|---|
train.py |
train |
YOLO 训练封装,暴露 epochs/batch/imgsz/optimizer/iou 等常用超参 |
val.py |
evaluate |
在数据集上验证模型,返回 ultralytics 指标 |
predict.py |
predict |
用 ultralytics 内置绘图推理,可 show 弹窗 / save 存盘 |
predict.py |
advanced_predict |
自绘检测框,支持中文字体与类别名整表替换 |
predict.py |
add_pillow_text |
在图像数组上写文本(走 Pillow,解决 OpenCV 不支持中文) |
predict.py |
get_infer_data |
按 IMAGE(随机抽样)/ DIR(整目录)取推理数据 |
export.py |
export_model |
导出 ONNX / TensorRT / OpenVINO / TorchScript |
model_utils.py |
get_best_last_model |
从训练产物中定位 best.pt / last.pt |
| 函数 | 作用 |
|---|---|
create_dirs |
建目录,overwrite 可取 None(交互确认)/ True / False |
rm_dirs |
递归删除目录 |
fetch_specific_files |
按扩展名收集文件(支持传目录或已有路径列表) |
process_files |
批量复制(mode=0)或删除(mode=1) |
add_suffix_to_files |
给文件夹内文件批量加后缀(不改扩展名),目标已存在则跳过 |
ignore_something |
屏蔽 warnings / logging |
# 推荐:显式导入需要的函数
from tools import extract_frames, count_labels, spilt_train_test, train
# 也可以(会触发全部子模块加载,见下方注意点)
from tools import *先统计一下手里的标签分布,心里有数再动手:
from tools import count_labels
stats = count_labels(
"./datasets/桥梁病害/labels",
names={0: "裂缝", 1: "剥落", 2: "钢筋外露锈蚀"},
)
print(stats["counts"]) # {'0:裂缝': 812, '1:剥落': 340, ...}
print(stats["labels"]) # 标注总数from tools import extract_frames
result = extract_frames(
input_path="./datasets/巡检视频",
output_dir="./datasets/巡检视频/frames",
every_n_frames=10, # 每 10 帧取 1 张
image_ext="jpg",
image_quality=100,
recursive=True, # 递归搜索子文件夹
flat_output=True, # 所有图片放同一文件夹
filename_prefix=True, # 文件名带视频名前缀,防止重名覆盖
)
print(result["total_saved"]) # 提取图片总数from tools import coco_to_txt, coco_txt_BaiDu, voc_to_txt
# 标准 COCO
coco_to_txt("./datasets/ann.json", "./datasets/labels", use_segments=False)
# 百度风格 COCO(annotations_path 传目录)
coco_txt_BaiDu("./datasets/baidu_ann/", "./datasets/labels")
# VOC XML(必须给类别映射)
voc_to_txt("./datasets/voc_xml/", "./datasets/labels",
obj_dict={"crack": 0, "spalling": 1})from tools import (
check_and_copy_missing_files,
replace_labels_with_target, batch_replace_labels,
fill_empty_labels, normalize_labels,
)
# 找出落单文件,复制出来人工复核
check_and_copy_missing_files(
img_path="./datasets/images",
label_path="./datasets/labels",
missing_dir="./datasets/_to_review",
)
# 把散乱的类别统一:1 和 2 都并成 0
replace_labels_with_target("./datasets/labels/a.txt", 1, 0)
replace_labels_with_target("./datasets/labels/a.txt", 2, 0, warn_if_no_match=True)
# 或整个文件夹批量(返回改动统计)
report = batch_replace_labels("./datasets/labels", origin_id=1, target_id=0,
warn_if_no_match=True)
print(report) # {'total': 300, 'changed_files': 42, 'replaced_lines': 87, 'no_match_files': 5}
# 给没有目标的图补空标签(先 dry_run 预览)
fill_empty_labels("./datasets/images", "./datasets/labels", dry_run=True)
fill_empty_labels("./datasets/images", "./datasets/labels", dry_run=False)
# 夹紧越界坐标
normalize_labels("./datasets/labels")from tools import spilt_train_test
spilt_train_test(
li_path="./datasets/all", # 混合存放图片与标签的目录,或路径列表
train_img_path="./datasets/train/images",
val_img_path="./datasets/val/images",
train_label_path="./datasets/train/labels",
val_label_path="./datasets/val/labels",
val_size=0.2, # float=比例,int=数量
random_state=110, # 固定种子,划分可复现
negative_path="./datasets/negatives", # 负样本混入训练集,压制误检
upset_photo=True, # 先清空目标目录再划分
)from tools import train, evaluate, get_best_last_model, export_model
from tools import advanced_predict
# 训练
train(
model_selection="yolov11n.pt",
yaml_data="./datasets/data.yaml",
epochs=100,
imgsz=640,
batch=-1, # -1 = 自动
optimizer="Adam",
cos_lr=True,
seed_change=False, # False 固定 seed=1,便于复现对比
project="./runs/detect",
)
# 验证
metrics = evaluate("./runs/detect/train/weights/best.pt", "./datasets/data.yaml")
# 定位权重
best = get_best_last_model(mode=0) # mode=0 → best.pt;mode=1 → last.pt
# 推理(自绘框 + 中文标签)
advanced_predict(
model_selection=best,
img_path="./datasets/val/images",
conf=0.5,
font_path="C:/Windows/Fonts/simhei.ttf", # 给字体路径才能显示中文
replace_text={0: "裂缝", 1: "剥落"}, # 类别名整表替换
save=True, # 存到 ./results/
show=False, # 批量跑图务必置 False
)
# 导出 ONNX 部署
export_model(best, format="onnx")统计标签分布是数据治理的第一步。支持 YOLO txt 与 VOC xml 混放,并可按格式分流查看。
from tools import count_labels
stats = count_labels(
"./datasets/labels",
recursive=True, # 含子文件夹
label_exts=(".txt", ".xml"),
names={0: "裂缝", 1: "剥落"}, # ID → 名称,显示成 "0:裂缝"
verbose=False,
)返回结构:
| 键 | 含义 |
|---|---|
files |
扫描到的标签文件数 |
parsed_files |
成功解析的文件数 |
labels |
标注总数 |
counts |
{类别: 数量},按数量降序 |
by_format |
{扩展名: {类别: 数量}},只含出现过的格式 |
failed |
[(路径, 错误信息), ...],单文件解析失败不中断整体统计 |
txt 统计出的是类别 ID(
"0"),xml 统计出的是类别名("car")。混放时counts会同时出现两者,想分开看用by_format。
YOLO 训练中「背景图」需要空的标签文件。本函数为每张缺失标签的图片生成 0 字节 .txt。
from tools import fill_empty_labels
report = fill_empty_labels(
images_dir="./datasets/images",
labels_dir="./datasets/labels",
output_dir=None, # None = 就地补全到 labels_dir
recursive=False,
label_ext=".txt",
dry_run=True, # ⭐ 先预览,确认后再改 False
verbose=False,
)
# {'total': 1200, 'existed': 1180, 'created': 20, 'failed': 0, 'failures': []}spilt_train_test(
li_path="./datasets/all", # 目录 或 文件路径列表
train_img_path=..., val_img_path=...,
train_label_path=..., val_label_path=...,
label_type="txt",
negative_path=None, # 负样本目录,随机混入训练集
val_size=0.2, # float 比例 / int 数量
random_state=110,
upset_photo=False, # True 会先清空目标目录
need_test=False, # 需同时 upset_photo=True
)注意划分行为的两个坑:见注意点 4。
predict |
advanced_predict |
|
|---|---|---|
| 绘图 | ultralytics 内置 | 自绘(OpenCV 矩形 + 文字背景块) |
| 中文标签 | ❌ 不支持 | ✅ 传 font_path 即可 |
| 类别名替换 | 需改模型 names |
replace_text={0: "裂缝"} 整表替换 |
| 存图位置 | runs/detect/predict*/ |
./results/(相对当前工作目录) |
| 适用场景 | 快速看效果、标准出图 | 交付中文可视化结果、论文配图 |
from tools import export_model
export_model("./runs/detect/train/weights/best.pt", format="onnx")format 常用值:onnx / torchscript / openvino / engine(TensorRT,需匹配 CUDA 版本)。
产物与权重同目录、同名、不同后缀。
from tools import extract_frames
result = extract_frames(
input_path="./datasets/clip.mp4", # 文件 或 文件夹
output_dir="./datasets/frames",
every_n_frames=10, # 1 = 每帧都存
image_ext="jpg", # jpg / jpeg / png / bmp / webp
image_quality=100, # jpg、webp 生效;png 忽略
recursive=True,
flat_output=True, # False = 每个视频一个子文件夹
filename_prefix=True, # 图片名带视频名前缀
)
# {'results': [...], 'failures': [...], 'out_root': Path, 'total_saved': 350}支持中文路径(内部走 cv2.imencode + tofile,规避 OpenCV 在 Windows 上的中文路径问题)。
from tools import create_dirs, rm_dirs, fetch_specific_files, process_files, add_suffix_to_files
create_dirs("./out", overwrite=False) # None=交互确认 / True=清空重建 / False=保留
rm_dirs("./temp")
files = fetch_specific_files("./imgs", file_type=("jpg", "png"))
process_files("./src", "./dst", file_type="jpg", mode=0) # mode=0 复制 / 1 删除
add_suffix_to_files("./imgs", "_v2") # a.jpg → a_v2.jpg(目标已存在则跳过)| 函数 | 风险行为 | 是否可逆 |
|---|---|---|
rm_files |
物理删除图片及其同名标签,不进回收站 | ❌ |
rm_dirs |
递归删除整个目录 | ❌ |
process_files(mode=1) |
逐个物理删除源文件 | ❌ |
create_dirs(overwrite=True) |
清空并重建已存在的目录 | ❌ |
spilt_train_test(upset_photo=True) |
清空四个目标目录中的现有文件后再划分 | ❌ |
split_meta |
删除并重建 ./meta_split_results |
❌ |
coco_to_txt / coco_txt_BaiDu / voc_to_txt |
save_dir 已存在则整个删除重建 |
❌ |
rm_icc_profile |
就地覆盖写 PNG | ❌ |
normalize_labels |
就地覆盖写 txt 坐标 | ❌ |
replace_labels_with_target / batch_replace_labels |
就地覆盖写标签类别 | ❌ |
add_suffix_to_files |
就地重命名文件 | ❌ |
建议:动手前先备份,尤其是 rm_* 系列与 upset_photo=True。
tools/__init__.py 会 import 全部子模块,因此只要装了 tools 包就要求所有依赖齐备。仅想用轻量函数(如 count_labels,只依赖 PIL + tqdm)时,可绕过包 __init__ 直接导入子模块:
from tools.preprocess import count_labels # 不需要 ultralytics / cv2以下路径不是相对被处理文件,而是相对调用时的 CWD:
| 函数 | 输出位置 |
|---|---|
split_meta |
./meta_split_results/{split_name}{i}/ |
collcet_meta(save=True) |
./meta |
advanced_predict(save=True) |
./results/ |
predict(save=True) |
runs/detect/predict*/(ultralytics 约定) |
get_best_last_model |
默认 ./runs/detect/train/weights |
在别的目录调用脚本时会写到意料之外的地方——建议在项目根目录执行,或改用绝对路径。
该分支固定从训练集随机抽 100 张作测试集,因此要求:
- 必须同时设
upset_photo=True(否则不会触发清空分支); - 训练集样本数需 ≥ 100,否则
random.sample报错。
spilt_train_test 把图片与标签各自独立划分,只按文件名主干在 val 集合内做匹配,并不检查图与标签是否成对。因此:
- 有图无标签 → 这张图会照常进入 train/val,但训练时该样本无标注;
- 有标签无图 → 该标签文件也会被复制过去,成为孤儿标签。
建议先跑一遍配对检查再划分(或传 negative_path 之外自行清洗):
from tools import check_and_copy_missing_files, spilt_train_test
check_and_copy_missing_files(
"./datasets/all", "./datasets/all",
missing_dir="./datasets/_to_review", # 落单文件复制出来人工复核
)
spilt_train_test(...)另外 negative_path 只识别 *.jpg,且抽样数有硬上限——最多 2500 张,且不超过训练集规模的 10%;若该目录的 jpg 数量不足抽样数会抛 ValueError。
函数名 collcet_meta(应为 collect)是历史命名,作为公开 API 保留以免破坏调用方。它不是笔误的当前状态,而是既定接口。
copy_files(verbose=...):为兼容旧调用保留,当前不产生输出。normalize_labels内部用text.index(tt)定位待改写列。若某行的类别 ID 字符串恰好与某个 ≥1.0 的坐标值相同(如一行形如1 1 0.5 0.2 0.1),index()会命中第 0 列的类别 ID 并被改写成0.99999,导致该行类别被破坏。代码注释中已标注此风险,批量使用前建议先抽查。