Ultralytics 数据层工具函数全解析:从 `ultralytics/data/utils.py` 看图片校验、标签缓存与数据集装配
ultralytics/data/utils.py 是 Ultralytics 数据管线的“后勤中枢”:官方参考文档 docs/en/reference/data/utils.md 以 API Reference 形式罗列了该模块全部 24 个公开函数。这些函数承担着深度图读写、图像/标签校验、多边形转掩码、数据集 YAML 解析装配、扫描缓存存取等职责,是 ultralytics/data/dataset.py、ultralytics/data/base.py 乃至整个训练/验证/导出流程能够稳定工作的基础。阅读本文后,你将理解每条数据进入训练管线前经历了哪些校验,掌握 *.cache 文件与数据集装配的底层机制,并能直接复用这些函数处理自定义数据集。
一、模块概览:常量与工具族谱
在展开具体函数前,先认识模块级常量和整体分工,它们是理解后续行为的钥匙。
1.1 模块级常量
源码头部 定义了以下被全仓广泛引用的常量:
| 常量 | 值(类型) | 含义 |
|---|---|---|
HELP_URL |
"See https://docs.ultralytics.com/datasets ..." |
数据集格式指引提示语 |
IMG_FORMATS |
13 种后缀的 set |
支持的图像格式:avif/bmp/dng/heic/heif/jp2/jpeg/jpg/mpo/png/tif/tiff/webp |
VID_FORMATS |
12 种后缀的 set |
支持的视频格式:asf/avi/gif/m4v/mkv/mov/mp4/mpeg/mpg/ts/wmv/webm |
FORMATS_HELP_MSG |
字符串 | 汇总上述两类格式的报错提示 |
DATASET_KEY_TYPES |
dict |
数据 YAML 各键允许的 Python 类型(path→str;train/val/test→str/list;names→list/dict;kpt_shape/flip_idx→list) |
DEPTH_PNG_SCALE |
1000 |
深度 PNG 默认比例,即 uint16 值按毫米编码,见 depth 数据集配置 |
IMG_FORMATS 与 VID_FORMATS 是数据装载的类型白名单:文件后缀不在集合内即被拒绝,校验逻辑见后文 check_image。它们也被 ultralytics/data/base.py、ultralytics/data/build.py、ultralytics/data/loaders.py 以及 similarity_search.py 等模块复用,作为图像/视频判定的统一标准。
1.2 函数族谱
按职责可将 24 个函数分为六组,下文逐组讲解:
- 深度图编解码:
save_depth_png、load_depth、verify_image_depth; - 图像与路径基础操作:
img2label_paths、check_file_speeds、get_hash、exif_size; - 图像/标签校验族:
check_image、verify_image、verify_image_label、verify_image_mask、visualize_image_annotations; - 多边形与掩码转换:
polygon2mask、polygons2masks、polygons2masks_overlap; - 数据集解析与装配:
find_dataset_yaml、get_split_fraction、convert_ndjson_to_yolo_if_needed、check_det_dataset、check_cls_dataset; - 缓存与杂项工具:
load_dataset_cache_file、save_dataset_cache_file、compress_one_image、add_polygon_background。
二、深度图编解码:save_depth_png / load_depth
DEPTH_PNG_SCALE = 1000 表示“以毫米为单位存储、值 0 视为无效像素”。两个函数构成一对互逆的读写接口,在深度估计(depth)任务的数据装配中扮演核心角色,并被 test_ndjson_converter.py 与 test_python.py 直接调用验证。
2.1 保存:save_depth_png(path, depth, scale=DEPTH_PNG_SCALE)
将“以米为单位的浮点深度图”编码为缩放后的 uint16 PNG:
scale必须是正的有限数值(拒绝布尔/非数/非正数),否则抛ValueError;- 输入
depth会被转换为float32并squeeze(),非 2D 抛ValueError; - 有效像素 = 有限值且大于 0;无效(含
0、NaN、inf)统一编码为0; - 有效值乘
scale后四舍五入,但np.maximum(scaled, 1)保证不小于 1(避免与“无效=0”冲突);若超出 uint16 上限(65535/scale米)抛ValueError; - 底层用
cv2.imwrite落盘,失败抛OSError。
depth 类数据集的自动下载脚本中直接引用它,例如 depth-diode.yaml、depth-sunrgbd.yaml、depth-vkitti2.yaml。各数据集的比例约定记录在 depth8.yaml:ARKitScenes 与 NYU-Depth-V2 用 1000(1mm、上限 65.535m),KITTI 用 256,Virtual KITTI 2 用 100(1cm)。
2.2 读取:load_depth(path, scale=DEPTH_PNG_SCALE)
对称地读取深度图,兼容两种存储:
.npy分支:要求 2D 浮点数组,随后np.nan_to_num把nan/±inf置 0;.png分支:用 PIL 打开,要求格式为 PNG 且模式为I或I;16(16 位整数灰度),否则抛ValueError;读取后转float32并除以scale还原成米。
三、图像与路径基础操作
3.1 路径映射:img2label_paths(img_paths, label_dir="labels", suffix=".txt")
将图像路径列表映射为标注路径列表:把路径中的 /{images}/ 片段替换为 /{label_dir}/,并替换扩展名。是 YOLO 约定“图像在 images/、标签在 labels/”的程序化实现。
- 检测/分割/姿态默认调用:
img2label_paths(self.im_files),见 dataset.py; - 语义分割使用自定义目录与后缀:
img2label_paths(self.im_files, label_dir=self.data.get("masks_dir", "masks"), suffix=".png"),见 dataset.py; - 数据集划分工具 split.py 与 DOTA 切割工具 split_dota.py 同样复用它同步搬运标签。
3.2 文件读取速度体检:check_file_speeds(files, threshold_ms=10, threshold_mb=50, max_files=5, prefix="")
对数据集文件访问速度做抽样检测并给出反馈,用于诊断“数据放在远程/挂载存储导致训练变慢”的问题。默认在 dataset.py 的 __init__ 热路径中被调用一次:check_file_speeds(im_files, prefix=self.prefix)(见 base.py 对应封装)。
工作机制:
- 随机抽样至多
max_files(默认 5)个文件; - 用
os.stat测 ping(stat 调用延迟,ms),用全量read()测 读速(MB/s),异常文件静默跳过; - 输出均值±标准差:当
avg_ping < threshold_ms且avg_speed > threshold_mb记为Fast image access ✅,否则警告Slow image access detected,并建议改用本地存储。
3.3 数据集指纹:get_hash(paths)
对一组文件或目录路径求单一哈希:累加所有 os.stat 得到的总字节数,再拼上路径字符串,用 sha256 计算并返回 hexdigest。它是缓存有效性校验的基石——dataset.py 的 get_cache_hash() 即 get_hash(self.label_files + self.im_files),训练/验证扫描图片与标签文件变化时缓存自动失效重建。
3.4 EXIF 矫正尺寸:exif_size(img)
返回经 EXIF 旋转矫正后的 PIL 图像尺寸。仅对 JPEG 生效:读取 EXIF orientation 标签(key 为 274),当旋转值为 6 或 8(即旋转 270°/90°)时交换宽高。手机拍摄照片常带方向信息,此函数保证校验/装配阶段拿到的宽高与像素真实布局一致。
四、图像与标签校验族:质量关卡与扫描缓存
这一族是理解 Ultralytics 启动阶段“Scanning labels …”日志的关键。所有 verify_* 校验函数都按“进程池可迭代”设计:返回固定长度的元组,由 dataset.py 的扫描器通过 ThreadPool.imap 并发消费。
4.1 单图校验:check_image(im_file) -> (msg, shape)
对单个图像文件做三类检查,并返回 (修正信息, (高, 宽)):
Image.open+im.verify()验证解码完整性;exif_size后断言宽高都大于 9 像素(<10 pixels报错);- 断言格式后缀属于
IMG_FORMATS。 - 额外修复:对
jpg/jpeg,检查文件末尾两字节是否为 JPEG 结束标记\xff\xd9,若是损坏 JPEG 则用ImageOps.exif_transpose(...).save(..., quality=100)自动修复并返回“corrupt JPEG restored”信息。
4.2 并行校验与计数
以下函数统一返回“找到/缺失/损坏/空”等计数与提示,供扫描器汇总成 {nf} images, {nm+ne} backgrounds, {nc} corrupt 的进度统计(见 dataset.py)。
verify_image(args):最简封装,args=(im_file, cls), prefix,成功则 nf=1,异常则 nc=1 并忽略损坏文件。
verify_image_depth(args)(用于深度任务):校验图像及其配对深度图存在且可读。对 .npy 要求 2D 浮点;对 PNG 要求 I/I;16 模式;并用对数宽高比差 abs(log((dw/dh)/(iw/ih))) <= 0.02 约束深度图与图像纵横比一致。深度文件缺失计 nm,损坏计 nc。
verify_image_mask(args)(用于语义分割):校验图像与语义掩码。掩码缺失时会在 IMG_FORMATS 内尝试其他扩展名的候选文件;掩码用 cv2.imread(..., IMREAD_GRAYSCALE) 读取并断言尺寸与图像一致;若开启 check_bit_depth,还会用 PIL 检测掩码是否为 1-bit 模式(mode == "1",用于识别 8-bit 调色板 PNG 这类特殊情况)。
verify_image_label(args)(检测/分割/姿态主校验)是内容最丰富的函数,入参为 (im_file, lb_file, prefix, keypoint, num_cls, nkpt, ndim, single_cls),处理逻辑:
- 若标签行含 >6 列且非姿态任务,判定为分割标注:断言不存在 5 列混排行,把多边形点解析为 segments,并用
segments2boxes(来自 ultralytics/utils/ops.py)转成 xywh 框; - 姿态任务要求每行
5 + nkpt*ndim列,并把lb[:, 5:]组织为关键点坐标; - 归一化越界检查:坐标允许 1% 容差(
points.max() <= 1.01),拒绝负类别/负坐标(lb.min() >= -0.01); - 类别越界检查:
max_cls < num_cls(single_cls时按 0 处理); - 重复行清理:
np.unique(..., return_index=True)去重并同步 segments,提示“duplicate labels removed”; - 空文件计
ne,缺失计nm,返回结构包含segments与keypoints。
调用端见 dataset.py 的 verify_args:把图片/标签路径与 repeat(prefix/use_keypoints/num_cls/nkpt/ndim/single_cls) 打包成 zip 迭代器喂给 verify_image_label,结果再经 result_to_label 转成含 cls/bboxes/segments/keypoints 的 label dict 供训练加载(dataset.py)。
4.3 校验可视化:visualize_image_annotations(image_path, txt_path, label_map)
把 YOLO 格式标注绘制回图像上,便于人工核对标签质量。逐行解析 class_id x_center y_center width height,换算为像素坐标后用 matplotlib 绘制矩形与类别名;边框颜色按类别 ID 从 ultralytics.utils.plotting.colors 分配,文字颜色根据背景亮度(0.2126R+0.7152G+0.0722B)在黑白间动态选择以保证可读。
五、多边形转掩码:实例/语义分割的骨架
分割任务在 cache_labels 阶段把多边形标注栅格化为掩码。该族函数被数据增强模块 ultralytics/data/augment.py(如 Mosaic 中的掩码拼接)直接引用,是 LoadMosaic/RandomAffine 等增强器处理实例掩码的底层依赖。
5.1 polygon2mask(imgsz, polygons, color=1, downsample_ratio=1) -> np.ndarray
把一组多边形填充到 (H, W) 的全零 uint8 掩码上,用 cv2.fillPoly 以指定 color 填充;随后按 downsample_ratio 用 cv2.resize 下采样。源码注释特别说明“先 fillPoly 再 resize”是为了保持 mask-ratio=1 时与损失计算一致的填充语义。
5.2 polygons2masks(imgsz, polygons, color, downsample_ratio=1)
逐实例调用 polygon2mask 并把结果堆叠成 np.ndarray,得到“每个实例一张二值掩码”的张量。
5.3 polygons2masks_overlap(imgsz, segments, downsample_ratio=1) -> (masks, index)
返回单张“重叠实例编码掩码”与按面积降序排序的实例索引:
- 掩码 dtype 自适应:实例数 >255 用
int32,否则uint8; - 按实例面积
argsort(-areas)降序; - 使用“running max”逐层写入:
np.maximum(masks, ms[i]*(i+1)),避免旧实现masks + mask在超过 128 层重叠时 uint8 溢出(源码注释明确说明此改进动机)。数据集cache_labels中即用polygons2masks_overlap((h, w), polys, downsample_ratio=1)构建目标掩码(见 dataset.py),test_python.py 也有对应回归测试。
六、数据集解析与装配:从 YAML 到可训练数据字典
这一组回答“yolo train data=coco8.yaml 背后发生了什么”。
6.1 定位 YAML:find_dataset_yaml(path)
给定数据集目录,先在根层级 glob("*.yaml"),找不到再递归 rglob;多个结果时优先选择与目录同 stem 的 YAML;若仍不唯一则抛断言错误,保证装配元信息只有一个权威来源。
6.2 划分比例归一:get_split_fraction(fraction, split)
处理 fraction 参数三种形态:列表(按 train/val/test 顺序取对应项,越界补 1.0)、单值浮点、0/1 边界值归一为浮点。规则是:仅 train/val/test 三键支持列表分派;非 train 划分未指定时默认为 1.0;train 与 val 的比例不能为 0(抛 ValueError,保证至少选中一张图)。它被 base.py、build.py 以及 RTDETR/YOLO 的 train.py、val.py 多处复用。
6.3 NDJSON/平台数据集自动转换:convert_ndjson_to_yolo_if_needed(data, fraction=1.0)
先 normalize_platform_uri 统一平台 ul:// URI,再判断是否以 .ndjson 结尾或属于平台数据集路径;是则异步调用 ultralytics/data/converter.py 的 convert_ndjson_to_yolo(data, fraction=fraction) 转为 YOLO 目录结构。训练/验证入口会在装配前统一调用它,见 trainer.py 与 validator.py。
6.4 检测数据集装配:check_det_dataset(dataset, autodownload=True, split="")
返回包含 train/val/test/names/nc/path/channels 等键的完整数据集字典,是 yolo 命令行与 model.train()/model.val() 的统一入口(trainer 内 check_det_dataset(self.args.data) 见 trainer.py)。主要步骤:
- 名称补全:裸数据集名(如
coco8)自动尝试补.yaml/.yml后缀; - 定位 YAML:目录输入转
find_dataset_yaml;zip/tar 压缩包先safe_download解压到DATASETS_DIR; - 键类型校验:按
DATASET_KEY_TYPES逐键断言类型,非法抛TypeError; - 必填键校验:
train/val必填(允许validation别名并自动改名告警);names与nc至少其一;nc必须是整数型(接受'10'/10.0这类可整化值);两者同时给出时长度必须一致;缺names时自动补class_{i}; - 类名规范化:
check_class_names(来自 ultralytics/nn/autobackend.py)校验过滤类名;默认channels=3; - 路径解析:相对路径以
DATASETS_DIR为基准,train/val/test/minival都拼到根路径并.resolve(),兼容../相对写法; - 自动下载:校验通过后若目标
val图像仍缺失,会执行 YAML 中download键——支持 http zip URL、bash ...命令和 Python 脚本三种形态,如 coco8.yaml 末行的 zip URL; - 字体预检:根据类名是否纯 ASCII 下载
Arial.ttf或Arial.Unicode.ttf(供标注可视化使用)。
6.5 分类数据集装配:check_cls_dataset(dataset, split="")
面向 ImageNet 风格的“目录即类别”结构:本地缺失时自动下载(imagenet 走 get_imagenet.sh,其余从 assets 下载 zip);若仅有散落图片则调用 split.py 的 split_classify_dataset(data_dir, train_ratio=0.8) 按 8:2 切分。类名来源:存在 .ndjson.yaml 时取其中 names,否则对 train 子目录按字典序 enumerate。返回含 train/val/test/nc/names/channels 的字典,并在控制台打印各 split 的图像数与类数统计。
七、扫描缓存与杂项工具
7.1 缓存写入:save_dataset_cache_file(prefix, path, x, version)
把扫描结果字典 x 序列化为 *.cache 文件:先写入 version 键,校验父目录可写(is_dir_writeable),删除旧文件后用 np.save 保存;失败则清理半成品并告警。调用点集中在 dataset.py 与 dataset.py:检测/分割/姿态/语义等各任务扫描完成后统一落盘。
7.2 缓存读取:load_dataset_cache_file(path) -> dict
读取 *.cache:先 gc.disable() 降低 pickle 反序列化耗时(源码注明参考了 upstream issue #1585 的优化),np.load(..., allow_pickle=True).item() 还原字典后再 gc.enable()。缓存命中路径见 dataset.py 与 dataset.py。
整套缓存机制配合 get_hash 工作:首次扫描生成带版本号与数据集指纹的 *.cache,后续启动若指纹匹配则跳过逐图校验,这是 Ultralytics 重复训练时“Scanning”阶段能瞬间完成的原因。
7.3 单图压缩:compress_one_image(f, f_new=None, max_dim=1920, quality=50)
在保持宽高比的前提下压缩单张图,小于 max_dim 的图不做缩放。优先走 PIL:关闭 MAX_IMAGE_PIXELS 防 DecompressionBomb 误报,RGBA/LA 先转 RGB,按 quality 以 optimize=True 存 JPEG;PIL 失败时降级 OpenCV cv2.resize(INTER_AREA) + cv2.imwrite。适合批量瘦身数据集,docstring 示例展示了 for f in Path(...).rglob("*.jpg") 的遍历用法。
7.4 多边形语义背景类:add_polygon_background(data) -> dict
为“基于多边形、无 masks_dir”的语义分割数据集补齐背景类:
- 已有
masks_dir或已处理过(_polygon_bg_added)则直接返回; nc == 1(二分类):保持单通道,置bg_class_idx = 0,栅格化结果天然为{0=bg, 1=fg};nc > 1:把"background"追加为id=nc的新类,data["nc"]变为nc+1,原多边形 cls 值维持前景编号不变;- 标记
_polygon_bg_added = True防止重复处理。
它被语义分割训练/验证装配调用,见 semantic/train.py 与 semantic/val.py。
八、在项目中的组合运用:一次数据扫描的完整链路
把上述函数串起来,即可还原 Ultralytics 每次训练启动时的“数据集装配 → 图像校验 → 缓存复用”流程:
yolo train data=coco8.yaml
└─ trainer.setup_model / preprocess: convert_ndjson_to_yolo_if_needed(data)
└─ check_det_dataset(data) # find_dataset_yaml → 键/类名校验 → 路径解析 → 自动下载
└─ build / BaseDataset.__init__
├─ get_split_fraction(fraction, "train")
├─ img2label_paths(im_files) # images/ → labels/
├─ check_file_speeds(im_files) # 存储介质体检
└─ cache_labels():
├─ get_hash(im_files + label_files) # 缓存指纹
├─ load_dataset_cache_file # 命中则跳过
└─ ThreadPool.imap(verify_image_label, ...)
├─ check_image → exif_size 修正
├─ 分割行 → segments2boxes → polygons2masks_overlap
├─ 姿态行 → kpt_mask 补可见性通道
└─ save_dataset_cache_file # 落盘复用
其中每类任务的校验入口由 verify_args 选择性返回:检测/姿态/实例分割走 verify_image_label,深度任务走 verify_image_depth(dataset.py),语义分割走 verify_image_mask(dataset.py),而纯图像走 verify_image(dataset.py)。
九、小结与使用建议
ultralytics/data/utils.py 的设计特点值得在自定义数据管线中借鉴:
- 校验结果标准化为定长元组 + 计数:
nf/nm/ne/nc四类计数与消息一起返回,天然适配ThreadPool.imap并行扫描与进度汇总,避免在 worker 内打日志造成乱序; - 缓存先行、指纹护航:
get_hash+load/save_dataset_cache_file让二次启动几乎零成本,而版本号与文件指纹共同防止脏缓存; - 校验策略“宽容但设防”:越界坐标容忍 1% 容差、自动修复损坏 JPEG、自动尝试掩码其他扩展名,同时严格拒绝类别越界与格式非法,兼顾友好与严谨;
- 装配逻辑与校验分离:
check_det_dataset/check_cls_dataset负责“找到并读对”,BaseDataset负责“逐条验并缓存”,两者通过标准 YAML 字典契约衔接。
需要自行接入数据时,优先直接导入这些经过生产检验的函数:深度数据用 save_depth_png/load_depth,掩码预处理用 polygons2masks_overlap,目录型分类数据交给 check_cls_dataset。更多数据集格式规范可参考 detect 数据集示例、深度数据集示例 以及分类/分割等 cfg/datasets 下的其余 YAML。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00