☰
YOLO目标检测实战:西红柿大番茄数据集从JSON标注到YOLOv8训练全攻略
2026/10/11 22:39:21 网站建设 项目流程

简介:面向计算机视觉与智能农业应用场景,这份数据集提供了西红柿与大番茄两类作物的真实图像样本及对应标注,适用于YOLO系列目标检测模型的训练、验证与算法对比,也能迁移至Faster R-CNN、SSD等常见检测框架,适合从事目标检测研究、农业智能化开发或相关课程实践的学习者使用。包内共279个文件,其中277张jpg图片覆盖多种生长阶段、拍摄角度及光照条件,包含不同程度的背景干扰与复杂遮挡,有助于提升算法在真实环境中的鲁棒性;另附2个json格式标注文件,对每张图片中的目标位置与类别进行了精确记录,为训练高质量模型提供了可靠标签基础。压缩包整体约162.54MB,结构简洁紧凑。截至目前已有134人学习使用,可作为农情监测、果实计数、成熟度判别及智能采摘等方向的数据支撑,同时也可作为深度学习教学与竞赛训练的实操素材。

1. 这份西红柿&大番茄数据集到底能干什么:先摸清边界再动手

做目标检测的人最缺的往往不是模型,而是干净、能直接用的数据。这份「YOLO目标检测-西红柿&大番茄检测数据集(图片+json格式标签).rar」提供的正是训练阶段最关键的原料:真实拍摄的西红柿和大番茄图片,以及对应的JSON格式标签。无论你要做采摘机器人的视觉识别、温室里的果实计数,还是果蔬分拣线上的大小分级,这份数据都能省掉你大量采集和人工标注的时间。

JSON标签的最大好处是它不绑定某个训练框架,你可以把边界框提取出来,转成YOLO、Darknet、MMDetection等任意格式。但也正因为“JSON”只是一个统称,收到压缩包后不能直接就开训。我建议你按“解压检查 → 格式转换 → 试训验证”三步走,每一步都有容易踩的坑。这篇文章会把每一步需要看的、需要改的命令和参数全部拆开讲,最后让你拿到手就能复现一套完整训练流程。

2. 解压后的数据长什么样:图片与JSON标签的两种常见组织方式

2.1 图片文件命名与目录结构:别被中文文件名坑到

解压一个rar数据集后,常见目录结构是images/下放所有jpg或png图片,annotations/下放JSON标签。有些数据集会把图片和JSON放在同一级目录,图片叫“番茄_0001.jpg”,同名JSON叫“番茄_0001.json”;也有把所有标注汇总成一个大JSON文件,放在annotations/下的情况。

先说命名问题。中文文件名在Windows上看起来没问题,但一旦把数据集传到Linux服务器,或者挂载到docker容器里,很容易遇到字符编码错乱,OpenCV读图片直接返回None,训练进程卡在读图阶段。我一般会先做一次全量重命名,改成“tomato_0001.jpg”这类纯英文小写文件名。与此同时,如果JSON里的imagePath字段引用了旧文件名,重命名后必须同步修改这个字段,否则图片和标签就对不上了。

另一个常见问题是子目录。图片可能放在images/train/和images/val/下,而JSON全部集中在annotations/下。转换格式时不能简单取文件名拼接路径,必须先理清每个JSON对应的图片在哪个子目录,否则后面做数据集划分时会漏掉一批图片。

2.2 JSON标签的两种格式:COCO还是LabelMe不要认错

JSON标签有好几种方言,但农业数据集里最常见的是COCO格式和LabelMe格式。它们都能表达“西红柿在哪里”,但数据结构完全不一样,转换脚本也不同。

对比项COCO JSONLabelMe JSON
文件组织多个图片的标注汇总在一个json里每个图片对应一个同名json
关键字段images、annotations、categoriesshapes、imagePath、imageData
框的表达annotations里的bbox为[x, y, width, height]shapes里的points为多边形坐标[[x1,y1],[x2,y2]]
坐标单位像素值像素值
类别来源categories数组里的nameshapes里每个元素的label字段
当前任务可用性直接可用,取bbox即可需要从多边形点计算外接矩形,取min/max即可

拿到JSON后,不要靠猜。先看第一层有没有annotations键,有就是COCO风格;如果第一层有shapes键,那就是LabelMe风格。两个都有的话,优先用COCO的bbox,因为LabelMe里的多边形可能是高压标注,反而容易引入噪声。

