首页
/ Ultralytics 数据层工具函数全解析:从 `ultralytics/data/utils.py` 看图片校验、标签缓存与数据集装配

Ultralytics 数据层工具函数全解析:从 `ultralytics/data/utils.py` 看图片校验、标签缓存与数据集装配

2026-09-07 16:49:42作者:柯茵沙

ultralytics/data/utils.py 是 Ultralytics 数据管线的“后勤中枢”:官方参考文档 docs/en/reference/data/utils.md 以 API Reference 形式罗列了该模块全部 24 个公开函数。这些函数承担着深度图读写、图像/标签校验、多边形转掩码、数据集 YAML 解析装配、扫描缓存存取等职责,是 ultralytics/data/dataset.pyultralytics/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_FORMATSVID_FORMATS 是数据装载的类型白名单:文件后缀不在集合内即被拒绝,校验逻辑见后文 check_image。它们也被 ultralytics/data/base.pyultralytics/data/build.pyultralytics/data/loaders.py 以及 similarity_search.py 等模块复用,作为图像/视频判定的统一标准。

1.2 函数族谱

按职责可将 24 个函数分为六组,下文逐组讲解:

  1. 深度图编解码save_depth_pngload_depthverify_image_depth
  2. 图像与路径基础操作img2label_pathscheck_file_speedsget_hashexif_size
  3. 图像/标签校验族check_imageverify_imageverify_image_labelverify_image_maskvisualize_image_annotations
  4. 多边形与掩码转换polygon2maskpolygons2maskspolygons2masks_overlap
  5. 数据集解析与装配find_dataset_yamlget_split_fractionconvert_ndjson_to_yolo_if_neededcheck_det_datasetcheck_cls_dataset
  6. 缓存与杂项工具load_dataset_cache_filesave_dataset_cache_filecompress_one_imageadd_polygon_background

二、深度图编解码:save_depth_png / load_depth

DEPTH_PNG_SCALE = 1000 表示“以毫米为单位存储、值 0 视为无效像素”。两个函数构成一对互逆的读写接口,在深度估计(depth)任务的数据装配中扮演核心角色,并被 test_ndjson_converter.pytest_python.py 直接调用验证。

2.1 保存:save_depth_png(path, depth, scale=DEPTH_PNG_SCALE)

将“以米为单位的浮点深度图”编码为缩放后的 uint16 PNG:

  • scale 必须是正的有限数值(拒绝布尔/非数/非正数),否则抛 ValueError
  • 输入 depth 会被转换为 float32squeeze(),非 2D 抛 ValueError
  • 有效像素 = 有限值且大于 0;无效(含 0NaNinf)统一编码为 0
  • 有效值乘 scale 后四舍五入,但 np.maximum(scaled, 1) 保证不小于 1(避免与“无效=0”冲突);若超出 uint16 上限(65535/scale 米)抛 ValueError
  • 底层用 cv2.imwrite 落盘,失败抛 OSError

depth 类数据集的自动下载脚本中直接引用它,例如 depth-diode.yamldepth-sunrgbd.yamldepth-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_numnan/±inf 置 0;
  • .png 分支:用 PIL 打开,要求格式为 PNG 且模式为 II;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 对应封装)。

工作机制:

  1. 随机抽样至多 max_files(默认 5)个文件;
  2. os.statping(stat 调用延迟,ms),用全量 read()读速(MB/s),异常文件静默跳过;
  3. 输出均值±标准差:当 avg_ping < threshold_msavg_speed > threshold_mb 记为 Fast image access ✅,否则警告 Slow image access detected,并建议改用本地存储。

3.3 数据集指纹:get_hash(paths)

对一组文件或目录路径求单一哈希:累加所有 os.stat 得到的总字节数,再拼上路径字符串,用 sha256 计算并返回 hexdigest。它是缓存有效性校验的基石——dataset.pyget_cache_hash()get_hash(self.label_files + self.im_files),训练/验证扫描图片与标签文件变化时缓存自动失效重建。

3.4 EXIF 矫正尺寸:exif_size(img)

返回经 EXIF 旋转矫正后的 PIL 图像尺寸。仅对 JPEG 生效:读取 EXIF orientation 标签(key 为 274),当旋转值为 68(即旋转 270°/90°)时交换宽高。手机拍摄照片常带方向信息,此函数保证校验/装配阶段拿到的宽高与像素真实布局一致。

四、图像与标签校验族:质量关卡与扫描缓存

