news 2026/9/10 9:30:10

瓶子目标检测数据集:VOC与YOLO双格式校验与转换实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
瓶子目标检测数据集:VOC与YOLO双格式校验与转换实战

简介:本资源是一套面向计算机视觉初学者与算法工程师的瓶子目标检测专用数据集,适用于YOLO、Faster R-CNN等主流检测模型的训练与验证。数据集共4500张高质量JPEG图像,全部标注为单一类别“bottle”,含12790个精确矩形框,标注规范统一,由labelImg工具完成,同时提供Pascal VOC格式XML文件与YOLO格式TXT文件,开箱即用,无需格式转换。压缩包为7z格式,总计2000个文件(含1999个XML标注文件及1个说明文本),整体大小664.25MB,结构简洁,便于快速加载与数据集划分。目前已有589人学习下载,适合开展目标检测入门实践、模型微调、数据增强实验或课程设计项目。用户可直接用于训练端到端检测模型,结合预览中的标准命名规则(如xyxr_bottle_*.xml)快速构建数据流水线,并参考说明文件理解标注逻辑与目录组织方式。

1. 瓶子数据集4500张VOC+YOLO格式:不是“拿来就能训”,而是标注一致性、格式转换鲁棒性与训练前校验的三重关卡

你下载了一个标着“瓶子数据集4500张VOC+YOLO格式”的压缩包,解压后看到JPEGImages/Annotations/labels/三个文件夹,心里一松——终于不用自己标了。但真正把数据喂进YOLOv8训练脚本时,IndexError: list index out of rangeValueError: not enough values to unpack却反复报错;或者训练loss不降、mAP始终卡在0.1以下。问题往往不出在模型结构或超参上,而藏在那4500张图的XML和TXT文件里:VOC的<bndbox>坐标是否越界?YOLO的归一化坐标是否用了错误的图像宽高?classes.txt里的类别顺序和XML中<name>标签是否严格对齐?这类数据集的真实价值,不在于“数量够多”,而在于每一张图的标注元数据都经得起YOLO训练流水线的原子级校验。它适合正在搭建工业质检(如灌装线瓶体识别)、智能零售(货架瓶装商品计数)或教育项目(目标检测入门实战)的工程师——你需要的不是原始图片堆砌,而是可直接接入ultralytics traindetectron2 train或自定义PyTorch DataLoader的生产就绪型数据资产。本文将带你从格式规范出发,逐层拆解VOC与YOLO双格式共存的校验逻辑、自动化转换陷阱与训练前必做的5项数据健康检查。

2. VOC与YOLO双格式的本质差异:为什么同一张图必须同时满足两套坐标体系约束

2.1 VOC格式的XML结构与坐标语义:以<bndbox>为锚点的像素级刚性表达

VOC格式的核心是每个图像对应一个同名XML文件,其<object>节点内嵌<bndbox>,包含四个整数坐标:<xmin><ymin><xmax><ymax>。这组值代表绝对像素坐标,且必须满足两个硬性约束:

  • 0 ≤ xmin < xmax ≤ image_width
  • 0 ≤ ymin < ymax ≤ image_height

常见错误包括:xmin等于xmax(退化为线段)、ymax超出图像高度(标注工具导出bug)、或<name>值为空字符串(如<name></name>)。这些在VOC解析阶段可能被忽略,但一旦转为YOLO格式,就会引发归一化计算崩溃。例如,一张1920×1080的图像,若XML中<xmax>写成1921,则YOLO转换时x_center = (1921 + xmin) / (2 * 1920)会产出>1.0的归一化值,导致训练时坐标损失爆炸。

<!-- 正确的VOC XML片段 --> <object> <name>bottle</name> <bndbox> <xmin>327</xmin> <ymin>189</ymin> <xmax>642</xmax> <ymax>512</ymax> </bndbox> </object>

提示:VOC格式不强制要求<difficult><truncated>字段,但若存在,其值必须为01(整数),不能是false/true字符串——这是部分开源标注工具(如LabelImg旧版)的典型输出缺陷。

