ARTICLE DETAIL

资讯详情

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

苹果照片数据集与Python处理:从414张原图到YOLOv3训练集

苹果照片数据集与Python处理:从414张原图到YOLOv3训练集 简介这份资源面向计算机视觉入门与进阶学习者以及需要完成目标检测课程设计或实验项目的开发者提供了一套可直接用于YOLOv3训练的苹果目标识别数据集及配套Python处理代码。包内共约2000个文件以1648张jpg图像和820个xml标注文件为主另含少量txt说明与py脚本压缩包约90.81MB。图像采用labelimg以预选框形式标注包含414张原始苹果图片与828张经数据增强、resize和填充处理后的图片xml文件与图像一一对应可直接转换为YOLO格式标签。配套Python代码覆盖数据预处理与格式转换环节省去从零搭建数据管线的成本。目前已有1073人学习下载适合希望快速跑通YOLOv3苹果检测流程、验证模型效果或作为二次开发起点的读者参考使用。1. 苹果照片数据集和 Python 处理代码414 张原图怎么撑起一个 YOLOv3 训练集手上只有几百张苹果照片想跑目标检测第一反应往往是「数据不够」。这份 VOC2007 格式的苹果数据集正好卡在这个痛点上414 张原始图片经过数据增强、resize 和填充后扩到 828 张配套 Python 处理脚本和 labelImg 生成的 xml 标注文件直接对接 YOLOv3 的训练管线。它不是那种动辄几万张的公开大集而是一个能让你在本地把「标注 → 增强 → 转格式 → 训练」整条链路跑通的完整小样本工程。适合两类人一是刚接触目标检测、想找个真实数据集练手的新手二是需要快速验证某个检测模型在小样本上表现的从业者。下面按「资源里有什么 → 怎么处理 → 怎么转 YOLO 格式 → 坑在哪」的顺序拆开讲。2. 数据集结构与 Python 预处理脚本从 414 张原图到 828 张增强样本2.1 目录结构与文件命名规律拿到压缩包解压后典型的目录长这样VOC2007/ ├── Annotations/ # xml 标注文件与图片同名 ├── JPEGImages/ # 原始图片414 张 ├── ImageSets/ │ └── Main/ # 训练/验证集划分文件 ├── SegmentationClass/ ├── SegmentationObject/ └── preprocess/ # 预处理脚本与增强后图片 ├── augment.py └── output/ # 828 张增强图图片命名是Apple (472).jpg、Apple (600).jpg这种带括号和序号的格式。这个命名本身没问题但在后续写脚本遍历时要注意括号在 shell 里是特殊字符用glob或os.listdir处理时不会出问题但如果你用 shell 通配符批量操作记得加引号或者转义。我一般直接用 Python 的pathlib遍历省得跟 shell 转义较劲。标注工具是 labelImg预选框形式生成的 xml 遵循 VOC2007 标准结构filename、size、object里的name、bndbox四个坐标。这套结构是后面转 YOLO 格式的基础先确认每个 xml 里的name字段统一是apple不然后面类别映射会出乱子。2.2 数据增强脚本拆解增强脚本的核心逻辑不复杂常见做法是用imgaug或albumentations做翻转、旋转、亮度调整再把原图和增强图一起 resize 到统一尺寸并填充。下面是一个能直接跑的增强脚本骨架import cv2 import numpy as np from pathlib import Path import albumentations as A # 输入输出路径 SRC_DIR Path(VOC2007/JPEGImages) DST_DIR Path(VOC2007/preprocess/output) DST_DIR.mkdir(parentsTrue, exist_okTrue) # 增强管线水平翻转 随机亮度 轻微旋转 transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.5), A.Rotate(limit15, p0.5, border_modecv2.BORDER_CONSTANT), ], bbox_paramsA.BboxParams(formatpascal_voc, label_fields[labels])) TARGET_SIZE 416 # YOLOv3 常用输入尺寸 def letterbox(img, target416): 等比缩放 灰边填充保持长宽比 h, w img.shape[:2] scale target / max(h, w) nh, nw int(h * scale), int(w * scale) resized cv2.resize(img, (nw, nh)) canvas np.full((target, target, 3), 128, dtypenp.uint8) top (target - nh) // 2 left (target - nw) // 2 canvas[top:topnh, left:leftnw] resized return canvas, scale, left, top for img_path in SRC_DIR.glob(*.jpg): img cv2.imread(str(img_path)) # 原图 resize 填充 lb_img, _, _, _ letterbox(img, TARGET_SIZE) cv2.imwrite(str(DST_DIR / forig_{img_path.name}), lb_img) # 增强图 aug transform(imageimg, bboxes[], labels[])[image] aug_img, _, _, _ letterbox(aug, TARGET_SIZE) cv2.imwrite(str(DST_DIR / faug_{img_path.name}), aug_img)这段代码有几个关键点要说清楚。letterbox是 YOLO 系列的标准预处理方式等比缩放后补灰边避免直接 resize 导致目标变形——苹果被拉扁了模型学到的形状特征就废了。填充值用 128 而不是 0是因为纯黑边在卷积里容易和真实暗色区域混淆128 的中性灰更安全。增强部分只做了翻转、亮度、旋转三种没上 cutout 或 mosaic因为小样本下过度增强反而会让模型学到噪声。BboxParams里传了空的 bboxes是因为这个脚本只处理图片标注的同步变换要在转格式阶段一起做否则框和图对不上这是新手最容易翻车的地方。2.3 增强前后的数量核对原图 414 张增强后 828 张正好是 2 倍。说明脚本对每张原图生成了一张增强图没有做多倍增强。这个量级对于 YOLOv3 来说偏小但作为流程验证够用。如果你要实际训练建议把增强倍数调到 35 倍同时保证验证集只用原图别把增强图混进 val否则评估指标会虚高。划分训练验证集时常见做法是按 8:2 切414 张原图里拿 330 张训练、84 张验证增强图只加进训练集。3. VOC xml 转 YOLO txt坐标归一化与类别映射的完整脚本3.1 两种格式的本质差异VOC 的 xml 存的是绝对像素坐标xmin, ymin, xmax, ymax原点在左上角。YOLO 的 txt 存的是归一化后的中心点坐标加宽高class_id cx cy w h每个值都在 01 之间。转换的核心就两步把绝对坐标转成中心点加宽高再除以图片的宽和高做归一化。听起来简单但图片经过 letterbox 之后坐标还得跟着缩放和平移一起变不然框会偏。3.2 转换脚本与参数说明import xml.etree.ElementTree as ET from pathlib import Path import cv2 CLASS_MAP {apple: 0} # 类别名到 id 的映射只有一个类 ANNO_DIR Path(VOC2007/Annotations) IMG_DIR Path(VOC2007/JPEGImages) LABEL_DIR Path(VOC2007/labels) LABEL_DIR.mkdir(exist_okTrue) def convert_bbox(size, box): VOC 绝对坐标 - YOLO 归一化中心坐标 dw, dh 1.0 / size[0], 1.0 / size[1] x (box[0] box[1]) / 2.0 y (box[2] box[3]) / 2.0 w box[1] - box[0] h box[3] - box[2] return x * dw, y * dh, w * dw, h * dh for xml_file in ANNO_DIR.glob(*.xml): tree ET.parse(xml_file) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in CLASS_MAP: continue cls_id CLASS_MAP[cls_name] bndbox obj.find(bndbox) box ( float(bndbox.find(xmin).text), float(bndbox.find(xmax).text), float(bndbox.find(ymin).text), float(bndbox.find(ymax).text), ) cx, cy, bw, bh convert_bbox((w, h), box) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_path LABEL_DIR / (xml_file.stem .txt) out_path.write_text(\n.join(lines))convert_bbox里先算中心点和宽高再乘1/w和1/h做归一化。保留 6 位小数是 YOLO 社区的惯例精度够用又不会让文件太大。CLASS_MAP只有一个类别apple映射到 0如果你后面要加其他水果在这里扩展就行但记得类别 id 从 0 开始连续别跳号。脚本会跳过不在CLASS_MAP里的类别这是有意的——防止标注里混进拼写错误的类别名导致训练时报错。3.3 转换后的校验方法转完别急着训练先做两件事。第一随机抽 5 个 txt 文件用 OpenCV 把框画回图上肉眼看框有没有偏。第二统计每个 txt 的行数如果某张图应该有苹果但 txt 是空的说明 xml 里类别名对不上或者框坐标有问题。校验脚本很短import cv2 from pathlib import Path img cv2.imread(VOC2007/JPEGImages/Apple (472).jpg) h, w img.shape[:2] for line in Path(VOC2007/labels/Apple (472).txt).read_text().splitlines(): cid, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw/2) * w) y1 int((cy - bh/2) * h) x2 int((cx bw/2) * w) y2 int((cy bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check.jpg, img)画出来的框如果贴着苹果边缘说明转换正确如果框整体偏移或大小不对回头查 xml 里的size字段和实际图片尺寸是否一致——有些标注工具会写错宽高这是血泪经验里最常见的一类问题。4. 避坑与排查小样本苹果数据集训练时最容易翻车的五件事4.1 现象训练 loss 一直不降mAP 接近 0原因通常是类别映射错了。YOLOv3 的classes配置和CLASS_MAP对不上比如配置文件里写了 80 类实际只有 1 类模型输出的通道数不匹配梯度全是乱的。解决检查yolov3.cfg里yolo层上方的classes1以及filters18公式是(classes5)*3两个数必须同步改。4.2 现象框画出来整体偏右下或偏左上原因是 letterbox 的填充偏移没同步到标注上。原图 resize 加填充后目标的绝对坐标变了但 xml 里还是原图坐标。解决要么在转格式前先把 xml 坐标按 letterbox 的 scale 和 padding 变换一遍要么干脆不做 letterbox直接 resize 到正方形——后者会变形但小样本下影响可控看你取舍。4.3 现象验证集 mAP 很高实际测试一塌糊涂原因是增强图混进了验证集。增强图和原图高度相似验证集里出现增强图等于变相泄露训练数据。解决划分数据集时只从原图里切验证集增强图全部归训练集并且验证集的 txt 只对应原图。4.4 现象训练到一半报ZeroDivisionError或框面积为 0原因是 xml 里有xmin xmax或ymin ymax的退化框归一化后宽高为 0。解决在转换脚本里加一行过滤if bw 0 or bh 0: continue把无效框丢掉别让它进训练。4.5 现象图片读进来是 Nonecv2.imread 返回空原因是文件名里的括号或空格在某些环境下被截断或者路径编码不对。解决用cv2.imdecode(np.fromfile(path, dtypenp.uint8), -1)替代cv2.imread能绕开中文路径和特殊字符的坑。这个习惯我后来在所有涉及中文或特殊字符路径的项目里都保留着。5. 从 828 张到可用模型锚框聚类与训练参数微调小样本训练 YOLOv3锚框anchor用默认的 COCO 聚类结果往往不合适因为苹果的宽高比和 COCO 里的通用目标差别大。我一般会用自己的标注跑一遍 k-means聚出 9 个锚框替换掉 cfg 里的默认值。脚本核心是拿所有归一化后的宽高做聚类import numpy as np from pathlib import Path from sklearn.cluster import KMeans wh [] for txt in Path(VOC2007/labels).glob(*.txt): for line in txt.read_text().splitlines(): _, _, _, bw, bh map(float, line.split()) wh.append([bw, bh]) wh np.array(wh) kmeans KMeans(n_clusters9, random_state0).fit(wh) anchors kmeans.cluster_centers_ anchors anchors[np.argsort(anchors[:, 0] * anchors[:, 1])] print(anchors , [round(a, 4) for pair in anchors for a in pair])聚出来的锚框按面积从小到大排填进 cfg 的anchors行。注意 YOLOv3 的锚框是相对 grid 的不是相对整图的所以这里用归一化宽高聚类后还要乘以特征图尺寸比如 13、26、52才是最终值。这一步容易搞混我踩过一次锚框大了十几倍训练直接不收敛。训练参数上小样本要把学习率调低常见做法是从 0.001 起步用 step 衰减batch 设 8 或 16看显存。迭代次数别照搬大集的几十万次828 张图跑 20004000 轮就够观察趋势了。数据量小的时候早停比固定轮数更靠谱验证 loss 连续 10 轮不降就停。验证模型有没有真的学到东西别只看 mAP。我习惯抽几张训练时没见过的苹果照片用训练好的权重跑一遍推理把框画出来看。如果框能稳定贴住苹果哪怕 mAP 只有 0.6这个模型在实际场景里也是能用的反过来 mAP 0.8 但框飘多半是验证集划分有问题。从那以后我每次做完小样本训练都强制走一遍「抽图 → 推理 → 画框 → 肉眼过」这个流程比盯指标踏实。希望帮到你。本文还有配套的精品资源点击获取
返回列表