有一点要注意:即使用了COCO格式,bbox也可能是[x1, y1, x2, y2]而不是[x, y, w, h]。这种“两点式”框在目标检测数据集里不算少见。保险起见,转换前随机抽一个注释,打印它的bbox长度和数值大小,判断是左上角宽高,还是左上角右下角。

2.3 用Python把JSON标签做一次“数据体检”

这一步是开训前最关键的事。不要急着转换格式,先把数据集从整体上扫一遍:图片数、标注数、类别分布、空标签、缺失图片。这个体检脚本可以直接对COCO和LabelMe两种格式复用。

import json import glob import os from collections import Counter # 假设解压在 dataset/ 目录下 img_dir = "dataset/images" label_dir = "dataset/annotations" # 情况一:COCO格式,所有标签在一个instances.json里 coco_file = glob.glob(f"{label_dir}/*.json") if len(coco_file) == 1 and "images" in json.load(open(coco_file[0], encoding="utf-8")): with open(coco_file[0], encoding="utf-8") as f: coco = json.load(f) print("图片数:", len(coco["images"])) print("标注框数:", len(coco["annotations"])) cat_map = {cat["id"]: cat["name"] for cat in coco["categories"]} counter = Counter() for ann in coco["annotations"]: counter[cat_map[ann["category_id"]]] += 1 print("类别分布:", dict(counter)) # 检查空标注图片 img_ids = {img["id"] for img in coco["images"]} ann_img_ids = {ann["image_id"] for ann in coco["annotations"]} print("无标注图片数:", len(img_ids - ann_img_ids)) # 情况二:LabelMe格式,每图一个json labelme_jsons = glob.glob(f"{label_dir}/*.json") if labelme_jsons and "shapes" in json.load(open(labelme_jsons[0], encoding="utf-8")): empty_count = 0 missing_img = 0 label_counter = Counter() for jf in labelme_jsons: with open(jf, encoding="utf-8") as f: data = json.load(f) if not data["shapes"]: empty_count += 1 img_path = os.path.join(img_dir, data.get("imagePath", "")) if not os.path.exists(img_path): missing_img += 1 for shape in data["shapes"]: label_counter[shape["label"]] += 1 print("空标签json数:", empty_count) print("图片缺失数:", missing_img) print("类别分布:", dict(label_counter))

这个脚本的作用是先建立“数据集黑匣子”的白盒视图。假如出现“图片数2000,标注框数只有500”,说明大量图片没有标注,训练时会把这些图当成背景图,严重干扰模型收敛。出现这种情况,要么删掉这些空图,要么重新检查JSON导出过程。

类别分布也要重点看。目标是两个类:西红柿和大番茄。如果打印出来多了一个“Tool”或者“小番茄”,说明标注软件里混入了其他类别标签。YOLO训练只认你给的classes.txt,多出来的类别要么合并,要么删掉,不能直接忽略。

3. 把JSON标签转成YOLO能用的txt:转换脚本与四个边界坑

3.1 YOLO的txt坐标为什么是“归一化的中心点”——先理解再转换

YOLO系列模型训练时,每张图片对应一个同名txt文件,每行格式是class x_center y_center width height。这四个坐标都是相对图像宽高的比例,取值在0到1之间。比如图片宽度是1920,一个框左边x等于480,框宽是960,那么归一化后的x_center就是(480 + 960/2) / 1920 = 0.5。

这种归一化设计是有原因的:模型在训练时会对输入图片做resize,无论原图是 1920×1080 还是 640×480,只要标签也是归一化比例,resize后标签依然有效,不需要重新计算坐标。这也是JSON里的像素坐标不能直接给YOLO用的根本原因。

所以转换的核心就是一套换算:像素坐标除以图像宽高得到比例,左上角坐标加上半宽得到中心点。另外还要记住一个反向关系,推理时输出的也是归一化坐标,画框时反过来乘以图像宽高就能还原成像素框。

3.2 转换脚本:同时兼容COCO和LabelMe

实际数据处理中,一个数据集的来源经常不止一种标注工具。我习惯写一个同时兼容COCO和LabelMe的转换函数,这样无论rar里是哪一种JSON,都能一步到位。

