拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

小番茄检测实战:VOC标注转YOLO格式与训练全流程

小番茄检测实战:VOC标注转YOLO格式与训练全流程

简介:YOLO小番茄目标检测数据集面向计算机视觉学习者、农业智能化开发者及科研人员,聚焦小番茄果实识别这一具体场景,解决成熟度判别与自动采摘中的目标定位难题。压缩包内含1790个文件,由895张不同角度、光照条件下拍摄的PNG图片和895个同名XML标签组成,每个XML均包含边界框坐标与类别信息,可直接配套YOLO系列模型进行训练与验证,也兼容SSD、Faster R-CNN等主流检测框架。数据按图片与标签一一对应整理,便于按需要划分训练集与测试集,配合旋转、翻转、缩放等数据增强方式可进一步提升模型在复杂环境下的泛化能力。目前已有85人学习下载,适合正在入门目标检测的读者作为标准格式练习数据,也适合研究者借助迁移学习或算法调优,持续优化小番茄检测精度,为农业自动化与智能采摘提供扎实的数据基础。

1. 小番茄检测为什么不能拿通用数据集硬扛

这份小番茄目标检测数据集,图片配xml格式标签,走的是经典的Pascal VOC组织方式。它要解决一个很具体的场景问题:温室里的小番茄目标小、成串生长、枝叶遮挡严重,拿COCO上预训练的YOLO权重直接去测,漏检和误检会让你开始怀疑是不是参数没调对。数据集的真正价值,是把「小目标+密集分布+同类遮挡」这个组合单独拎出来,让检测模型从零训练或微调时,有一份干净、可校验的标注可用。它适合三类人:做农业视觉落地的工程师、研究小目标检测方向的学生、想用一份现成数据完整跑一遍YOLO训练流程的从业者。这篇笔记从xml解析讲到转换脚本、训练参数和常见坑,目标是让你拿到.rar解压后一个晚上能跑通训练。

2. 读懂xml标签:VOC格式的目录结构与解析方法

2.1 解压后的第一件事:先看目录树和xml长什么样

别急着训练。先解压,用tree命令把目录结构列出来,确认图片和标注的对应关系。常见做法是.rar里包含images和annotations两个目录,图片是.jpg,标注是同名.xml,一张图对应一个xml文件。先确认文件名是否一一对应,有没有多余的标注文件或孤儿图片,这决定了后面转换脚本要不要加存在性判断。

tree -L 2 .

输出大概是这样的结构:

. ├── images │ ├── 00001.jpg │ └── 00002.jpg └── annotations ├── 00001.xml └── 00002.xml

如果发现annotations里有的xml在images里没有对应图片,后面写转换脚本时就要跳过;反过来图片多、标注少,则要考虑是不是标注还没做完。这种目录结构检查花不了两分钟,但能省掉后面训练时一半的报错排查时间。

接着随便打开一个xml看内容。用浏览器直接拖进去也行,VSCode也行,关键是别用系统记事本打开——xml里所有标签会挤成一行,根本没法看结构。浏览器会把内容自动排版成树状,够用。

<annotation> <folder>images</folder> <filename>00001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>tomato</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>315</xmin> <ymin>240</ymin> <xmax>365</xmax> <ymax>285</ymax> </bndbox> </object> </annotation>

这个xml里最核心的是两组信息:size里的宽高,以及每个object下的name和bndbox。bndbox里的xmin/xmax/ymin/ymax是目标框左上角和右下角的像素坐标,坐标系原点在图片左上角,x向右、y向下。这些坐标是之后转YOLO格式的唯一数据来源,任何一步错了,训练出来的模型都是在垃圾标注上拟合。

2.2 用Python批量读出全部标注框:xml解析最小脚本

解析xml不需要引入大型库,Python标准库xml.etree.ElementTree就够用。用find和findtext逐层取值,比一次性把xml转成字典再取字段更直观,也更容易在字段缺失时快速定位问题。

