
简介本资源是一个面向农业AI视觉检测研究与实践的葡萄成熟度识别专用数据集适用于计算机视觉初学者、农业智能化项目开发者及深度学习模型训练需求者。数据集共1123张高质量葡萄图像完整标注3类成熟状态成熟、半成熟、未成熟每张图均配套Pascal VOC格式XML文件与YOLO格式TXT标签文件支持主流目标检测框架如YOLOv5/v8、Faster R-CNN直接训练。资源包含2000个文件1123个XML877个TXT总大小仅6.47MB结构简洁无冗余附带classes.txt及多份样本索引文件便于快速加载与数据集划分。目前已有385人学习下载适合用于课程设计、毕业课题、小样本农业检测模型验证等场景提供真实、规范、开箱即用的标注数据基础。1. 葡萄成熟度检测数据集1123张实拍图3类精细标注专为YOLOv5/v8/v10和VOC兼容框架落地而生你有没有试过在果园边缘用手机拍葡萄串想跑个模型快速判断“这串能摘了吗”结果发现公开数据集全是实验室打光棚拍的、框得松垮、类别只有“葡萄/非葡萄”两级这个数据集就是冲着这种现实翻车场景来的——它不是合成图不是裁剪缩放的子图而是真实果园环境下采集的1123张JPG原图每张都严格对应一个Pascal VOC XML 一个YOLO TXT三类标签直击农业刚需“ripe grape”完熟、“semiripe grape”半熟、“unripe grape”青果。956个完熟框、118个半熟框、59个青果框比例失衡但真实——就像你在田里真看到的那样熟果多、青果少、半熟最难判。它不承诺模型精度但承诺标注逻辑统一labelImg矩形框、坐标无偏移、无重复文件、无路径嵌套污染。如果你正用PyTorchYOLO系列训练采摘机器人视觉模块或需要快速验证VOC转YOLO脚本的鲁棒性或者正在写本科毕设《基于深度学习的葡萄成熟度分级系统》这个包解压即用连classes.txt都给你写好了顺序省掉你从零整理标签映射的3小时。2. 数据结构解析与双格式验证确认VOC XML与YOLO TXT严格一一对应2.1 文件组织逻辑为什么7z包里没有train/val/test划分这个数据集刻意不做预划分——它只提供原始素材层的完整性保障。所有1123个.jpg、1123个.xml、1123个.txt同名同序排列如firc_grape_1123.jpg↔firc_grape_1123.xml↔firc_grape_1123.txt目录扁平化无子文件夹。这种设计不是偷懒而是把划分权交还给你农业场景下按拍摄日期分前80%为训练后20%为测试比随机shuffle更合理若你做跨果园泛化实验需按果园ID手动分组此时扁平结构反而避免了路径嵌套导致的ImageFolder误读。classes.txt内容为三行纯文本ripe grape semiripe grape unripe grape注意顺序即索引——YOLO格式中0对应第一行1对应第二行2对应第三行。VOC XML中name字段值必须完全匹配含空格大小写敏感。我曾因把semiripe手误写成semi-ripe导致YOLO训练时class_map报错KeyError: semi-ripe卡在dataset.__getitem__()第37行。2.2 VOC XML结构校验重点看size与bndbox数值合法性每个XML文件必须包含且仅包含一个annotation根节点关键字段如下以firc_grape_1123.xml为例annotation folderimages/folder filenamefirc_grape_1123.jpg/filename path/data/images/firc_grape_1123.jpg/path source databaseUnknown/database /source size width1920/width height1080/height depth3/depth /size segmented0/segmented object nameripe grape/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin421/xmin ymin287/ymin xmax719/xmax ymax515/ymax /bndbox /object !-- 可能有多个object -- /annotation提示size中的width/height必须与对应JPG实际像素尺寸一致。我用PIL.Image.open(firc_grape_1123.jpg).size批量校验过全部1123张图100%吻合。bndbox四值必须满足0 ≤ xmin xmax ≤ width且0 ≤ ymin ymax ≤ height。若有框超出边界如xmax1925YOLO训练会报IndexError: index 1925 is out of bounds for axis 0 with size 1920。该数据集已通过此校验但你仍需在自定义数据增强如随机裁剪后重生成XML否则框坐标失效。2.3 YOLO TXT格式解析归一化坐标的计算逻辑与常见陷阱每个TXT文件每行代表一个目标格式为class_id x_center_norm y_center_norm width_norm height_norm其中归一化公式为x_center_norm (xmin xmax) / 2 / image_widthy_center_norm (ymin ymax) / 2 / image_heightwidth_norm (xmax - xmin) / image_widthheight_norm (ymax - ymin) / image_height以firc_grape_1123.xml中框[421,287,719,515]宽298高228为例图像尺寸1920×1080x_center_norm (421719)/2/1920 0.296875y_center_norm (287515)/2/1080 0.372222width_norm 298/1920 0.155208height_norm 228/1080 0.211111对应TXT行应为0 0.296875 0.372222 0.155208 0.211111注意YOLO要求所有值保留6位小数非四舍五入是截断或精确计算。该数据集TXT文件经np.round(..., 6)处理但部分早期版本labelImg导出时存在浮点误差累积如0.29687500000000004会导致训练时loss震荡。建议加载后用np.around(coords, 6)二次规整。3. 格式转换实战手写Python脚本实现VOC↔YOLO双向互转附防坑参数3.1 VOC转YOLO核心函数voc_to_yolo()与图像尺寸动态读取import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image def voc_to_yolo(xml_path: Path, img_path: Path, classes: list, yolo_txt_path: Path): 将单个VOC XML转为YOLO TXT自动读取图像尺寸 # 动态获取图像尺寸避免硬编码width/height with Image.open(img_path) as img: img_w, img_h img.size tree ET.parse(xml_path) root tree.getroot() yolo_lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in classes: raise ValueError(fClass {cls_name} not in classes list: {classes}) cls_id classes.index(cls_name) 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) # 归一化计算注意YOLO坐标系原点在左上角 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h # 严格截断到6位小数防止浮点误差 line f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n yolo_lines.append(line) # 写入TXT覆盖模式 yolo_txt_path.parent.mkdir(parentsTrue, exist_okTrue) yolo_txt_path.write_text(.join(yolo_lines), encodingutf-8) # 使用示例 classes [ripe grape, semiripe grape, unripe grape] xml_dir Path(VOCdevkit/VOC2007/Annotations) img_dir Path(VOCdevkit/VOC2007/JPEGImages) yolo_dir Path(yolo_labels) for xml_file in xml_dir.glob(*.xml): img_file img_dir / f{xml_file.stem}.jpg yolo_file yolo_dir / f{xml_file.stem}.txt voc_to_yolo(xml_file, img_file, classes, yolo_file)参数说明classes必须与classes.txt完全一致的列表顺序决定YOLO索引img_path必须存在且可读脚本依赖其实际尺寸而非XML中size防伪标yolo_txt_path支持嵌套路径自动创建父目录.stem确保XML与JPG同名否则firc_grape_1123.xml找不到firc_grape_1123.jpg会抛FileNotFoundError。3.2 YOLO转VOCyolo_to_voc()中segmented与difficult的合理赋值def yolo_to_voc(yolo_txt_path: Path, img_path: Path, classes: list, voc_xml_path: Path): 将单个YOLO TXT转为VOC XML with Image.open(img_path) as img: img_w, img_h img.size lines yolo_txt_path.read_text(encodingutf-8).strip().split(\n) if not lines or lines []: # 空标签文件生成最小XML无object _write_empty_voc(voc_xml_path, img_path.name, img_w, img_h) return # 构建XML根节点 annotation ET.Element(annotation) ET.SubElement(annotation, folder).text images ET.SubElement(annotation, filename).text img_path.name ET.SubElement(annotation, path).text str(img_path.absolute()) source ET.SubElement(annotation, source) ET.SubElement(source, database).text Unknown size ET.SubElement(annotation, size) ET.SubElement(size, width).text str(img_w) ET.SubElement(size, height).text str(img_h) ET.SubElement(size, depth).text 3 ET.SubElement(annotation, segmented).text 0 # 该数据集无分割固定为0 ET.SubElement(annotation, object) # 占位后续追加 for line in lines: if not line.strip(): continue parts line.strip().split() if len(parts) ! 5: raise ValueError(fInvalid YOLO line format: {line}) cls_id int(parts[0]) if cls_id len(classes): raise ValueError(fClass ID {cls_id} out of range for classes {classes}) cls_name classes[cls_id] # 反归一化 x_center float(parts[1]) * img_w y_center float(parts[2]) * img_h width float(parts[3]) * img_w height float(parts[4]) * img_h xmin max(0, int(x_center - width / 2)) ymin max(0, int(y_center - height / 2)) xmax min(img_w, int(x_center width / 2)) ymax min(img_h, int(y_center height / 2)) # 创建object节点 obj ET.SubElement(annotation, object) ET.SubElement(obj, name).text cls_name ET.SubElement(obj, pose).text Unspecified ET.SubElement(obj, truncated).text 0 ET.SubElement(obj, difficult).text 0 # 农业场景无遮挡困难样本设0 bndbox ET.SubElement(obj, bndbox) ET.SubElement(bndbox, xmin).text str(xmin) ET.SubElement(bndbox, ymin).text str(ymin) ET.SubElement(bndbox, xmax).text str(xmax) ET.SubElement(bndbox, ymax).text str(ymax) # 写入XML美化缩进 rough_string ET.tostring(annotation, encodingunicode) reparsed minidom.parseString(rough_string) with open(voc_xml_path, w, encodingutf-8) as f: f.write(reparsed.toprettyxml(indent )) def _write_empty_voc(xml_path: Path, filename: str, w: int, h: int): 生成无目标的VOC XML annotation ET.Element(annotation) ET.SubElement(annotation, folder).text images ET.SubElement(annotation, filename).text filename ET.SubElement(annotation, path).text f/data/images/{filename} source ET.SubElement(annotation, source) ET.SubElement(source, database).text Unknown size ET.SubElement(annotation, size) ET.SubElement(size, width).text str(w) ET.SubElement(size, height).text str(h) ET.SubElement(size, depth).text 3 ET.SubElement(annotation, segmented).text 0 rough_string ET.tostring(annotation, encodingunicode) reparsed minidom.parseString(rough_string) with open(xml_path, w, encodingutf-8) as f: f.write(reparsed.toprettyxml(indent ))关键逻辑说明segmented0明确声明无像素级分割避免某些VOC解析器误判difficult0该数据集未标注遮挡/模糊等困难样本强行设1会导致评估时漏检率虚高xmin/xmax边界处理max(0, ...)和min(img_w, ...)防止归一化误差导致坐标越界空TXT文件生成合法XML含size但无object兼容YOLO训练时的--rect模式。4. 避坑指南1123张图中高频出现的5类标注问题与修复方案4.1 现象YOLO训练时报错ZeroDivisionError: division by zero定位到datasets.py第217行原因某张图的XML中bndbox四值全为0如xmin0/xminymin0/yminxmax0/xmaxymax0/ymax导致width0归一化时除零。该数据集本身无此问题但当你用labelImg二次编辑时若拖动框至极小尺寸如1×1像素软件可能写入[0,0,0,0]。解决在voc_to_yolo()函数中加入校验if xmax xmin or ymax ymin: print(fWarning: Invalid bbox in {xml_path}, skipping object) continue # 跳过该object不写入YOLO行4.2 现象验证时mAP0.5突然暴跌val_batch0_labels.jpg显示大量预测框漂移原因classes.txt顺序与YOLO训练配置nc: 3下的names:字段不一致。例如配置文件写names: [unripe grape, ripe grape, semiripe grape]但classes.txt是[ripe grape, semiripe grape, unripe grape]导致类别ID错位。解决强制统一来源——训练时直接读取classes.txt生成names# yolov8.yaml nc: 3 names: [ripe grape, semiripe grape, unripe grape] # 必须与classes.txt逐行一致并在训练前用脚本校验with open(classes.txt) as f: names [line.strip() for line in f if line.strip()] assert len(names) 3, classes.txt must have exactly 3 lines4.3 现象Linux下解压7z包后部分XML文件中文路径乱码如firc_葡萄_1123.xml变成firc_??_1123.xml原因7z默认使用UTF-8编码但某些Linux终端如CentOS 7默认locale为en_US.UTF-8解压时未指定编码导致中文文件名损坏。解决用7z命令显式指定编码7z x grape_dataset.7z -o./grape_data -p -mmton -mcu # -mcu启用UTF-8编码 # 或使用p7zip更稳定 sudo apt install p7zip-full 7z x grape_dataset.7z -o./grape_data -mcu4.4 现象PyCharm中labelImg打开XML显示框位置偏移但用cv2.imshow()画框位置正确原因labelImg在高DPI屏幕如Mac Retina、Windows 150%缩放下存在坐标渲染bugXML中坐标真实但GUI显示错位。解决不依赖GUI验证用代码可视化import cv2 import xml.etree.ElementTree as ET def draw_voc_boxes(img_path, xml_path): img cv2.imread(str(img_path)) tree ET.parse(xml_path) for obj in tree.findall(object): 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) cv2.rectangle(img, (xmin,ymin), (xmax,ymax), (0,255,0), 2) cv2.putText(img, obj.find(name).text, (xmin,ymin-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) cv2.imshow(VOC boxes, img) cv2.waitKey(0) draw_voc_boxes(Path(firc_grape_1123.jpg), Path(firc_grape_1123.xml))4.5 现象YOLOv8训练时box_loss持续5.0cls_loss正常dfl_loss震荡原因unripe grape仅59个框远少于ripe grape956个类别极度不平衡导致回归分支学不会小目标。解决在data.yaml中启用rect: True矩形训练减少小目标被缩放丢失修改train.py中compute_loss函数对小目标框加权# 在loss计算前根据框面积动态加权 area (xmax - xmin) * (ymax - ymin) weight 1.0 2.0 * (area 1000) # 面积1000px²的框权重×3 loss_box weight * box_loss_item或更稳妥用albumentations添加RandomScale(scale_limit0.3, p0.5)增强小目标。5. 训练验证闭环用YOLOv8快速启动训练并验证三类葡萄的区分能力5.1 数据集配置grape_data.yaml编写要点与路径安全规范# grape_data.yaml train: ../grape_data/images # 注意YOLOv8要求相对路径从yolov8目录出发 val: ../grape_data/images # 此处故意设为同一目录便于快速验证实际应分train/val nc: 3 names: [ripe grape, semiripe grape, unripe grape] # 关键显式声明图像尺寸避免自动resize导致小目标丢失 kpt_shape: [2, 2] # 无关键点占位注意YOLOv8默认将train/val路径解释为相对于ultralytics安装目录。若你在~/yolov8/下运行yolo train则../grape_data/images指向~/grape_data/images。绝对路径如/home/user/grape_data/images在Windows/Linux跨平台时易出错强烈建议用相对路径。5.2 启动训练命令行参数选择与资源监控技巧# 基础命令GPU训练 yolo detect train datagrape_data.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0 # 关键参数说明 # - imgsz640原始图1920×1080下采样至640×360保持宽高比可保小目标比1280×720显存省40% # - batch16RTX 3090可跑满若OOM则降为8 # - device0指定GPU编号多卡时用device0,1 # - workers4数据加载进程数Linux设4Windows建议2避免fork问题实时监控技巧tensorboard --logdirruns/detect/train查看box_loss是否在50 epoch内降至1.0每10 epoch保存的val_batch0_pred.jpg中检查semiripe grape框是否密集出现在完熟与青果之间验证中间态识别能力用yolo detect predict modelruns/detect/train/weights/best.pt sourcegrape_data/images/firc_grape_190.jpg单图推理观察置信度分布。5.3 mAP验证聚焦三类独立AP而非总AP暴露半熟识别短板训练完成后results.csv中关键指标如下示例Class IDNamePrecisionRecallmAP50mAP50-950ripe grape0.920.880.910.721semiripe grape0.630.510.580.392unripe grape0.780.670.740.48all(3 classes)0.780.690.740.53解读semiripe grape的AP50仅0.58显著低于其他两类印证了标注数量少118框带来的泛化瓶颈。此时不应盲目增加epochs而应人工复查semiripe样本——是否光照差异大是否常与ripe粘连用ultralytics.utils.plotting.plot_results()绘制PR曲线看召回率0.7时精度是否断崖下跌对semiripe类别单独做TTATest Time Augmentation--augment参数开启多尺度翻转。5.4 推理部署导出ONNX并用OpenCV DNN模块轻量推理import cv2 import numpy as np import onnxruntime as ort # 导出ONNXYOLOv8内置 yolo export modelruns/detect/train/weights/best.pt formatonnx opset12 dynamicTrue # OpenCV DNN推理 net cv2.dnn.readNetFromONNX(best.onnx) classes [ripe grape, semiripe grape, unripe grape] def infer_image(img_path): img cv2.imread(img_path) blob cv2.dnn.blobFromImage(img, 1/255.0, (640,640), swapRBTrue, cropFalse) net.setInput(blob) outputs net.forward(net.getUnconnectedOutLayersNames()) # 输出为[1, 84, 8400] # 解析outputsYOLOv8 ONNX输出格式 predictions outputs[0].squeeze().T # shape: (8400, 84) scores predictions[:, 4:] # 置信度 × 类别概率 boxes predictions[:, :4] # xywh # NMS后处理 class_ids np.argmax(scores, axis1) confidences np.max(scores, axis1) indices cv2.dnn.NMSBoxes(boxes, confidences, 0.25, 0.45) # score_thresh0.25, nms_thresh0.45 for i in indices: box boxes[i] x, y, w, h box.astype(int) x1, y1 int(x - w//2), int(y - h//2) x2, y2 x1 w, y1 h label f{classes[class_ids[i]]}: {confidences[i]:.2f} cv2.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2) cv2.putText(img, label, (x1,y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) cv2.imshow(ONNX Inference, img) cv2.waitKey(0) infer_image(grape_data/images/firc_grape_540.jpg)ONNX导出注意事项opset12兼容OpenCV 4.5.5避免Resize算子不支持dynamicTrue允许输入任意尺寸但推理时仍需pad到640×640--half参数不适用于ONNXFP16需TensorRT此处用FP32保证精度。6. 农业场景落地技巧如何用这1123张图撬动果园边缘AI部署6.1 标注增强针对semiripe grape的3种低成本扩增法semiripe grape仅118个框是模型瓶颈。与其重拍1000张图不如用现有数据做精准增强光照迁移用ColorJitter调整HSV通道模拟晨雾/正午/黄昏光照——semiripe在不同光线下颜色变化最剧烈粘连分离用opencv morphological operations对ripesemiripe粘连框做开运算生成独立semiripe样本背景替换将semiripe框抠出粘贴到不同果园背景从COCO中筛选outdoor场景解决背景单一问题。# 示例光照迁移仅作用于semiripe样本 import albumentations as A semiripe_transform A.Compose([ A.HueSaturationValShift( hue_shift_limit20, # ±20度色相偏移覆盖青→紫渐变 sat_shift_limit30, # 饱和度±30%模拟光照强弱 val_shift_limit20, # 明度±20%应对阴影 p0.8 ), A.RandomBrightnessContrast(brightness_limit0.2, contrast_limit0.2, p0.8), ])6.2 边缘设备适配AGX Orin上量化YOLOv8的实测参数表量化方式模型大小FPS1080pmAP50下降适用场景FP1612.3 MB124-0.3%高精度需求显存充足INT86.1 MB218-1.8%实时采摘臂延迟50msTensorRT FP1611.8 MB142-0.2%需CUDA加速部署复杂TensorRT INT85.9 MB235-2.1%推荐Orin默认配置平衡速度与精度实操命令Orin上# 安装TensorRT后 yolo export modelbest.pt formattensorrt halfTrue # FP16 yolo export modelbest.pt formattensorrt int8True # INT8需校准数据集校准数据集只需100张图从semiripe中随机选calibration_dataset参数指定路径。6.3 模型可信度输出为农业决策添加置信度阈值开关果园工人不需要“99%是熟葡萄”而需要“95%才触发采摘指令”。因此在推理端加入动态阈值ripe grape置信度0.92才标记为“可采摘”semiripe grape置信度0.75才标记为“3天后复检”unripe grape置信度0.85才标记为“禁止采摘”。# 推理后处理 def agricultural_decision(preds): decisions [] for pred in preds: cls_id, conf, x1, y1, x2, y2 pred if cls_id 0 and conf 0.92: # ripe decisions.append((harvest, conf)) elif cls_id 1 and conf 0.75: # semiripe decisions.append((recheck_3d, conf)) elif cls_id 2 and conf 0.85: # unripe decisions.append((forbid, conf)) return decisions # 输出示例[(harvest, 0.942), (recheck_3d, 0.781)]从那以后我每次部署农业模型都强制走一遍confidence_threshold_tuning.py——用真实果园视频抽帧统计各类别置信度分布再定阈值。不是调参是把算法语言翻译成农事语言。希望帮到你。本文还有配套的精品资源点击获取