import json import os import glob # 定义类别顺序,后面YOLO就按这个顺序输出 # 这里固定为 ['tomato_small', 'tomato_big'],即 0 和 1 categories = ["tomato_small", "tomato_big"] def coco_to_yolo(coco_json, out_dir): with open(coco_json, encoding="utf-8") as f: coco = json.load(f) cat_id_map = {} for cat in coco["categories"]: name = cat["name"] # 如果类别名是中文,先映射到标准英文 if name == "西红柿": cat_id_map[cat["id"]] = 0 elif name in ("大番茄", "tomato_big", "big_tomato"): cat_id_map[cat["id"]] = 1 else: # 其他类别按出现顺序补位,但这里建议合并进已有类 cat_id_map[cat["id"]] = len(categories) os.makedirs(out_dir, exist_ok=True) for img in coco["images"]: width = img["width"] height = img["height"] lines = [] for ann in coco["annotations"]: if ann["image_id"] != img["id"]: continue x, y, w, h = ann["bbox"] # 过滤异常框:宽高为0或负值 if w <= 0 or h <= 0: continue # 转为归一化中心点坐标 x_center = (x + w / 2) / width y_center = (y + h / 2) / height w_norm = w / width h_norm = h / height # 防止边界溢出,裁剪到[0,1] x_center = max(0, min(1, x_center)) y_center = max(0, min(1, y_center)) w_norm = max(0, min(1, w_norm)) h_norm = max(0, min(1, h_norm)) lines.append(f"{cat_id_map[ann['category_id']]} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n") txt_name = os.path.splitext(os.path.basename(img["file_name"]))[0] + ".txt" with open(os.path.join(out_dir, txt_name), "w", encoding="utf-8") as f: f.writelines(lines) print("COCO转换完成,输出目录:", out_dir) def labelme_to_yolo(json_dir, out_dir, img_root): jsons = glob.glob(os.path.join(json_dir, "*.json")) os.makedirs(out_dir, exist_ok=True) for jf in jsons: with open(jf, encoding="utf-8") as f: data = json.load(f) img_rel = data["imagePath"] img_path = os.path.abspath(os.path.join(img_root, img_rel)) # 获取图片宽高 import cv2 img = cv2.imread(img_path) if img is None: print("警告: 图片读取失败,跳过", img_path) continue height, width = img.shape[:2] lines = [] for shape in data["shapes"]: label = shape["label"] # LabelMe用的是多边形点,取外接矩形 pts = shape["points"] xs = [p[0] for p in pts] ys = [p[1] for p in pts] x1 = min(xs) y1 = min(ys) x2 = max(xs) y2 = max(ys) w = x2 - x1 h = y2 - y1 if w <= 0 or h <= 0: continue x_center = (x1 + w / 2) / width y_center = (y1 + h / 2) / height if label == "西红柿": cls_id = 0 elif label in ("大番茄", "tomato_big"): cls_id = 1 else: continue # 未知标签直接丢弃 lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w/width:.6f} {h/height:.6f}\n") txt_name = os.path.splitext(os.path.basename(jf))[0] + ".txt" with open(os.path.join(out_dir, txt_name), "w", encoding="utf-8") as f: f.writelines(lines) print("LabelMe转换完成,共处理", len(jsons), "个文件")

脚本里有几个参数值得单独说明:categories列表的顺序就是YOLO训练时的类别id顺序,一旦定了就不要在训练途中改动;cat_id_map的存在是为了处理COCO原始category_id不连续的情况;w_norm和h_norm用max/min裁剪看起来多余,但在一些标注不规范的数据集里,框会超出图像边界,不裁剪的话训练时会让模型学到错误的位置信息。

3.3 类别映射与train/val划分:先定类别再切分

转换工作完成后,下一步是划分训练集和验证集。划分的一个大原则是:先定类别顺序,再切分文件。因为YOLO的dataset.yaml里names顺序必须和txt里的数字一一对应,如果后面发现类别顺序错了,整个训练集都要重新转。

我一般会这样划分:把所有图片和txt文件放在一起,用随机种子打乱顺序,然后取80%到images/train和labels/train,20%到images/val和labels/val。注意不能用简单的“前80个文件归训练,后20个归验证”,因为数据集的原始排列往往按采集时间、地块或光照条件分组,会引入分布偏差。