import xml.etree.ElementTree as ET tree = ET.parse("annotations/00001.xml") root = tree.getroot() # 图片尺寸 size = root.find("size") w = int(size.findtext("width")) h = int(size.findtext("height")) print(f"图片尺寸: {w} x {h}") # 遍历所有目标框 for i, obj in enumerate(root.findall("object")): name = obj.findtext("name") box = obj.find("bndbox") xmin = float(box.findtext("xmin")) ymin = float(box.findtext("ymin")) xmax = float(box.findtext("xmax")) ymax = float(box.findtext("ymax")) print(f"框{i}: 类别={name}, 坐标=({xmin}, {ymin}) -> ({xmax}, {ymax})")

findtext和find("name").text的区别在于:字段不存在时findtext返回None,而find("name").text会直接抛AttributeError。批量处理几百个xml时,一个缺失字段就会让整个脚本中断,所以能用findtext就别用find().text。

跑通这段脚本后,你应该能看到每个xml里的目标框都被读出来了。这时候做一件事:统计一下所有xml里的类别名,看是不是只有tomato一种。

grep -h "<name>" annotations/*.xml | sort | uniq -c

如果看到tomato、Tomato、tomatos混在一起,说明标注工具的自動補全不一致。别小看这个问题,类别名不统一是转换阶段最常见的翻车原因之一,后面章节会专门讲怎么处理。

2.3 编辑xml的工具选型与编码坑

读xml用浏览器或编辑器都能干,但改xml就得多留个心眼。如果标注框本身有错需要微调,我不建议直接手改xml——坐标一多容易眼花。用标注工具打开原图、拖一下框、重新保存,比自己改数字靠谱得多。

纯看结构的话,VSCode装一个XML扩展就能格式化和高亮。Chrome或Firefox直接打开本地xml文件是最省事的方案,树状折叠、点击展开,视觉上比编辑器还友好。

编码是xml最容易踩的隐性坑。VOC标注文件绝大多数是UTF-8,但Windows上解压、再用记事本另存过,可能被改成带BOM的UTF-8或GBK。带BOM的xml在ET.parse时通常能忍,但GBK编码里的中文字段会被解析成乱码。

import xml.etree.ElementTree as ET try: tree = ET.parse("annotations/00001.xml") except ET.ParseError: print("解析失败,尝试指定编码重新读") content = open("annotations/00001.xml", encoding="gbk").read() root = ET.fromstring(content)

实际上更稳妥的做法是写一个目录级的编码检查:读前几个字节判断有没有UTF-8 BOM,再逐个文件尝试解析,解析失败的单独拉出来看。这种一次性检查脚本花十分钟写好,后面转换阶段会省掉大量排查时间。

3. 把xml转成YOLO能吃的txt:转换脚本与归一化细节

3.1 为什么YOLO训练要txt而不是xml

YOLO系列训练时读的标注是.txt文件,不是xml。原因不是YOLO不认VOC格式,而是txt标注的存储方式对训练框架更友好:每行一个目标,格式是类别id 中心点x 中心点y 宽度w 高度h,五个数字全部归一化到0到1之间。归一化坐标的好处是训练时不管输入分辨率怎么变,标注都不用跟着改。

xml里的坐标是像素值、绝对坐标,而且一张图有多个目标就是多个object节点,训练框架每次读取都要解析xml树、遍历节点、再换算坐标。txt则是一行一个框、纯文本读取,解析成本和内存占用都小得多。另外YOLO的数据加载器对txt结构有硬性约定:第一列是整数类别id,后面四列是浮点坐标,顺序错了模型根本训不起来。

转换的核心就是把xml里的像素坐标(xmin, ymin, xmax, ymax)换算成归一化的(cx, cy, w, h):

cx = (xmin + xmax) / 2 / 图片宽度 cy = (ymin + ymax) / 2 / 图片高度 w = (xmax - xmin) / 图片宽度 h = (ymax - ymin) / 图片高度

注意这里全部除以的是图片宽或高,而不是同一个值。x相关的坐标除以宽,y相关的坐标除以高,混用的话目标框会被压扁或拉长。

3.2 转换脚本全流程:批量处理与越界过滤

写脚本直接跑目录,一次性把xml全转成txt。下面的脚本是一份能直接沿用的模板,包含目录创建、批量处理、越界修正、过小框过滤四部分。

import xml.etree.ElementTree as ET from pathlib import Path XML_DIR = Path("annotations") # 存放xml的目录 IMG_DIR = Path("images") # 存放jpg的目录 LABEL_DIR = Path("labels") # 转换后txt的输出目录 LABEL_DIR.mkdir(exist_ok=True) CLASS_MAP = { "tomato": 0, # 统一的类别名 "ripe_tomato": 0, # 如果标注分成熟/未熟,可合并为同一类 "green_tomato": 0, } def convert_one(xml_file: Path) -> None: tree = ET.parse(xml_file) root = tree.getroot() img_name = root.findtext("filename") img_path = IMG_DIR / img_name if not img_path.exists(): print(f"[跳过] 图片不存在: {img_path}") return size = root.find("size") w = float(size.findtext("width")) h = float(size.findtext("height")) lines = [] for obj in root.findall("object"): name = obj.findtext("name").strip().lower() if name not in CLASS_MAP: print(f"[跳过] 未映射类别: {name} in {xml_file.name}") continue box = obj.find("bndbox") xmin = float(box.findtext("xmin")) ymin = float(box.findtext("ymin")) xmax = float(box.findtext("xmax")) ymax = float(box.findtext("ymax")) # 越界修正:把坐标clip到图片内 xmin = max(0, min(xmin, w - 1)) ymin = max(0, min(ymin, h - 1)) xmax = max(0, min(xmax, w - 1)) ymax = max(0, min(ymax, h - 1)) # 过小框过滤:宽或高小于2像素的框没意义 if xmax - xmin < 2 or ymax - ymin < 2: print(f"[过滤] 过小框: {xml_file.name} {name}") continue # 归一化坐标 cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if lines: txt_path = LABEL_DIR / (xml_file.stem + ".txt") txt_path.write_text("\n".join(lines), encoding="utf-8") for xml_file in sorted(XML_DIR.glob("*.xml")): convert_one(xml_file) print(f"转换完成,txt输出目录: {LABEL_DIR.resolve()}")

这段脚本的逻辑分四层:先找图片确认xml和图片能对上;再读宽高做归一化的分母;然后逐目标处理类别映射、越界修正、过小过滤;最后把生成的行写入txt。.strip().lower()是在做类别名归一化,Tomato、tomato这类变体全被统一成tomato,这个细节能避免一大堆因为空格或大小写导致的类别错乱。

参数调整上,CLASS_MAP是你唯一必须按数据集实际情况改的地方。如果xml里本来就是tomato一种,只留一行映射即可。2像素的最小框阈值针对小番茄场景够用,如果你这份数据集中有大量极近景大果图,可以调到5,把贴图边缘的半个框也滤掉。

转换完成后,检查一下输出目录里的txt数量和xml数量是否一致。允许少——有的xml可能所有框都被过滤掉了,但不允许多——txt比xml多说明中间有脏逻辑。

3.3 类别映射表与data.yaml:一个字母都不能错

转换脚本里的CLASS_MAP决定txt里第一列的整数,data.yaml里的names决定这个整数对应什么类别名。两边不一致是训练阶段最常见的低级错误:txt里写了0,yaml里names的0号却是green_tomato,模型训练时损失函数倒是能正常算,但推理结果和可视化标签会错位。

一份标准的小番茄data.yaml长这样:

# data.yaml path: /home/yourname/small_tomato # 数据集根目录,建议写绝对路径 train: images/train val: images/val names: 0: tomato

注意path如果写相对路径,YOLO会以当前工作目录去拼,训练时工作目录一换就找不到数据集,直接报AssertionError: Dataset not found。我一般直接写绝对路径,一劳永逸。

names的索引必须从0开始连续递增,不能跳号,不能只有一项时写成names: [tomato]这种列表形式——Ultralytics的版本里列表也能解析,但和CLASS_MAP逐一对应时容易数错下标。

3.4 转换后的自检:标注回显图必须看一遍

转换脚本跑完,txt里的数字看起来都挺正常,但数字正常不代表坐标对。唯一可靠的验证方式是画回显图:读原图,把txt里的归一化坐标换算回像素坐标,画框保存,然后肉眼抽查几十张。

import cv2 from pathlib import Path IMG_DIR = Path("images") LABEL_DIR = Path("labels") CHECK_DIR = Path("check") CHECK_DIR.mkdir(exist_ok=True) for txt_path in list(LABEL_DIR.glob("*.txt"))[:50]: img_path = IMG_DIR / (txt_path.stem + ".jpg") img = cv2.imread(str(img_path)) if img is None: continue h, w = img.shape[:2] for line in txt_path.read_text().strip().splitlines(): parts = line.split() cls_id, cx, cy, bw, bh = map(float, parts) # 归一化坐标换算回像素 x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) color = (0, 0, 255) if cls_id == 0 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, f"cls{int(cls_id)}", (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(str(CHECK_DIR / (txt_path.stem + ".jpg")), img) print(f"回显图已保存到 {CHECK_DIR.resolve()}")

这里有个容易忽略的细节:回显时cv2.imread读出来的高宽顺序是(h, w),换算像素坐标时分子分母别搞反。抽检时重点看两类图:一张图里目标超过20个的密集簇,以及目标贴着图片边缘的样本。密集簇检查框之间是否明显重叠偏移,边缘样本检查框有没有被clip截断。回显这十分钟花得值——训练三小时后发现标注是歪的,那才是真的血泪教训。

4. 小番茄数据集落地的常见问题与排查

4.1 坐标越界和过小框:训练震荡的第一嫌疑

现象:训练时loss曲线上下剧烈震荡,前几十个epoch完全没有下降趋势;打开回显图发现有的框画到了图片外面,有的框只有几个像素大。

原因:xml里存在坐标大于图片宽高或小于0的标注框,这是标注工具手滑或标注时图片被裁剪过导致的。更隐蔽的情况是图片的EXIF旋转信息没有生效,标注软件看到的图片方向和转换脚本读图的方向差了90度,坐标自然全偏。过小框则是标注时框只框住了一个像素级的番茄尖,这种框对训练只有噪声贡献。

解决:转换脚本里必须做两层防御。第一层是clip,把坐标硬拉到图片边界内;第二层是面积过滤,宽或高小于2像素的框直接丢弃。clip保证不越界,过滤保证不引入噪声。

xmin = max(0, min(xmin, w - 1)) ymin = max(0, min(ymin, h - 1)) xmax = max(0, min(xmax, w - 1)) ymax = max(0, min(ymax, h - 1)) if xmax - xmin < 2 or ymax - ymin < 2: continue

注意clip和过滤的顺序不能反。先clip再计算宽高,否则原本越界的框在过滤阶段可能因为负数差值被误删。

4.2 图片解码与EXIF旋转:读图翻车的两类情况

现象:训练时报通道数错误,或验证时发现一半图片的检测框都偏转了90度。

原因:直接拍的小番茄照片可能是手机或相机直出,这类图片有两个问题。一是通道顺序或通道数不标准,有的jpg实际是RGBA四通道,OpenCV的imread默认按BGR三通道读,四通道图会被截断成奇怪的色块;二是EXIF里带着旋转方向标签,标注工具按旋转后的方向标框,而训练框架读图时不应用EXIF旋转,导致框和图像内容错位。

解决:数据处理阶段先统一转一遍图片。用Pillow读图,应用EXIF旋转,统一转成RGB三通道,再另存为干净的标准jpg。

from PIL import Image, ImageOps from pathlib import Path SRC_DIR = Path("images_raw") DST_DIR = Path("images") DST_DIR.mkdir(exist_ok=True) for img_file in SRC_DIR.iterdir(): im = Image.open(img_file) im = ImageOps.exif_transpose(im) # 应用EXIF旋转 im = im.convert("RGB") # 统一三通道 im.save(DST_DIR / (img_file.stem + ".jpg"), quality=95) print(f"已处理: {img_file.name}")

这步做完,务必重新核对xml里的width和height与转出的图片是否一致。EXIF旋转生效后,横图可能变竖图,xml里的size字段要跟着更新,否则坐标换算会整体偏移。

4.3 小目标训不出来的参数坑:输入分辨率与增强

现象:训练能收敛,验证集mAP@0.5看着还行,但实际检测时漏掉大量远处的小番茄;mAP@0.5:0.95明显偏低。

原因:小番茄在1920x1080的原始图里可能只有20x30像素,喂进默认imgsz=640的网络后,目标被压缩到不到10个像素,特征图上的响应几乎消失。这不是模型问题,是输入分辨率配不上目标尺寸。Ultralytics YOLOv8没有anchor机制,靠特征图网格密度感知目标大小,输入分辨率直接决定小目标的特征强度。

解决:把imgsz提高到768或896,同时开启mosaic和copy_paste增强。copy_paste会把一张图里的小目标复制粘贴到另一张图的随机位置,对小目标密集场景非常有效,因为它制造了大量目标样本,且不改变原始目标形状。

# 明显提升了小目标召回的一组参数 yolo detect train data=data.yaml model=yolov8s.pt \ imgsz=768 epochs=150 batch=16 \ mosaic=1.0 copy_paste=0.3 close_mosaic=10

close_mosaic=10的含义是最后10个epoch关闭mosaic增强,让模型在接近真实分布的图片上收敛。不要整个训练过程都开着mosaic——最后阶段还混着四张图的拼接,模型的BN统计和框回归会被带偏。

4.4 划分不固定导致的复现性问题

现象:同样的数据、同样的参数,两次训练出来的mAP能差两三个点。

原因:数据集划分没有固定。每次跑训练脚本都重新随机划分train/val,两次划分的分布不一样,结果自然不一样。更隐蔽的是PyTorch和CUDA的随机性,但那份影响远小于数据划分变动。

解决:先划分数据集,再训练。划分时固定随机种子,或者按文件名哈希划到train/val,保证每次跑训练用的都是同一份划分。

python -c " import random from pathlib import Path random.seed(42) files = sorted(Path('images').glob('*.jpg')) random.shuffle(files) val_count = int(len(files) * 0.15) from pathlib import Path Path('images/train').mkdir(parents=True, exist_ok=True) Path('images/val').mkdir(parents=True, exist_ok=True) Path('labels/train').mkdir(parents=True, exist_ok=True) Path('labels/val').mkdir(parents=True, exist_ok=True) for f in files[:val_count]: f.rename(Path('images/val') / f.name) (Path('labels') / (f.stem + '.txt')).rename(Path('labels/val') / (f.stem + '.txt')) for f in files[val_count:]: f.rename(Path('images/train') / f.name) (Path('labels') / (f.stem + '.txt')).rename(Path('labels/train') / (f.stem + '.txt')) "

划分后检查一下images/train和labels/train的文件数是否一致,避免因为孤儿txt或孤儿图片导致训练时数据加载报错。

5. 用YOLOv8把小番茄数据集训起来:参数与loss怎么看

5.1 先定baseline:imgsz、模型大小和batch怎么选

小番茄数据集的目标特性是「小目标、密集、遮挡多」,这直接决定baseline的参数选型。模型大小从yolov8n到yolov8m都可以跑,但要明确:模型越大,小目标特征提取能力越强,同时显存占用和推理耗时线性上升。

小番茄场景推荐从yolov8s起步。n在小目标上会明显吃力——它的C2f模块通道数太少,语义信息不足以支撑密集场景的区分;m对小番茄这种单类场景属于性能溢出,训练周期拉长,收益有限。先用s跑通,再按验证集结果决定是否升级。

输入分辨率imgsz是小番茄场景最敏感的参数。默认640在小目标上漏检严重,建议768起步。显存不够时优先降batch而不是降imgsz,8G显存跑768+16的batch通常没问题,再不够就把batch砍到8。

5.2 训练命令与数据增强参数速查

基于前面转好的目录结构和data.yaml,训练命令如下:

yolo detect train \ data=data.yaml \ model=yolov8s.pt \ imgsz=768 \ epochs=150 \ batch=16 \ patience=30 \ project=runs \ name=tomato_v8s \ copy_paste=0.3 \ close_mosaic=10

model=yolov8s.pt是预训练权重,Ultralytics会尝试从官方地址自动下载。如果你的环境网络不通,需要手动下载后放到当前目录,否则训练脚本会卡在下载阶段。放好权重后,脚本能识别到本地文件,直接加载预训练参数做迁移学习,训练收敛速度比从零训练快一个量级。

数据增强参数里,copy_paste=0.3和close_mosaic=10是小番茄场景最值得调的两个:

参数建议值作用与理由
imgsz768小目标需要更高输入分辨率,640下远处番茄几乎不可见
modelyolov8s.pt单类密集场景的性价比选择,n太小m浪费
batch168G显存的安全值,显存更大可加到32
epochs150单类检测任务一般100-200收敛,150是稳妥区间
patience30验证指标连续30轮不提升就早停,省时间
copy_paste0.3小目标复制增强,直接提升密集场景召回
close_mosaic10最后10轮关闭mosaic,避免拼接图干扰收敛
flipud0.0小番茄生长方向有统计意义,不做上下翻转

flipud=0.0是一个容易被忽略的细节。番茄是垂挂生长的,上下翻转后图像语义仍然合理,但模型会学到「番茄既可以朝上也可以朝下生长」这种错误先验,实际部署时对姿态判断产生干扰。单类检测任务里,省掉上下翻转的收益往往比多一个数据增强更大。

5.3 训练时盯着三条曲线:box_loss、cls_loss、dfl_loss

YOLOv8的损失函数由三个分支组成:box_loss负责预测框和真实框的IoU差异,cls_loss负责分类置信度,dfl_loss负责框边界的分布拟合。训练日志里会同时输出train和val两组,重点盯着val的这三条曲线。

box_loss在前20个epoch快速下降,之后缓慢趋平,属于正常;如果一直震荡不下降,基本可以确定标注里有脏数据,回头查坐标越界或类别错乱。cls_loss单类任务会下降得很快,它反应的是模型对「这是不是番茄」的把握,一般来说50个epoch后就不再明显下降。dfl_loss和box_loss走势相近,但它更敏感——框边界参差不齐时,dfl_loss会持续偏高且不平滑。

训练结束后,用验证集跑一轮预测,输出PR曲线和混淆矩阵:

yolo detect val \ model=runs/tomato_v8s/weights/best.pt \ data=data.yaml \ imgsz=768

单类检测的混淆矩阵没有多类那么丰富,但PR曲线里recall的值很关键。小番茄密集场景下,mAP@0.5:0.95反映的是框的精细度,而recall反映的是有没有漏检。如果recall低于0.7,优先怀疑输入分辨率不够或copy_paste没开;如果recall高但mAP@0.5:0.95低,说明框的位置精度差,此时再考虑换更大的模型或提高imgsz。

6. 训练后的验证:回显检查、漏检分析和二次标注

训练收尾不等于项目收尾。best.pt拿到手,第一件事不是直接部署,而是用真实场景图做一轮压力测试。

yolo predict \ model=runs/tomato_v8s/weights/best.pt \ source=test_images/ \ imgsz=768 \ conf=0.25 \ save=True

跑完后,挑最挤的一串番茄图,把conf降到0.15再跑一遍。置信度阈值一降,原本被压制住的漏检框会全涌出来。对比两次结果,如果低阈值下多出来的框大多数是对的,说明模型本身学到了特征,只是部署时阈值设太高;如果多出来的框全是错检,说明模型的置信度校准有问题,回退到0.25更稳妥。

真正的漏检,用阈值调不出来。挑几张密集到分不清果粒的图,逐颗数一下图里有多少番茄,再数数模型框出了多少。这个人工数数的过程很枯燥,但它是评估数据集质量最直接的方式——如果人工都数不清,标注本身也没有唯一标准,此时补再多的训练也白搭。

二次标注是提升密集场景精度的最后手段。把验证集里漏检最多的几张图,用标注工具打开,补上模型没学到的边界案例——被叶子挡住一半的番茄、光线发暗的果实、重叠到几乎只剩边缘的果子。补完标注,转成txt,增量训练:

yolo detect train data=data.yaml model=runs/tomato_v8s/weights/best.pt \ imgsz=768 epochs=50 batch=16 \ copy_paste=0.3 close_mosaic=10

从已有权重继续训练而不是重新开始,通常几十个epoch就能看到漏检明显减少。

我自己的习惯是每次换数据集都先跑一遍完整回显再进训练,宁可多花十分钟看图,也不愿意训练三小时后回头查标注问题。模型的性能上限,从标注完成那一刻就已经定了,训练只是把它逼近那个上限而已。希望这些经验能帮你少走几趟弯路。

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

返回列表