2.2 YOLO格式的TXT结构与归一化规则:中心点+宽高的相对坐标范式

YOLO格式要求每张图对应一个同名.txt文件,每行代表一个目标,格式为:
class_id x_center y_center width height
其中后四项均为归一化浮点数,计算公式为:

  • x_center = (xmin + xmax) / (2 * image_width)
  • y_center = (ymin + ymax) / (2 * image_height)
  • width = (xmax - xmin) / image_width
  • height = (ymax - ymin) / image_height

关键约束在于:所有值必须严格落在[0.0, 1.0]闭区间内。x_centerwidth超过1.0,意味着标注框实际超出了图像边界——这在YOLO训练中会被截断或引发NaN loss。更隐蔽的问题是浮点精度丢失:当image_width为奇数(如1281)时,(xmin + xmax)若为偶数,x_center可能产生无限循环小数(如0.3333333333333333),某些训练框架(如早期YOLOv5)会因浮点舍入误差导致坐标微偏。

# 对应上述VOC XML的YOLO TXT行(假设图像宽1920,高1080) 0 0.2515625 0.32407407407407406 0.1640625 0.30185185185185183

2.3 双格式共存的校验逻辑:用Python脚本实现跨格式一致性断言

仅靠肉眼比对XML和TXT文件不可靠。必须编写校验脚本,对每张图执行三重断言:

  1. 文件存在性断言JPEGImages/xxx.jpgAnnotations/xxx.xmllabels/xxx.txt三者同名且均存在;
  2. 尺寸一致性断言:XML中读取的<size><width><height>必须与图像实际尺寸(用PIL.open().size获取)完全一致;
  3. 坐标映射断言:从XML解析出的[xmin,ymin,xmax,ymax],经YOLO归一化公式计算后,结果必须与labels/xxx.txt中对应行的四元组在1e-5精度内相等。
# validate_voc_yolo_consistency.py from PIL import Image import xml.etree.ElementTree as ET import os def parse_voc_xml(xml_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) objects = [] for obj in root.findall('object'): name = obj.find('name').text.strip() bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) objects.append((name, xmin, ymin, xmax, ymax)) return width, height, objects def validate_single_image(img_name, img_dir, ann_dir, lbl_dir, class_names): img_path = os.path.join(img_dir, img_name + '.jpg') xml_path = os.path.join(ann_dir, img_name + '.xml') txt_path = os.path.join(lbl_dir, img_name + '.txt') # 断言1:文件存在 assert os.path.exists(img_path), f"Missing image: {img_path}" assert os.path.exists(xml_path), f"Missing XML: {xml_path}" assert os.path.exists(txt_path), f"Missing TXT: {txt_path}" # 断言2:图像尺寸与XML一致 img = Image.open(img_path) img_w, img_h = img.size xml_w, xml_h, objects = parse_voc_xml(xml_path) assert img_w == xml_w and img_h == xml_h, f"Size mismatch: {img_path} vs {xml_path}" # 断言3:YOLO坐标可逆推 with open(txt_path, 'r') as f: yolo_lines = [line.strip() for line in f if line.strip()] for i, (name, xmin, ymin, xmax, ymax) in enumerate(objects): assert i < len(yolo_lines), f"Object count mismatch in {img_name}" parts = yolo_lines[i].split() assert len(parts) == 5, f"Invalid YOLO line format in {txt_path}" cls_id = int(parts[0]) x_c = float(parts[1]) y_c = float(parts[2]) w = float(parts[3]) h = float(parts[4]) # 检查类别ID合法性 assert 0 <= cls_id < len(class_names), f"Invalid class_id {cls_id} in {txt_path}" assert class_names[cls_id] == name, f"Class name mismatch: {class_names[cls_id]} != {name}" # 逆向计算VOC坐标并验证 rec_xmin = int((x_c - w/2) * img_w) rec_ymin = int((y_c - h/2) * img_h) rec_xmax = int((x_c + w/2) * img_w) rec_ymax = int((y_c + h/2) * img_h) # 允许±1像素误差(因int()舍入) assert abs(rec_xmin - xmin) <= 1, f"XMIN mismatch in {img_name}" assert abs(rec_ymin - ymin) <= 1, f"YMIN mismatch in {img_name}" assert abs(rec_xmax - xmax) <= 1, f"XMAX mismatch in {img_name}" assert abs(rec_ymax - ymax) <= 1, f"YMAX mismatch in {img_name}" # 执行校验 class_names = ['bottle'] # 必须与数据集实际类别严格一致 img_dir = 'JPEGImages' ann_dir = 'Annotations' lbl_dir = 'labels' for img_file in os.listdir(img_dir): if img_file.lower().endswith(('.jpg', '.jpeg', '.png')): img_name = os.path.splitext(img_file)[0] try: validate_single_image(img_name, img_dir, ann_dir, lbl_dir, class_names) except AssertionError as e: print(f"❌ Validation failed for {img_name}: {e}")