import random import os import shutil # train_val_split.py random.seed(42) samples = [f.replace(".jpg", "") for f in os.listdir("dataset/images") if f.endswith(".jpg")] random.shuffle(samples) split_idx = int(len(samples) * 0.8) image_src = "dataset/images" label_src = "dataset/labels_yolo" os.makedirs("dataset/images/train", exist_ok=True) os.makedirs("dataset/images/val", exist_ok=True) os.makedirs("dataset/labels/train", exist_ok=True) os.makedirs("dataset/labels/val", exist_ok=True) for name in samples[:split_idx]: shutil.move(f"{image_src}/{name}.jpg", "dataset/images/train/") shutil.move(f"{label_src}/{name}.txt", "dataset/labels/train/") for name in samples[split_idx:]: shutil.move(f"{image_src}/{name}.jpg", "dataset/images/val/") shutil.move(f"{label_src}/{name}.txt", "dataset/labels/val/")

这个脚本用随机种子保证了可复现。如果你用多卡训练或者后续要调参,固定seed非常重要,否则每次划分的验证集都不一样,模型对比就没有意义。划分后建议顺手打印train/val里每个类别的框数量,确保没有出现“验证集里全是大番茄,训练集里全是西红柿”这种极端分布。

3.4 验证转换结果:把txt画的框画回原图,比看坐标更直观

坐标数值看起来对,不代表框就贴对了。我见过不少转换后“换算公式错了但数值恰好不报错”的情况,比如把x和y互换了、把宽高当成了中心点。与其肉眼盯着txt,不如直接画图。

import cv2 import os def draw_yolo_txt(image_path, txt_path, out_path): img = cv2.imread(image_path) if img is None: return with open(txt_path, encoding="utf-8") as f: lines = f.readlines() for line in lines: parts = line.strip().split() cls_id = int(parts[0]) x_c, y_c, w_n, h_n = map(float, parts[1:]) h, w = img.shape[:2] x1 = int((x_c - w_n / 2) * w) y1 = int((y_c - h_n / 2) * h) x2 = int((x_c + w_n / 2) * w) y2 = int((y_c + h_n / 2) * h) color = (0, 255, 0) if cls_id == 0 else (0, 0, 255) label = "tomato_small" if cls_id == 0 else "tomato_big" cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, label, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1, cv2.LINE_AA) cv2.imwrite(out_path, img) print("已保存:", out_path) # 随机抽5张验证 import glob import random samples = glob.glob("dataset/images/val/*.jpg")[:5] for s in samples: name = os.path.basename(s).replace(".jpg", "") txt = f"dataset/labels/val/{name}.txt" draw_yolo_txt(s, txt, f"debug/{name}_check.jpg")

这一步能看到两类框的颜色区分。绿色是tomato_small,红色是tomato_big。如果发现红框位置偏了,优先检查JSON里bbox是[x,y,w,h]还是[x1,y1,x2,y2];如果发现所有框都偏移了半个图,那多半是像素坐标除以了错误的宽高,或者原始json里的图片尺寸字段和实际图片尺寸不一致。

很多数据集的问题在这一步就暴露了,千万不要跳过。

4. 用YOLOv8训练西红柿&大番茄检测模型:命令与参数全部给到

4.1 准备dataset.yaml:数据集的“门牌地址”

YOLOv8训练时不会自己去扫描目录,它需要一个yaml文件告诉它“图片和标签在哪、类别是什么”。这个yaml是训练前最后一道配置关。

# dataset.yaml path: /data/tomato_dataset # 数据集根目录,写绝对路径最保险 train: images/train # 相对于path的训练图片目录 val: images/val # 验证图片目录 nc: 2 # 类别数,这里必须是2 names: 0: tomato_small 1: tomato_big

path这一项是最容易出错的。如果写的是相对路径,不同目录下执行训练命令结果可能完全不一样,我习惯在yaml里直接写死绝对路径,并在训练前确认这个目录存在。train和val指向的目录里应该只有图片文件,YOLO会自动在同级目录的labels子目录下寻找同名txt文件。也就是说images/train/1.jpg对应labels/train/1.txt,这两个目录必须把名字匹配好。

names的顺序就是前面转换脚本里categories的顺序。如果你在3.2里用的是["tomato_small", "tomato_big"],这里就必须保持同样顺序,否则模型输出和标注对不上。

