简介:这套YOLO格式的工业液滴气泡多目标检测数据集,面向工业视觉、流体力学与人工智能检测开发场景,聚焦气液两相流中单液滴、单气泡、液泡混合体及高密度液滴群的定位识别,适合化工能源管道监测、流体实验、水体净化等方向。资源共2000个文件,整体大小约26.32MB,以1971个txt标注文件为主体,另含27张jpg图像、1个yaml配置和1份docx说明文档;数据划分为训练集1219张、验证集752张,覆盖不同光照、拍摄角度及稀疏与密集等流体状态。已有335人学习。数据集标注经流体力学专家校验,四级分类体系严格,包含气泡破裂、液滴聚合等动态过程的连续帧数据,可直接用于YOLOv12等模型的训练与验证,也可支持密集小目标识别、多尺度目标评测,以及两相流数值模拟、工业数字孪生视觉模块等科研与工程任务。
1. 工业液滴气泡多目标检测数据集.zip:一个压缩包里装的不只是图片
拿到“工业液滴气泡多目标检测数据集.zip”这个名字,第一反应往往是:解压,看一下,扔给 yolov8 开始训练。真正在产线上摸过一轮的人才明白,这个压缩包里装的不是“图片加标签”,而是一整套关于目标怎么定义、边界怎么画、分布怎么切、格式怎么转的博弈。工业场景里的液滴和气泡,出现在喷墨打印、喷淋降温、微流控反应、电池注液这类环节,目标小、边缘糊、反光强,背景还有纹理干扰,通用检测模型直接拿过来训练,mAP 常常很难看。这类数据集的价值不在“有多少张图”,而在标注规范、分布设计和格式转换这些看不见的工序。这篇文章就按一线工程师处理这类数据集的常规做法,把从解压到训练、再到把数据资产做厚的路径走一遍。适合准备做工业视觉检测、用 yolov8 或 mmrotate 训练自己数据的算法工程师。
2. 数据集的结构与标注:多目标检测的关卡在数据工艺
2.1 先看压缩包结构,再谈模型训练
拿到 zip 之后,我一般不会急着解压到当前目录,而是先列一下压缩包内容,心里有个底。
unzip -l 工业液滴气泡多目标检测数据集.zip | head -60这条命令会把 zip 内的文件清单打印出来。重点看三件事:images和labels是否分目录;标签文件是.txt还是.json;有没有train.txt / val.txt这类划分文件。常见的数据集组织方式是:
工业液滴气泡多目标检测数据集/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── train.txt ├── val.txt └── README.md如果看到的是这种结构,说明数据集已经按 YOLO 惯例组织好了。注意看README.md里写了什么:类别名、标注坐标系、拍摄环境、是否包含遮挡目标。这些信息决定了你的预处理和后处理策略,不要跳过。
2.2 液滴气泡为什么要单独定义标注规范
通用目标检测数据集里,目标是“人、车、猫”,边界相对清楚。液滴和气泡不一样,透明、边缘与背景亮度接近,有时连人眼都分不清边界。这带来三个标注特异点,直接决定检测上限。
第一个是边界定义。气泡在光源照射下会出现高光环,标注框到底框到高光内侧还是外侧?不同标注员的习惯不同,会导致同一个目标的框大小有 10% 到 20% 的波动。处理这类数据集,我会在预处理阶段统一做一次缩放:把所有框按原始标注的 0.95 系数向中心收缩,消除标注员之间的边缘冗余。
第二个是目标尺寸分布。工业场景中液滴常常只有十几个像素,属于典型小目标。小目标在标注时容易被漏标,一个漏标的前景会变成训练时的难负样本,直接把 recall 拉下来。评估一个数据集能不能用,先看你标注里小于 32×32 像素的框占比,超过 40% 就要在训练时用小目标增强策略,而不是靠加大 mosaic 碰运气。
第三个是密集遮挡。喷雾场景中相邻液滴几乎贴在一起,重叠区域大,这时候标注框的严格边界比类别本身更敏感。密集目标场景建议用旋转框标注,而不是普通水平框,否则 NMS 阶段会产生大量误抑制。这个问题在第 5 章展开。
2.3 标注格式互转:txt 与 json 的桥梁
大部分工业数据集交付时给的是 YOLO 格式的 txt,每行一个目标,结构是class_id x_center y_center width height,坐标都是相对图片宽高的归一化值。这种格式的好处是简单,一个文件夹加同名 txt 就完成了标注。坏处是扩展性差:没有 segmentation、没有属性字段、没有数据集级元信息。
如果你后面要切换检测框架,比如从 YOLO 换到 mmrotate 或 Detectron2,需要转成 COCO json。coco2017 数据集结构之所以成为事实标准,就是因为它把 images、annotations、categories 拆成三个数组,注解字段可扩展。下面这段代码把 YOLO txt 转成 COCO 格式:
import json import os from PIL import Image def yolo_txt_to_coco(images_dir, labels_dir, class_names, output_json): coco = { "images": [], "annotations": [], "categories": [{"id": i, "name": n} for i, n in enumerate(class_names)] } ann_id = 0 for img_name in sorted(os.listdir(images_dir)): if not img_name.endswith(('.jpg', '.png', '.bmp')): continue img_path = os.path.join(images_dir, img_name) with Image.open(img_path) as img: width, height = img.size coco["images"].append({ "id": len(coco["images"]), "file_name": img_name, "width": width, "height": height }) txt_path = os.path.join(labels_dir, os.path.splitext(img_name)[0] + '.txt') if not os.path.exists(txt_path): continue with open(txt_path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id, x_c, y_c, w, h = map(float, parts) x = (x_c - w / 2) * width y = (y_c - h / 2) * height w_pix, h_pix = w * width, h * height coco["annotations"].append({ "id": ann_id, "image_id": coco["images"][-1]["id"], "category_id": int(cls_id), "bbox": [x, y, w_pix, h_pix], "area": w_pix * h_pix, "iscrowd": 0 }) ann_id += 1 with open(output_json, 'w') as f: json.dump(coco, f, indent=2) yolo_txt_to_coco("images/train", "labels/train", ["droplet", "bubble"], "train_coco.json")这段代码把 txt 中的归一化坐标乘上图片宽高,转成 COCO 需要的像素坐标。iscrowd置 0 表示普通目标;如果原数据集标了密集群体的轮廓,可以把iscrowd置 1,在评估时跳过该框。转之前先统计 txt 里的坐标是否都在 0 到 1 之间,这一步能拦掉 80% 的文件格式错误。转换后的 json 用任何支持 COCO 的可视化脚本打开检查一遍,再进入下一步。
2.4 划分数据集:按来源分组,而不是随机打散
模型训练前要做 train/val 划分,最容易犯的错是直接random_split。工业数据集往往是连续采样的:同一段视频抽帧得到的几十帧,背景几乎一样,液滴位置只差几个像素。如果这些相似帧同时落在 train 和 val 里,验证集指标会虚高,实际部署到新工位就露馅。
正确的做法是按采样来源分组。数据集的目录如果按日期或机台号组织,比如20240112_camera1_shot01/,就把相同前缀的图片分到同一组,再按组划分:
import os import random images = os.listdir("images") groups = {} for img in images: group = img.split("_")[0] + "_" + img.split("_")[1] # 按日期和相机分组 groups.setdefault(group, []).append(img) all_groups = list(groups.keys()) random.Random(42).shuffle(all_groups) val_group_count = max(1, int(len(all_groups) * 0.2)) val_groups = set(all_groups[:val_group_count]) with open("train.txt", "w") as ft, open("val.txt", "w") as fv: for group, imgs in groups.items(): for img in imgs: line = os.path.join("images", img) + "\n" if group in val_groups: fv.write(line) else: ft.write(line)按组划分后,val 里的场景分布和 train 不再重叠,训练的泛化评估才有意义。注意random.seed固定下来,否则同一份数据每次划分结果不同,后续实验对比就不公平了。
3. 解压后的第一件事:用数据体检脚本把 zip 变成可信任的训练集
3.1 校验完整性与目录一致性
从网上下载的数据集,尤其是 zip 这种压缩包,第一件事不是解压而是校验。zip 文件在传输过程中可能截断,也可能在 Windows 上解压后出现中文文件名乱码。我的常规流程是两步:
unzip -t 工业液滴气泡多目标检测数据集.zip-t参数逐文件测试 zip 的 CRC 校验和,只要有一个文件报错,就说明压缩包损坏,后面训练出的模型没有任何参考价值。测试通过后再解压:
unzip 工业液滴气泡多目标检测数据集.zip -d ./dataset解压后我会跑一个一致性检查:确认每一张图片都有对应的标注文件,每一个标注文件都有对应的图片。图片和 txt 数量不一致,通常是打包时丢文件,或者后期人工删除图片时没同步删标签。这一步用一行 Python 就能做完:
import os from pathlib import Path img_dir = Path("dataset/images/train") lbl_dir = Path("dataset/labels/train") img_files = {p.stem for p in img_dir.glob("*.jpg")} | {p.stem for p in img_dir.glob("*.png")} lbl_files = {p.stem for p in lbl_dir.glob("*.txt")} print("图片无标签:", img_files - lbl_files) print("标签无图片:", lbl_files - img_files)输出里如果出现文件,先检查这些文件是否真的无用,再决定是补标注还是从训练集剔除。
3.2 标注分布统计:类别、尺寸、密度一个都不能少
判定数据集质量最有效的手段,是统计目标的类别分布和尺寸分布。下面是分析 YOLO 标注的核心脚本:
import os import numpy as np from collections import Counter def analyze_annotations(labels_dir, img_size_map): cls_counter = Counter() sizes = [] boxes_per_img = [] aspect_ratios = [] for txt_name in os.listdir(labels_dir): if not txt_name.endswith('.txt'): continue img_w, img_h = img_size_map[txt_name[:-4]] count = 0 with open(os.path.join(labels_dir, txt_name)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls, xc, yc, w, h = map(float, parts) w_pix, h_pix = w * img_w, h * img_h sizes.append(max(w_pix, h_pix)) aspect_ratios.append(w_pix / max(h_pix, 1e-6)) cls_counter[int(cls)] += 1 count += 1 boxes_per_img.append(count) sizes = np.array(sizes) print("类别分布:", dict(cls_counter)) print("目标像素尺寸: p50=%.1f p90=%.1f p99=%.1f" % ( np.percentile(sizes, 50), np.percentile(sizes, 90), np.percentile(sizes, 99))) print("每图目标数: p50=%.1f p90=%.1f" % ( np.percentile(boxes_per_img, 50), np.percentile(boxes_per_img, 90))) analyze_annotations("dataset/labels/train", img_size_map)img_size_map是图片宽高字典,由上一节读取图片尺寸时顺便生成。类别分布能直接反映类别不平衡程度,如果气泡样本数是液滴的 10 倍,训练时要设置cls损失权重。尺寸分布里的 p90 如果小于 40 像素,说明这是小目标数据集,配imgsz=960甚至1280比配640的效果好得多;每图目标数则决定 NMS 的阈值和最大检测数上限。
3.3 四类异常检查:越界、空标签、坏图、错位
数据集的“脏”主要体现在四类异常,逐项排查:
第一,坐标越界。归一化坐标中x_center + width / 2 > 1.0或y_center + height / 2 > 1.0,这类框在裁剪时会触发数组越界,训练直接崩溃。第二,空标签。某些图片的 txt 是空文件,图形上有目标但没标,会成为持续的误检源头。第三,图片损坏。某些 jpg 文件头被截断,OpenCV 读取时报错。第四,类别 id 错位。txt 里写的 class_id 超出了数据集的类别总数,通常是两类数据合并时忘了改代号。
一次性处理这些异常的脚本我放在一个函数里:
import cv2 import os import numpy as np def sanitize_labels(img_dir, lbl_dir, num_classes): for txt_name in os.listdir(lbl_dir): if not txt_name.endswith('.txt'): continue img_path = os.path.join(img_dir, txt_name[:-4] + '.jpg') if not os.path.exists(img_path): img_path = os.path.join(img_dir, txt_name[:-4] + '.png') img = cv2.imread(img_path) if img is None: print(f"坏图: {img_path}") continue h, w = img.shape[:2] lines = [] with open(os.path.join(lbl_dir, txt_name)) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls, xc, yc, bw, bh = map(float, parts) if cls >= num_classes: print(f"类别越界: {txt_name} cls={cls}") continue if bw <= 0 or bh <= 0 or bw > 1 or bh > 1: continue if xc - bw/2 < -0.05 or xc + bw/2 > 1.05 or yc - bh/2 < -0.05 or yc + bh/2 > 1.05: xc = min(max(xc, bw/2), 1 - bw/2) yc = min(max(yc, bh/2), 1 - bh/2) lines.append(f"{int(cls)} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") if lines: with open(os.path.join(lbl_dir, txt_name), 'w') as f: f.write("\n".join(lines) + "\n") else: print(f"空标签: {txt_name}")num_classes必须和类别列表一一对应。坐标略超边界的情况我用 clamp 修正,超出太多则删除该框。处理完之后,把打印出的文件列表人工再过一眼,确认不是大批量异常。
注意:批量修改标签前先把原始文件备份一份。
cp -r labels labels_backup即可,别用 git 来回切,大文件数据集没必要。
3.4 可视化抽检:画框看图比任何指标都直接
统计指标只能告诉你“数据长什么样”,画框叠加图才能告诉你“标注对不对”。这一步通常用 OpenCV 批量画框,输出到单独的支持目录:
python draw_boxes.py --images dataset/images/val --labels dataset/labels/val --output dataset/check_visualdraw_boxes.py的核心逻辑是读一张图、读同名 txt、cv2.rectangle画框、cv2.imwrite写出。关键参数是--max_samples,建议每类随机抽 30 张,不要全量画,时间成本高且信息冗余。抽检时重点看三类问题:框有没有压到液滴的反射光斑上;两个相邻目标是否被画成一个大框;背景纹理(滤网、管道接缝)有没有被误标为目标。这三类问题在统计脚本里看不出来,但画出来一眼就能发现。
4. 用 yolov8 在自己的数据集上完成一个可评估的训练闭环
4.1 把数据集接进 ultralytics:一个 yaml 文件的事
数据体检完毕,接下来进入 yolov8 训练自己的数据集的环节。ultralytics 的训练入口只认一个配置 yaml,内容是路径、类别名、类别数:
# droplet_bubble.yaml path: /data/industrial_droplet # 数据集根目录 train: images/train # 相对 path 的训练集目录 val: images/val # 相对 path 的验证集目录 names: 0: droplet 1: bubblepath建议写绝对路径,因为训练脚本跨机器迁移时,相对路径解析容易出错。train和val指向图片目录即可,yolov8 会自动去同名labels目录找 txt。如果标签目录叫annotations而不是labels,需要建一个软链接:
ln -s /data/industrial_droplet/annotations /data/industrial_droplet/labels这一步经常被忽略,报错却是“AssertionError: Label not found”,排查半天发现只是目录名不匹配。yaml 里names的序号必须和 txt 里第一列的 class_id 对应,差一个位子就会把液滴当成气泡训练。
4.2 训练时真正值得调的参数
接好数据后,合理做法是先跑一个 20 轮的 baseline,不要动任何增强参数。这轮训练的目的不是刷指标,而是确认数据链路通畅、loss 能正常下降、验证集没有报错。baseline 通过后再往下调参数:
| 参数 | 默认值 | 液滴气泡场景建议 | 理由 |
|---|---|---|---|
imgsz | 640 | 960 或 1280 | 目标偏小,放大输入分辨率等效于丰富小目标信息 |
batch | 16 | 按显存决定,调大优先 | 小目标对 batch size 敏感,过小导致梯度噪声大 |
mosaic | 1.0 | 0.5~1.0 | 液滴之间本来就近,mosaic 截断后目标更碎,降低剪切比例 |
hsv_h | 0.015 | 0.0~0.005 | 工业场景颜色变化小,过度色偏会让模型学到假的颜色特征 |
lrf | 0.01 | 0.005 | 液滴场景收敛慢,末期小学习率帮助跑稳 |
patience | 100 | 30 | 数据量大时提前收敛,没必要等满轮次 |
启动训练的命令:
yolo train model=yolov8s.pt data=droplet_bubble.yaml imgsz=960 batch=32 epochs=100 patience=30 project=runs/droplet name=exp1yolov8s.pt是预训练权重,在 COCO 上收敛过,迁移到工业小目标数据上效果比随机初始化好很多。project和name参数控制输出目录,建议每次实验换一个 name,方便对比。epochs 设为 100 而不是 300,工业数据集通常几万张,100 轮足够收敛,多出的轮次只是过拟合。
4.3 训练后看这四张图,而不是只看 mAP
训练结束,输出目录runs/droplet/exp1下会有results.png、confusion_matrix.png、PR_curve.png、val_batch*.jpg几张图。这些图比一个孤零零的 mAP 数字有用得多。
results.png里的train/box_loss和val/box_loss是关键。两个 loss 同步下降且没有明显回升,说明训练正常;val loss 先降后升而 train loss 还在降,就是过拟合,回退到早停的权重文件。
confusion_matrix.png里重点关注背景(background)列:如果液滴那一行的 background 响应值高,说明模型把液滴当成了背景,通常是漏标太多导致。把漏标样本补进数据集,比调任何超参数都有效。
PR_curve.png里曲线和坐标轴围成的面积就是 AP,看曲线右下角的“长尾”部分,尾越长说明置信度阈值对结果的影响越大。部署时把置信度阈值调到 0.3 还是 0.7,从这里看。
val_batch*.jpg是验证集上的预测叠加图,看误检集中在哪里。工业场景里常见的是把滤网纹理、玻璃管边缘、气泡反光高光误检成小目标。如果是反光类误检,在增强里加gaussian_blur和dropout比加数据更节省时间。
4.4 训练失败的常见原因与排错顺序
训练过程中报错,按这个顺序排查能省下大量时间:
第一,CUDA out of memory。优先降 batch,而不是降 imgsz,因为小 batch 对精度影响小于小分辨率。第二,AssertionError: image not found,检查 yaml 里路径是否写对,注意 ultralytics 要求路径里不能有中文。第三,loss 是nan。在 yaml 中补充seed: 42,多数情况是增强爆了梯度。第四,mAP 一直是 0。打开训练日志看val/box_loss是否下降,如果下降但 mAP 为 0,几乎可以断定是标签里的类别序号和names对不上。
注意:工业数据集解压后如果放在中文路径下,ultralytics 的某些版本解析不了带中文的绝对路径。整个项目目录用英文命名,是成本最低的防坑手段。
5. 最后三件事:把 zip 数据集的剩余价值榨干
5.1 推理阶段用 TTA,不花标注成本换两个点
训练好的模型往往还有一点提升空间,测试时增强(TTA)是最省事的手段。yolov8 的推理接口直接支持:
yolo predict model=runs/droplet/exp1/weights/best.pt source=test_images/ imgsz=960 tta=TrueTTA 会对每张图做多尺度缩放和翻转推理,再融合结果,小目标场景通常能带来 1 到 3 个点的 mAP 提升,代价是推理时间乘三到四倍。线上实时检测用不起 TTA,但离线抽检和产线联调阶段用 TTA 评估模型上限,非常划算。
5.2 液滴贴壁检测:从水平框切换到旋转框
如果数据集里大量气泡贴附在管壁、液滴沿边缘滑动,水平框会同时框住目标和背景,NMS 阶段相互抑制严重。这类场景有个专门的说法叫 rotated object detection,mmrotate 就是专门干这个的框架。处理过程不复杂:先把水平框标注转成旋转框标注,格式是class_id xc yc w h angle,再按 mmrotate 的 DOTA 格式组织数据。
python tools/analysis/rotate_dataset.py \ --data /data/industrial_droplet \ --format yolo \ --out /data/industrial_droplet/rotated转换后训练的命令与普通检测类似,只是模型换成RotatedRetinaNet或Oriented R-CNN。注意旋转框数据集的验证集划分必须和水平框数据集完全一致,否则两组实验没法对比。多目标检测做到旋转框这一层,基本就到工业视觉数据标注的极限了,往下的收益很有限。
5.3 用自训练把静态 zip 变成动态数据资产
静态数据集训练出的模型,上线三个月后会因为光源老化、喷嘴磨损逐渐失效。把 zip 数据集做厚的工程做法是自训练:用当前 best 模型去跑新采集的未标注视频,生成伪标签,再用低置信度过滤和人工抽检修正。
yolo predict model=best.pt source=production_videos/ imgsz=960 conf=0.35 \ save_txt=True save_conf=Trueconf=0.35和save_conf=True是关键,伪标签里附带置信度分值,后续用脚本过滤掉低于 0.8 的框。剩下高置信度框直接入库作为下一轮训练数据,低置信度部分交给人工挑几张错图补充标注。这样每一轮生产数据回流,都会让模型在真实环境下的分布上再适应一轮,精度不掉,部署寿命拉长。这也是数据质量要求及评价方法在工业界落地最实在的形态:数据集不是一次交付暂停,而是一条不断回收、不断验证的流水线。
本文还有配套的精品资源,点击获取