这一族是理解 Ultralytics 启动阶段“Scanning labels …”日志的关键。所有 verify_* 校验函数都按“进程池可迭代”设计:返回固定长度的元组,由 dataset.py 的扫描器通过 ThreadPool.imap 并发消费。

4.1 单图校验:check_image(im_file) -> (msg, shape)

对单个图像文件做三类检查,并返回 (修正信息, (高, 宽))

  1. Image.open + im.verify() 验证解码完整性;
  2. exif_size 后断言宽高都大于 9 像素(<10 pixels 报错);
  3. 断言格式后缀属于 IMG_FORMATS
  4. 额外修复:对 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_clssingle_cls 时按 0 处理);
  • 重复行清理np.unique(..., return_index=True) 去重并同步 segments,提示“duplicate labels removed”;
  • 空文件计 ne,缺失计 nm,返回结构包含 segmentskeypoints

调用端见 dataset.pyverify_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_ratiocv2.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.0trainval 的比例不能为 0(抛 ValueError,保证至少选中一张图)。它被 base.pybuild.py 以及 RTDETR/YOLO 的 train.pyval.py 多处复用。

6.3 NDJSON/平台数据集自动转换:convert_ndjson_to_yolo_if_needed(data, fraction=1.0)

normalize_platform_uri 统一平台 ul:// URI,再判断是否以 .ndjson 结尾或属于平台数据集路径;是则异步调用 ultralytics/data/converter.pyconvert_ndjson_to_yolo(data, fraction=fraction) 转为 YOLO 目录结构。训练/验证入口会在装配前统一调用它,见 trainer.pyvalidator.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)。主要步骤:

  1. 名称补全:裸数据集名(如 coco8)自动尝试补 .yaml/.yml 后缀;
  2. 定位 YAML:目录输入转 find_dataset_yaml;zip/tar 压缩包先 safe_download 解压到 DATASETS_DIR
  3. 键类型校验:按 DATASET_KEY_TYPES 逐键断言类型,非法抛 TypeError
  4. 必填键校验train/val 必填(允许 validation 别名并自动改名告警);namesnc 至少其一;nc 必须是整数型(接受 '10'/10.0 这类可整化值);两者同时给出时长度必须一致;缺 names 时自动补 class_{i}
  5. 类名规范化check_class_names(来自 ultralytics/nn/autobackend.py)校验过滤类名;默认 channels=3
  6. 路径解析:相对路径以 DATASETS_DIR 为基准,train/val/test/minival 都拼到根路径并 .resolve(),兼容 ../ 相对写法;
  7. 自动下载:校验通过后若目标 val 图像仍缺失,会执行 YAML 中 download 键——支持 http zip URL、bash ... 命令和 Python 脚本三种形态,如 coco8.yaml 末行的 zip URL;
  8. 字体预检:根据类名是否纯 ASCII 下载 Arial.ttfArial.Unicode.ttf(供标注可视化使用)。

6.5 分类数据集装配:check_cls_dataset(dataset, split="")

面向 ImageNet 风格的“目录即类别”结构:本地缺失时自动下载(imagenetget_imagenet.sh,其余从 assets 下载 zip);若仅有散落图片则调用 split.pysplit_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.pydataset.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.pydataset.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,按 qualityoptimize=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.pysemantic/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_depthdataset.py),语义分割走 verify_image_maskdataset.py),而纯图像走 verify_imagedataset.py)。

九、小结与使用建议

ultralytics/data/utils.py 的设计特点值得在自定义数据管线中借鉴:

  1. 校验结果标准化为定长元组 + 计数nf/nm/ne/nc 四类计数与消息一起返回,天然适配 ThreadPool.imap 并行扫描与进度汇总,避免在 worker 内打日志造成乱序;
  2. 缓存先行、指纹护航get_hash + load/save_dataset_cache_file 让二次启动几乎零成本,而版本号与文件指纹共同防止脏缓存;
  3. 校验策略“宽容但设防”:越界坐标容忍 1% 容差、自动修复损坏 JPEG、自动尝试掩码其他扩展名,同时严格拒绝类别越界与格式非法,兼顾友好与严谨;
  4. 装配逻辑与校验分离check_det_dataset/check_cls_dataset 负责“找到并读对”,BaseDataset 负责“逐条验并缓存”,两者通过标准 YAML 字典契约衔接。

需要自行接入数据时,优先直接导入这些经过生产检验的函数:深度数据用 save_depth_png/load_depth,掩码预处理用 polygons2masks_overlap,目录型分类数据交给 check_cls_dataset。更多数据集格式规范可参考 detect 数据集示例深度数据集示例 以及分类/分割等 cfg/datasets 下的其余 YAML。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388