4.2 训练命令选型:模型尺寸、batch、epochs怎么定

配置好yaml后,直接用YOLOv8的CLI命令开始训练。常见做法是用官方预训练权重做微调,不要从随机权重开始。因为西红柿检测属于相对简单的视觉任务,预训练模型已经学会了基础纹理和形状特征,微调几十个epoch就能收敛。

yolo detect train \ data=/data/tomato_dataset/dataset.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ workers=8 \ seed=42

这里的参数我按重要性逐个说:

  • model=yolov8n.pt:模型大小。n是nano,最轻量,适合两类目标和不算太小的果实检测。如果你的图片里有很多小番茄,或者目标占比很小,换成yolov8s.pt甚至yolov8m.pt会更稳,但显存占用和训练时间都会上升。
  • imgsz=640:训练输入尺寸。原图如果是1920×1080,直接缩到640会让小目标信息流失。可以先跑一版640看mAP,如果小番茄漏检明显,再调到960或1280。
  • batch=16:按显存定。8GB卡建议8,16GB卡可以16到32。batch太大不提升精度,太小则loss抖动厉害。
  • epochs=100:对微调来说足够。YOLOv8默认early stopping patience是100,意思是连续100个epoch验证集mAP没提升就自动停。实际训练经常在第50到80轮就停了。
  • seed=42:固定随机种子,保证每次训练可复现。

训练过程中终端会打印每轮的box_loss、cls_loss、mAP50等指标。第一轮mAP可能很低,不要急着停,看完整周期再说。

4.3 训练过程看什么:loss曲线、mAP、PR曲线

训练结束后,训练目录下会生成results.png、confusion_matrix.png、PR_curve.png等文件。这些图不是摆设,我是按下面的顺序来判断模型是否合格:

  1. 看results.png里train/box_loss和val/box_loss是否整体下降。如果val loss下降到一半又反弹,说明学习率太高或者数据有问题。
  2. 看mAP50是否稳定在一个阈值以上。西红柿这类目标大、类别少的数据集,mAP50达到0.9以上才算合格;如果只有0.7,说明类别混淆或标注噪声严重。
  3. 看confusion_matrix.png的最后一列background(背景)。如果背景误检率高,说明模型把很多非目标物体当成了番茄,常见原因是空标注图片没有清理。

这里想再强调一次:训练参数不是固定的。imgsz和batch是强相关的两个变量,如果以后你的显卡变了,这两个参数需要重新匹配,不要照抄一个配置就期望在所有硬件上得到一致结果。

5. 避坑:训练西红柿&大番茄检测最常见的5个翻车点

5.1 类别标签错位:大番茄全被认成西红柿

现象:训练完mAP很高,但跑实际图片时,所有大番茄都被框成西红柿类,小番茄却几乎不识别。

原因:转换时直接沿用了JSON里原始的category_id,但原始id可能是1和3,YOLO只认0和1,导致类别数字发生了位移。

解决:在转换脚本里必须显式建立一个重新映射的字典,比如cat_id_map = {1: 0, 3: 1},而不是简单取ann["category_id"]。同时用3.4的画框脚本可视化几十张,确认“绿色框是西红柿、红色框是大番茄”后再训练。

5.2 JSON里隐藏的“空框”和“重复框”

现象:训练时某几张图的loss异常大,loss曲线出现尖刺。检查发现一张图上居然有50多个框,但图片上只有3个番茄。

原因:数据标注软件在导出时把多次编辑的历史框重复保留了;还有部分框宽高为0或负值,转换脚本没有过滤。

解决:转换前统一过滤宽高小于等于0的框;对同一张图里IoU大于0.9的重复框,只保留面积更大的那个。这样做的逻辑是,重复框来自同一目标,训练时相当于给这个目标加了成倍权重,会破坏类别平衡。

5.3 imgsz与图片尺寸不匹配导致小番茄漏检

现象:训练时mAP50还行,但实际部署到采摘相机上,远处的番茄漏检严重,框出来的都是近处的。

原因:数据原图是1920×1080,缩放到640×640后,长期处于画面边缘的番茄可能只有8像素宽,模型根本学不到这个特征。

解决:先把训练参数里的imgsz调到1280跑一版对比mAP。如果显存不够,退而求其次,把原图按中心裁剪成多个1024×1024 tiles再训练,推理时也按tile拼图预测。对农业场景,切图比拉高分辨率更实用。