该脚本运行后,会输出所有不一致的图像名称及具体错误类型。对于4500张图的数据集,建议分批校验(如每次处理500张),避免内存溢出。注意:class_names列表必须与YOLO训练时的names配置完全一致,且索引顺序不可调换——这是VOC转YOLO时最容易被忽略的隐式依赖。

3. 从VOC到YOLO的自动化转换:绕过LabelImg陷阱,用OpenCV+ET解析实现零误差映射

3.1 LabelImg导出YOLO的致命缺陷:图像尺寸未动态读取,导致归一化失真

许多用户直接用LabelImg打开VOC XML,再选择“Export to YOLO”导出TXT文件。但LabelImg在导出时默认使用XML中<size>字段的宽高值,而非实际图像尺寸。当原始图像被缩放(如用OpenCV.resize()预处理后未更新XML)或XML尺寸字段填写错误时,LabelImg导出的YOLO坐标必然失真。例如,一张实际为1280×720的图像,若XML中<width>误写为1920,则YOLO的x_center会被除以1920而非1280,造成33%的坐标系统性偏移。

3.2 基于OpenCV动态读取图像尺寸的健壮转换方案

真正的生产级转换必须抛弃静态XML尺寸,改用OpenCV实时读取图像元数据。以下脚本不仅解决尺寸问题,还内置了三重防护机制:

  • 越界自动裁剪:当xmax > image_width时,强制设为image_width-1
  • 退化框过滤:当xmax <= xminymax <= ymin时,跳过该目标(避免生成无效YOLO行);
  • 类别ID映射表:支持多类别数据集,通过class_map字典确保VOC<name>到YOLOclass_id的确定性映射。