5.4 数据集划分不随机,验证集变成“单一光照专场”

现象:训练集loss下降到0.05,验证集mAP却不到0.6,而且每次重启训练验证集结果都不稳定。

原因:图片文件在目录里按采摘当天的顺序排列,前80%全是晴天温室,后20%全是阴天大棚。如果你按文件顺序切train/val,验证集就等于只在阴天图片上测试。

解决:用随机种子打乱文件列表后,再按8:2划分。进阶做法是按“采集地点或大棚编号”分组:同一个大棚的图片只进训练集或验证集,避免模型因为记住了大棚环境特征而虚高。

5.5 增强参数太凶,西红柿颜色完全失真

现象:训练过程mAP不断升高,但可视化验证集预测结果时,明明是大红色的西红柿被框成偏蓝色,模型好像分不清颜色了。

原因:YOLOv8默认开启的HSV增强会把色相、饱和度拉得很大,原本用于通用检测的颜色扰动,对果蔬这类颜色高度敏感的目标反而有害。

解决:训练时显式限制增强参数,在命令后加上hsv_h=0.01 hsv_s=0.3 hsv_v=0.3,甚至全部设为0。果蔬检测里,形状和纹理是主要判别线索,颜色增强可以做,但必须温柔,否则模型学到的“西红柿是红的”这个基本规律会被破坏。

6. 最后一步:把模型导出并做一次端到端验证

6.1 用最佳权重在测试集上做“交付验收”

训练完成后,runs/detect/train/weights/best.pt就是验证集表现最好的权重。在正式用之前,我习惯先跑一遍测试集,把结果落成可检查的图片而不是只看终端指标。

yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=dataset/images/test \ conf=0.25 \ save_txt=True \ save_conf=True \ project=runs/verify \ name=final_check

这里的conf=0.25是置信度阈值,低于0.25的预测框会被过滤掉。测试集里如果有很多遮挡严重的番茄,可以降到0.1再看效果,但部署时还是建议回到0.25,减少误检。save_txt=True会输出每个预测框的txt结果,方便用脚本统计检测数量和类别分布,比肉眼翻图更可靠。

做完这一步,才算对模型质量有一个明确的认知。如果测试集mAP和目标场景差距大,优先回头检查数据划分和标注噪声,而不是调整训练参数。

6.2 导出ONNX并写一个最小推理Demo

部署阶段,我一般会把best.pt导出成ONNX格式,这样既能脱离训练框架推理,也能在边缘设备上跑。

yolo export model=runs/detect/train/weights/best.pt format=onnx imgsz=640 dynamic=True

用ONNX做推理时,输出张量是一个[1, 6, 8400]或[1, 84, 8400]形状,需要自己补充NMS后处理。下面是一个最小可运行的Python推理示例:

import cv2 import numpy as np import onnxruntime as ort session = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) input_name = session.get_inputs()[0].name image = cv2.imread("test.jpg") img_h, img_w = image.shape[:2] resized = cv2.resize(image, (640, 640)).astype(np.float32) / 255.0 blob = np.transpose(resized, (2, 0, 1))[None] outs = session.run(None, {input_name: blob})[0] # [1, 84, 8400] # 取第0张,去掉前4个坐标和类别数 preds = np.transpose(outs[0]) boxes = preds[:, :4] scores = preds[:, 4:6] # 两个类别的置信度 box = boxes * np.array([img_w, img_h, img_w, img_h]) # 反归一化 cv2.rectangle(image, (int(box[0,0]), int(box[0,1])), (int(box[0,2]), int(box[0,3])), (0,255,0), 2) cv2.imwrite("result.jpg", image)

这里只演示了单框输出,实际需要先对8400个候选框做NMS,再取每个类别的最高分框。推理时如果发现框整体偏移,多半是letterbox预处理没有还原坐标,务必把缩放和padding的偏移量补回来。

我现在的经验习惯是,拿到任何封成压缩包的数据集,第一件事绝不是开训,而是先做数据体检和真实框可视化,确认图片和JSON真的对得上。如果这一步最开始不做,之后所有mAP数字都是不可靠的。希望这篇能帮你把这套流程跑顺,也让番茄检测少走我走过的那些弯路。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询