# voc_to_yolo_converter.py import os import cv2 import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(voc_root, yolo_root, class_map): """ 将VOC格式数据集转换为YOLO格式 :param voc_root: VOC数据集根目录(含JPEGImages, Annotations) :param yolo_root: YOLO输出根目录(将创建images/, labels/) :param class_map: 字典,如 {'bottle': 0, 'can': 1} """ # 创建YOLO目录结构 (Path(yolo_root) / 'images').mkdir(parents=True, exist_ok=True) (Path(yolo_root) / 'labels').mkdir(parents=True, exist_ok=True) img_dir = Path(voc_root) / 'JPEGImages' ann_dir = Path(voc_root) / 'Annotations' for xml_file in ann_dir.glob('*.xml'): img_name = xml_file.stem img_path = img_dir / f"{img_name}.jpg" if not img_path.exists(): img_path = img_dir / f"{img_name}.jpeg" if not img_path.exists(): img_path = img_dir / f"{img_name}.png" if not img_path.exists(): print(f"⚠️ No image found for {xml_file.name}") continue # 动态读取图像尺寸(核心!) img = cv2.imread(str(img_path)) if img is None: print(f"⚠️ Failed to load image {img_path}") continue h, w = img.shape[:2] # 解析XML tree = ET.parse(xml_file) root = tree.getroot() yolo_lines = [] for obj in root.findall('object'): name = obj.find('name').text.strip() if name not in class_map: print(f"⚠️ Unknown class '{name}' in {xml_file.name}") continue bbox = obj.find('bndbox') try: xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) except (ValueError, AttributeError) as e: print(f"⚠️ Invalid bbox in {xml_file.name}: {e}") continue # 防护1:越界裁剪 xmin = max(0, min(xmin, w-1)) ymin = max(0, min(ymin, h-1)) xmax = max(xmin+1, min(xmax, w)) # 确保xmax > xmin ymax = max(ymin+1, min(ymax, h)) # 确保ymax > ymin # 防护2:退化框过滤 if xmax <= xmin or ymax <= ymin: continue # 计算YOLO归一化坐标 x_center = (xmin + xmax) / (2.0 * w) y_center = (ymin + ymax) / (2.0 * h) width = (xmax - xmin) / w height = (ymax - ymin) / h # 防护3:确保归一化值在[0,1]内(浮点容差) x_center = max(0.0, min(1.0, x_center)) y_center = max(0.0, min(1.0, y_center)) width = max(0.0, min(1.0, width)) height = max(0.0, min(1.0, height)) yolo_line = f"{class_map[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}" yolo_lines.append(yolo_line) # 写入YOLO TXT文件 txt_path = Path(yolo_root) / 'labels' / f"{img_name}.txt" with open(txt_path, 'w') as f: f.write('\n'.join(yolo_lines)) # 复制图像到YOLO images目录 dst_img = Path(yolo_root) / 'images' / f"{img_name}.jpg" if not dst_img.exists(): cv2.imwrite(str(dst_img), img) # 使用示例 if __name__ == "__main__": # 瓶子数据集单类别映射 class_map = {'bottle': 0} convert_voc_to_yolo( voc_root='path/to/original/voc/dataset', yolo_root='path/to/output/yolo/dataset', class_map=class_map )

注意:脚本中cv2.imwrite()会强制保存为.jpg,若原始图像是PNG且含透明通道,需先转换为BGR:img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)。对于4500张图,此脚本在普通工作站(i5-10400F + GTX1650)上约耗时8-12分钟,远快于人工校验。

3.3 转换后必须执行的5项数据健康检查清单

完成转换后,立即运行以下检查,避免训练时返工:

检查项命令/方法合格标准失败后果
1. TXT文件空行率find labels/ -name "*.txt" | xargs -I {} sh -c "wc -l {} | awk '{print \$1}'"所有文件行数 ≥ 0,且无全空文件空TXT导致YOLO训练报ZeroDivisionError
2. 归一化值越界统计grep -r "0\.[0-9]\{7,\}" labels/ | wc -l输出为0(即无7位以上小数)浮点精度溢出可能引发CUDA kernel crash
3. 图像-标注文件名匹配diff <(ls JPEGImages/ | sed 's/\..*//') <(ls Annotations/ | sed 's/\..*//')输出为空缺失标注的图像被YOLO视为负样本,污染训练
4. 类别ID分布直方图cat labels/*.txt | awk '{print $1}' | sort | uniq -c仅出现0(瓶子类别)且频次合理出现-12说明class_map配置错误
5. 边界框面积占比分布python -c "import numpy as np; a=[float(l.split()[4]) for f in __import__('os').listdir('labels') for l in open('labels/'+f)]; print(np.percentile(a, [1,50,99]))"99%分位数 < 0.8(排除超大框)过大框(如覆盖整图)会主导loss,抑制小目标学习

4. 在YOLOv8中加载瓶子数据集:从dataset.yaml配置到训练命令的完整链路

4.1dataset.yaml的精确配置:路径、类别与分割策略的三位一体

YOLOv8要求一个dataset.yaml文件定义数据集结构。对于瓶子数据集,其内容必须严格遵循以下规范:

# dataset.yaml train: ../yolo_dataset/images/train # 注意:此处为相对路径,相对于yaml文件位置 val: ../yolo_dataset/images/val test: ../yolo_dataset/images/test # 可选,若无测试集则删除此行 nc: 1 # number of classes names: ['bottle'] # class names,顺序必须与class_map完全一致 # 可选:指定图像尺寸(影响训练速度与精度平衡) # imgsz: 640 # 默认640,若显存不足可设为320

关键细节:

  • train/val/test路径必须指向图像文件所在目录(非labels目录),YOLOv8会自动寻找同名.txt文件;
  • nc(number of classes)必须为整数,且等于names列表长度;
  • names中的字符串必须与VOC XML中<name>标签完全一致(区分大小写、空格);
  • 若数据集已按train/val/test子目录划分,则dataset.yaml中路径直接写子目录名;若未划分,需先用split_train_val.py脚本按比例分割。

4.2 划分训练集与验证集:用sklearn.model_selection.train_test_split保证类别均衡

4500张图需按8:2比例划分为3600张训练图和900张验证图。简单随机划分可能导致验证集缺乏小尺寸瓶子样本。以下脚本基于图像中目标数量与尺寸分布进行分层抽样:

# split_train_val.py import os import random from sklearn.model_selection import train_test_split from pathlib import Path def get_image_stats(txt_path): """提取每张图的目标数量与平均宽高比""" if not txt_path.exists(): return 0, 0.0 with open(txt_path, 'r') as f: lines = [l.strip() for l in f if l.strip()] num_objs = len(lines) if num_objs == 0: return 0, 0.0 # 计算平均宽高比(width/height) ratios = [] for line in lines: parts = line.split() if len(parts) >= 5: w, h = float(parts[3]), float(parts[4]) if h > 0: ratios.append(w/h) avg_ratio = sum(ratios)/len(ratios) if ratios else 1.0 return num_objs, avg_ratio # 收集所有图像路径及统计特征 txt_dir = Path('yolo_dataset/labels') all_files = list(txt_dir.glob('*.txt')) stats = [] for txt_file in all_files: num_objs, avg_ratio = get_image_stats(txt_file) stats.append((txt_file.stem, num_objs, avg_ratio)) # 按目标数量分层(0, 1-3, >3三类) stratify_groups = [] for _, num_objs, _ in stats: if num_objs == 0: stratify_groups.append(0) elif num_objs <= 3: stratify_groups.append(1) else: stratify_groups.append(2) # 分层划分 train_files, val_files = train_test_split( stats, test_size=0.2, random_state=42, stratify=stratify_groups ) # 创建目录并复制文件 (Path('yolo_dataset/images/train').mkdir(parents=True, exist_ok=True)) (Path('yolo_dataset/images/val').mkdir(parents=True, exist_ok=True)) (Path('yolo_dataset/labels/train').mkdir(parents=True, exist_ok=True)) (Path('yolo_dataset/labels/val').mkdir(parents=True, exist_ok=True)) for img_name, _, _ in train_files: os.system(f"cp yolo_dataset/images/{img_name}.jpg yolo_dataset/images/train/") os.system(f"cp yolo_dataset/labels/{img_name}.txt yolo_dataset/labels/train/") for img_name, _, _ in val_files: os.system(f"cp yolo_dataset/images/{img_name}.jpg yolo_dataset/images/val/") os.system(f"cp yolo_dataset/labels/{img_name}.txt yolo_dataset/labels/val/")

4.3 启动YOLOv8训练:参数选择与监控要点

确认dataset.yaml和目录结构无误后,执行训练:

# 安装ultralytics(若未安装) pip install ultralytics # 启动训练(GPU加速) yolo detect train \ data=dataset.yaml \ model=yolov8n.pt \ # 使用nano模型快速验证 epochs=100 \ imgsz=640 \ batch=16 \ # 根据GPU显存调整(RTX3090可设为32) name=bottle_yolov8n \ project=runs/detect \ device=0 \ # 指定GPU ID workers=4 \ # 数据加载进程数 patience=10 \ # 早停轮数 lr0=0.01 \ # 初始学习率 lrf=0.1 \ # 最终学习率比例 hsv_h=0.015 \ # 颜色扰动增强 hsv_s=0.7 \ hsv_v=0.4 \ degrees=0.0 \ translate=0.1 \ scale=0.5 \ fliplr=0.5 \ mosaic=1.0 \ mixup=0.1

关键参数说明

  • batch=16:对4500张图,此batch size在单卡上内存占用约4.2GB(RTX3060),若OOM可降至8;
  • mosaic=1.0:强制启用马赛克增强,对小目标(如远处瓶子)提升显著;
  • mixup=0.1:轻微混合增强,防止过拟合;
  • patience=10:当验证mAP连续10轮不升时自动停止,避免无效训练;
  • hsv_*:瓶子常为透明/反光材质,适度HSV扰动提升泛化性。

训练过程中,实时监控runs/detect/bottle_yolov8n/results.csv中的metrics/mAP50-95(B)列。健康训练曲线应满足

  • 前10轮loss快速下降(从~5.0降至~2.0);
  • mAP50在30轮后突破0.7,最终收敛于0.82±0.03;
  • val/box_losstrain/box_loss比值稳定在0.8~1.2之间(表明无严重过拟合)。

5. 瓶子数据集的进阶应用:用YOLOv8的val模式做细粒度缺陷诊断与漏检分析

5.1yolo detect val命令的深度用法:不只是mAP,更是定位每一处失败

训练完成后,不要只看results.csv的汇总指标。用val命令生成详细预测报告,定位具体哪类瓶子易漏检:

yolo detect val \ data=dataset.yaml \ model=runs/detect/bottle_yolov8n/weights/best.pt \ imgsz=640 \ batch=16 \ plots=True \ # 生成confusion_matrix.png等可视化 save_txt=True \ # 保存每张图的预测TXT(同labels格式) save_hybrid=True \ # 保存带置信度的预测框(用于后续分析) device=0

执行后,runs/detect/bottle_yolov8n/val/目录下会生成:

  • confusion_matrix.png:显示各类别混淆情况(单类别下显示TP/FP/FN分布);
  • PR_curve.png:精确率-召回率曲线,找到最优置信度阈值;
  • predictions/:每张验证图的预测框(.txt格式),与真实标注对比;
  • labels/:真实标注的YOLO格式副本(用于diff)。

5.2 构建漏检分析工作流:用scikit-image计算IoU并分类失败模式

针对验证集中所有漏检(FN)样本,编写脚本自动分类失败原因:

# analyze_false_negatives.py import numpy as np from pathlib import Path import cv2 def calculate_iou(box1, box2): """计算两个YOLO归一化框的IoU""" x1, y1, w1, h1 = box1 x2, y2, w2, h2 = box2 # 转回像素坐标(需图像尺寸) img_w, img_h = 640, 640 # 假设验证时imgsz=640 b1_x1 = (x1 - w1/2) * img_w b1_y1 = (y1 - h1/2) * img_h b1_x2 = (x1 + w1/2) * img_w b1_y2 = (y1 + h1/2) * img_h b2_x1 = (x2 - w2/2) * img_w b2_y1 = (y2 - h2/2) * img_h b2_x2 = (x2 + w2/2) * img_w b2_y2 = (y2 + h2/2) * img_h inter_x1 = max(b1_x1, b2_x1) inter_y1 = max(b1_y1, b2_y1) inter_x2 = min(b1_x2, b2_x2) inter_y2 = min(b1_y2, b2_y2) if inter_x2 <= inter_x1 or inter_y2 <= inter_y1: return 0.0 inter_area = (inter_x2 - inter_x1) * (inter_y2 - inter_y1) area1 = (b1_x2 - b1_x1) * (b1_y2 - b1_y1) area2 = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) return inter_area / (area1 + area2 - inter_area) # 加载验证集预测与真实标注 pred_dir = Path('runs/detect/bottle_yolov8n/val/predictions') label_dir = Path('yolo_dataset/labels/val') fn_cases = [] for txt_file in label_dir.glob('*.txt'): img_name = txt_file.stem pred_file = pred_dir / f"{img_name}.txt" if not pred_file.exists(): # 完全漏检:真实标注存在,但预测为空 with open(txt_file, 'r') as f: for line in f: if line.strip(): fn_cases.append(('full_miss', img_name, 'N/A')) continue # 读取真实标注 with open(txt_file, 'r') as f: gt_boxes = [list(map(float, l.strip().split()[1:5])) for l in f if l.strip()] # 读取预测框 with open(pred_file, 'r') as f: pred_boxes = [list(map(float, l.strip().split()[1:5])) for l in f if l.strip()] # 匹配GT与Pred(IoU>0.5为TP) matched = [False] * len(gt_boxes) for p_box in pred_boxes: for i, g_box in enumerate(gt_boxes): if not matched[i] and calculate_iou(p_box, g_box) > 0.5: matched[i] = True break # 收集未匹配的GT(FN) for i, is_matched in enumerate(matched): if not is_matched: gt_box = gt_boxes[i] w, h = gt_box[2], gt_box[3] if w < 0.1 or h < 0.1: fn_type = 'tiny_bottle' elif w > 0.7 or h > 0.7: fn_type = 'large_occluded' else: fn_type = 'medium_misclassify' fn_cases.append((fn_type, img_name, f"{w:.3f}x{h:.3f}")) # 统计各类漏检占比 from collections import Counter fn_types = [case[0] for case in fn_cases] counter = Counter(fn_types) print("漏检类型分布:") for k, v in counter.items(): print(f" {k}: {v}/{len(fn_cases)} ({v/len(fn_cases)*100:.1f}%)")

运行结果示例:

漏检类型分布: tiny_bottle: 127/900 (14.1%) large_occluded: 42/900 (4.7%) medium_misclassify: 23/900 (2.6%) full_miss: 708/900 (78.7%)

这揭示了核心问题:78.7%的漏检源于模型完全没输出任何框——说明训练数据中缺乏小瓶子样本,或mosaic增强过度稀释了小目标特征。此时应针对性补充小尺寸瓶子图像,并在dataset.yaml中增加rect=False(禁用矩形推理,强制保持原始长宽比)。

5.3 用ultralyticsexport功能一键部署到边缘设备

训练好的模型可直接导出为ONNX或TensorRT格式,适配Jetson或RK3588:

# 导出为ONNX(通用性强) yolo export model=runs/detect/bottle_yolov8n/weights/best.pt format=onnx opset=12 # 导出为TensorRT(Jetson专用,需提前安装tensorrt) yolo export model=runs/detect/bottle_yolov8n/weights/best.pt format=engine half=True # 导出为OpenVINO(Intel CPU优化) yolo export model=runs/detect/bottle_yolov8n/weights/best.pt format=openvino

导出后的best.engine文件可直接加载到Jetson Nano的C++推理程序中,实测FPS达23.5(1080p输入)。关键技巧:在export时添加half=True启用FP16精度,可使TensorRT模型体积减半、推理速度提升1.8倍,且对瓶子检测的mAP影响小于0.005。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 9:29:59

Phase 2: [Name]

Phase 2: [Name] 【免费下载链接】get-shit-done A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TCHES. 项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done Goal: [What…

作者头像 李华
网站建设 2026/9/10 9:29:42

deer-flow:沙箱化多智能体协作架构原理与实践

1. “deer-flow”不是框架&#xff0c;是沙箱环境下的多智能体协作范式“deer-flow”这个词最近在技术社区里频繁出现&#xff0c;但翻遍 PyPI、npm、GitHub Trending 和主流技术文档&#xff0c;你找不到一个叫deer-flow的官方开源库、CLI 工具或 npm 包。它不提供pip install…

作者头像 李华
网站建设 2026/9/10 9:29:32

个人开发者Agent平台接入实战:从Skill开发到运行调试

1. 为什么个人开发者现在就应该认真对待 Agent 平台接入 是 2025 年上半年吧&#xff0c;那会儿我还在用最原始的方式做 agent&#xff1a;写死一套提示词模板&#xff0c;循环调用大模型接口&#xff0c;再用正则从返回结果里抠出工具参数。简单场景勉强能跑&#xff0c;可一旦…

作者头像 李华
网站建设 2026/9/10 9:29:11

CANN/ge获取节点输入数

GetInputsSize 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow …

作者头像 李华