YOLOv8目标检测实战:从药品包装盒数据集处理到模型训练调优
简介:目标检测是计算机视觉的核心任务之一,旨在识别图像中特定目标的位置与类别。其核心原理是通过深度学习模型,如YOLO系列,学习从像素到边界框与类别概率的映射。这项技术在工业自动化、智能安防、自动驾驶等领域具有极高的技术价值,是实现场景理解的关键。在具体的应用场景中,例如药品识别与分拣,一个规范且高质量的数据集是模型成功的基础。本文以包含板蓝根颗粒和999感冒灵的双类别药品包装盒数据集为例,详细解析了VOC与YOLO两种主流标注格式的互转原理,并提供了完整的数据预处理、YOLOv8模型训练、参数调优及结果评估的工程实践流程,为初学者构建完整的目标检测项目闭环提供了清晰的指引。
1. 项目背景与数据集价值解析
最近在整理一个挺有意思的小型数据集,是关于药品包装盒的目标检测,具体包含了板蓝根颗粒和999感冒灵这两个类别。这个数据集虽然不大,总共只有111张图像,但麻雀虽小五脏俱全,它同时提供了VOC和YOLO两种主流格式的标注文件。对于刚入门计算机视觉,特别是想动手实践目标检测的朋友来说,这是一个非常理想的“练手”材料。我自己在带新人或者做算法原型验证时,也常常会寻找这类主题明确、标注规范、体量适中的数据集来快速搭建实验环境。
你可能会问,市面上开源数据集那么多,为什么还要关注这个小小的药品数据集?这里面的价值其实挺多的。首先, 场景非常具体且贴近生活 。药品包装盒的检测在自动化药房、智能售货机、药品分拣流水线乃至家庭用药管理APP中都有实际的应用前景。其次, 双类别的设定恰到好处 。它既避免了单类别过于简单、学不到东西的问题,又不像COCO那样动辄80个类别,让初学者在数据准备和模型调试上耗费过多精力。最后, 双格式标注是最大的亮点 。VOC格式(XML文件)结构清晰,包含了目标的边界框和类别信息,是很多传统算法和评估工具的标准输入。而YOLO格式(.txt文件)则直接、紧凑,是当前YOLO系列、Ultralytics生态等主流检测框架训练时所需的格式。拥有同一批数据的两种标注,意味着你可以无缝地在不同框架、不同评估流程之间切换和对比,这对于深入理解目标检测的数据处理全链路至关重要。
从技术学习的角度看,这个数据集能帮你走通一个完整的目标检测项目闭环:从数据理解、格式解析、数据集划分(训练集/验证集/测试集),到模型选择(比如用YOLOv5/v8还是SSD)、训练配置、性能评估,再到最后可能的数据增强策略尝试。整个过程涉及到的工具和概念,比如labelImg标注工具、PASCAL VOC评估指标(mAP)、YOLO的锚框(anchor)机制、以及PyTorch或TensorFlow的DataLoader构建,你都能通过这个数据集得到实践。接下来,我就以这个“板蓝根颗粒与999感冒灵”数据集为例,拆解一下处理这类目标检测数据的完整流程和核心要点。
2. 数据集深度剖析与预处理实战
拿到一个数据集,第一步绝不是急着扔进模型里训练。花时间把数据“摸透”,是后续所有工作能顺利进行的基础。对于这个111张图像的双类别药品数据集,我们需要从图像内容、标注质量、格式解析和数据集划分几个层面入手。
2.1 图像内容与标注质量检查
首先,我们需要直观地了解数据长什么样。我通常会用一个简单的Python脚本,配合OpenCV和matplotlib,随机抽样查看一些图像及其标注框。
import os
import cv2
import random
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from xml.etree import ElementTree as ET # 用于解析VOC格式
def plot_image_with_boxes(img_path, annotation_path, format='voc'):
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # OpenCV读取为BGR,转为RGB显示
fig, ax = plt.subplots(1, figsize=(12, 8))
ax.imshow(img)
if format.lower() == 'voc':
# 解析VOC XML
tree = ET.parse(annotation_path)
root = tree.getroot()
for obj in root.findall('object'):
cls_name = obj.find('name').text
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)
# 绘制矩形框
rect = patches.Rectangle((xmin, ymin), xmax-xmin, ymax-ymin,
linewidth=2, edgecolor='r', facecolor='none')
ax.add_patch(rect)
# 添加类别标签
ax.text(xmin, ymin-5, cls_name, color='red', fontsize=12, weight='bold')
elif format.lower() == 'yolo':
# 解析YOLO .txt, 注意YOLO格式是归一化的中心点坐标和宽高
img_h, img_w = img.shape[:2]
with open(annotation_path, 'r') as f:
for line in f.readlines():
cls_id, x_center_norm, y_center_norm, w_norm, h_norm = map(float, line.strip().split())
# 转换回像素坐标
x_center = x_center_norm * img_w
y_center = y_center_norm * img_h
w = w_norm * img_w
h = h_norm * img_h
xmin = int(x_center - w/2)
ymin = int(y_center - h/2)
xmax = int(x_center + w/2)
ymax = int(y_center + h/2)
rect = patches.Rectangle((xmin, ymin), w, h,
linewidth=2, edgecolor='g', facecolor='none')
ax.add_patch(rect)
ax.text(xmin, ymin-5, f'class_{int(cls_id)}', color='green', fontsize=12, weight='bold')
ax.axis('off')
plt.show()
# 假设数据按如下结构组织
# dataset/
# ├── images/ (存放所有.jpg图像)
# ├── annotations_voc/ (存放对应的.xml文件)
# └── annotations_yolo/ (存放对应的.txt文件)
image_dir = './dataset/images'
voc_anno_dir = './dataset/annotations_voc'
image_files = [f for f in os.listdir(image_dir) if f.endswith('.jpg')]
sample_img = random.choice(image_files)
img_path = os.path.join(image_dir, sample_img)
voc_anno_path = os.path.join(voc_anno_dir, sample_img.replace('.jpg', '.xml'))
plot_image_with_boxes(img_path, voc_anno_path, format='voc')
运行这个脚本,我们能立刻看到图像中药品包装盒的位置、大小以及标注的精细程度。对于这个数据集,你需要特别关注几点:
- 目标尺寸 :药品包装盒在图像中是占比较大还是较小?这关系到后续是否要针对小目标检测进行优化。
- 拍摄角度与光照 :图像是正面平拍,还是带有一定角度?光照是否均匀?这会影响模型的泛化能力。
- 标注框的准确性 :边界框是否紧密贴合药品包装盒?是否存在漏标或错标?通过抽样可视化,能快速发现明显的标注问题。
- 类别平衡 :板蓝根颗粒和999感冒灵的样本数量是否大致相当?严重的数据不平衡会影响模型对少数类的识别能力。
注意 :在检查YOLO格式标注时,务必确认其类别ID的映射关系。通常,
0代表第一个类别(如banlangen),1代表第二个类别(如999ganmaoling)。这个映射关系通常记录在一个名为classes.txt或data.yaml的文件中,如果没有,你需要根据标注文件或VOC文件反推并创建它,这是训练前的关键一步。
2.2 VOC与YOLO格式互转原理与实操
这个数据集提供了两种格式,这很棒,但我们经常遇到的情况是只有一种格式。因此,掌握两种格式的互转是必备技能。理解它们的核心差异是关键:
- VOC格式 (XML) :使用图像的绝对像素坐标来表示边界框
(xmin, ymin, xmax, ymax)。信息丰富,可读性强。 - YOLO格式 (TXT) :使用归一化的相对坐标
(x_center, y_center, width, height),每个值都在0到1之间。格式紧凑,直接用于训练。
转换公式如下(假设图像宽度为 img_w ,高度为 img_h ):
- VOC -> 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
-
- YOLO -> VOC :
-
xmin = (x_center - width/2) * img_w -
ymin = (y_center - height/2) * img_h -
xmax = (x_center + width/2) * img_w -
ymax = (y_center + height/2) * img_h
-
这里提供一个将VOC格式批量转换为YOLO格式的实用脚本:
import os
import xml.etree.ElementTree as ET
def convert_voc_to_yolo(voc_anno_dir, output_dir, class_list):
"""
将VOC格式标注批量转换为YOLO格式。
Args:
voc_anno_dir: VOC格式XML文件所在目录。
output_dir: 输出YOLO格式TXT文件的目录。
class_list: 类别名称列表,如 ['banlangen', '999ganmaoling']。
"""
os.makedirs(output_dir, exist_ok=True)
# 创建类别名到ID的映射字典
class_to_id = {name: idx for idx, name in enumerate(class_list)}
for xml_file in os.listdir(voc_anno_dir):
if not xml_file.endswith('.xml'):
continue
tree = ET.parse(os.path.join(voc_anno_dir, xml_file))
root = tree.getroot()
# 获取图像尺寸
size_elem = root.find('size')
img_w = int(size_elem.find('width').text)
img_h = int(size_elem.find('height').text)
yolo_lines = []
for obj in root.findall('object'):
cls_name = obj.find('name').text
if cls_name not in class_to_id:
print(f"警告: {xml_file} 中发现未知类别 '{cls_name}',已跳过。")
continue
cls_id = class_to_id[cls_name]
bbox = obj.find('bndbox')
xmin = float(bbox.find('xmin').text)
ymin = float(bbox.find('ymin').text)
xmax = float(bbox.find('xmax').text)
ymax = float(bbox.find('ymax').text)
# 转换坐标
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
# 确保坐标在[0,1]范围内
x_center = max(0, min(1, x_center))
y_center = max(0, min(1, y_center))
width = max(0, min(1, width))
height = max(0, min(1, height))
yolo_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}")
# 写入YOLO格式文件
output_txt_path = os.path.join(output_dir, xml_file.replace('.xml', '.txt'))
with open(output_txt_path, 'w') as f:
f.write('\n'.join(yolo_lines))
# 使用示例
class_list = ['banlangen', '999ganmaoling'] # 必须与XML中的类别名严格一致
convert_voc_to_yolo('./dataset/annotations_voc', './dataset/annotations_yolo_converted', class_list)
实操心得 :转换后一定要用2.1节的可视化脚本,随机抽查几张转换后的YOLO标注,与原始的VOC标注对比,确保转换过程没有出错。坐标归一化时可能因为四舍五入产生微小误差,但只要在合理范围内(如1e-6)即可接受。
2.3 数据集划分与YAML配置文件创建
111张图像不算多,因此划分比例需要谨慎。常见的做法是按 训练集:验证集:测试集 = 7:2:1 或 8:1:1 的比例进行随机划分。测试集用于最终评估模型泛化能力,验证集用于训练过程中的超参数调整和早停。
划分完成后,我们需要创建一个YAML配置文件(例如 data.yaml ),这是YOLOv5/v8等框架要求的数据集描述文件。
import os
import random
import shutil
from sklearn.model_selection import train_test_split
def split_dataset(image_dir, train_ratio=0.7, val_ratio=0.2, test_ratio=0.1):
"""
划分数据集,并创建对应的文件夹结构。
"""
assert abs(train_ratio + val_ratio + test_ratio - 1.0) < 1e-9, "比例之和必须为1"
all_images = [f for f in os.listdir(image_dir) if f.endswith(('.jpg', '.png', '.jpeg'))]
random.shuffle(all_images) # 随机打乱
total = len(all_images)
train_end = int(total * train_ratio)
val_end = train_end + int(total * val_ratio)
train_images = all_images[:train_end]
val_images = all_images[train_end:val_end]
test_images = all_images[val_end:]
# 创建目录结构
splits = {'train': train_images, 'val': val_images, 'test': test_images}
base_path = './dataset_split'
for split_name, img_list in splits.items():
split_img_dir = os.path.join(base_path, 'images', split_name)
split_label_dir = os.path.join(base_path, 'labels', split_name) # 假设标签文件在labels目录,与images平行
os.makedirs(split_img_dir, exist_ok=True)
os.makedirs(split_label_dir, exist_ok=True)
for img_file in img_list:
# 拷贝图像
src_img = os.path.join(image_dir, img_file)
dst_img = os.path.join(split_img_dir, img_file)
shutil.copy(src_img, dst_img)
# 拷贝对应的标签文件 (假设标签文件与图像同名,扩展名为.txt)
label_file = img_file.rsplit('.', 1)[0] + '.txt'
src_label = os.path.join('./dataset/annotations_yolo', label_file) # 使用YOLO格式标签
dst_label = os.path.join(split_label_dir, label_file)
if os.path.exists(src_label):
shutil.copy(src_label, dst_label)
else:
print(f"警告: 未找到标签文件 {src_label}")
print(f"划分完成: 训练集{len(train_images)}张, 验证集{len(val_images)}张, 测试集{len(test_images)}张")
return base_path, train_images, val_images, test_images
# 执行划分
base_path, _, _, _ = split_dataset('./dataset/images', train_ratio=0.7, val_ratio=0.2, test_ratio=0.1)
划分好后,在 dataset_split 目录下创建 data.yaml 文件:
# data.yaml
path: ./dataset_split # 数据集根目录
train: images/train # 训练集图像路径,相对于path
val: images/val # 验证集图像路径,相对于path
test: images/test # 测试集图像路径(可选)
# 类别信息
names:
0: banlangen # 类别ID 0 对应板蓝根颗粒
1: 999ganmaoling # 类别ID 1 对应999感冒灵
# 可选:类别数量
nc: 2
这个YAML文件是连接你的数据和YOLO训练脚本的桥梁,路径一定要写对。 path 可以是绝对路径,也可以是相对于训练脚本所在位置的相对路径。
3. 基于YOLOv8的模型训练与调优全流程
数据准备好了,接下来就是选择模型和训练。这里我选择YOLOv8,因为它生态成熟,文档清晰,从训练到部署的链路非常完整。我们使用Ultralytics提供的Python API来进行,这比单纯使用命令行有更大的灵活性。
3.1 环境搭建与模型选择
首先,安装Ultralytics包:
pip install ultralytics
YOLOv8提供了不同尺度的模型,从轻量级的YOLOv8n到高精度的YOLOv8x。对于我们的111张图像的小数据集,选择过大的模型极易导致过拟合。我的建议是:
- 首选 YOLOv8n (nano) 或 YOLOv8s (small) 。它们参数量少,训练速度快,在小数据集上更容易收敛,作为原型验证和入门学习性价比最高。
- 如果担心精度,可以从YOLOv8s开始。如果训练后发现欠拟合(训练集和验证集损失都下不去),再考虑换稍大的模型,但务必配合更强的数据增强和正则化手段。
3.2 训练脚本与核心参数详解
下面是一个基础的训练脚本,我加入了大量注释来解释每个关键参数的作用:
from ultralytics import YOLO
import os
def train_yolov8():
# 加载一个预训练模型。强烈建议使用预训练权重,这能极大加速收敛并提升最终性能。
# 即使预训练模型是在COCO等通用数据集上训练的,其提取通用特征的能力也远胜于随机初始化。
model = YOLO('yolov8s.pt') # 这里使用YOLOv8s,你可以换成'yolov8n.pt'
# 开始训练
results = model.train(
data='./dataset_split/data.yaml', # 上一步创建的配置文件路径
epochs=100, # 训练轮数。小数据集可以适当增加,如150-200。
imgsz=640, # 输入图像尺寸。YOLOv8默认640,也可尝试512。
batch=16, # 批次大小。根据你的GPU内存调整。11G显存可设16或32。
workers=4, # 数据加载线程数。CPU核心数多可以调高,加快数据读取。
device='0', # 使用GPU 0。如果是CPU则设为'cpu',多卡可用'0,1'。
name='drug_detection_v8s', # 本次训练的实验名称,用于保存结果。
pretrained=True, # 使用预训练权重(已通过加载模型指定)。
optimizer='SGD', # 优化器。SGD是经典选择,'AdamW'也可能有更好效果。
lr0=0.01, # 初始学习率。这是最重要的超参数之一。
lrf=0.01, # 最终学习率因子 = lr0 * lrf。用于余弦退火等调度。
momentum=0.937, # SGD动量参数。
weight_decay=0.0005, # 权重衰减,防止过拟合。
warmup_epochs=3.0, # 学习率热身轮数,开始时从小学习率逐渐增大,稳定训练。
box=7.5, # 边界框损失权重。
cls=0.5, # 分类损失权重。对于类别少的数据集,可以调低。
dfl=1.5, # Distribution Focal Loss权重(v8新增)。
save=True, # 保存训练过程中的最佳模型和最后模型。
save_period=-1, # 每N轮保存一次检查点。-1表示仅按标准规则保存。
cache=False, # 是否缓存数据集到内存或磁盘以加速训练。小数据集可以开启。
resume=False, # 是否从上次保存的检查点恢复训练。
amp=True, # 自动混合精度训练,大幅减少显存占用并加速训练。
fraction=1.0, # 使用数据集的比例,可用于快速实验。
profile=False, # 是否在训练时进行性能分析(速度/内存)。
seed=42, # 随机种子,确保实验可复现。
deterministic=True, # 保证可复现性,可能会牺牲一些训练速度。
single_cls=False, # 是否将所有类别视为单一类别。我们有两个类别,设为False。
# 数据增强参数 (非常重要!)
hsv_h=0.015, # 色调(H)增强幅度。
hsv_s=0.7, # 饱和度(S)增强幅度。
hsv_v=0.4, # 明度(V)增强幅度。
degrees=0.0, # 旋转角度范围。小数据集可设小值,如5.0。
translate=0.1, # 平移幅度。
scale=0.5, # 缩放幅度。
shear=0.0, # 剪切幅度。
perspective=0.0, # 透视变换幅度。
flipud=0.0, # 上下翻转概率。
fliplr=0.5, # 左右翻转概率。对于水平对称目标有效。
mosaic=1.0, # Mosaic数据增强的概率。小数据集建议保持1.0。
mixup=0.0, # MixUp数据增强的概率。小数据集可谨慎使用或设为0。
copy_paste=0.0, # 复制粘贴增强的概率。需要额外处理,通常为0。
)
if __name__ == '__main__':
train_yolov8()
核心参数调优经验 :
- 学习率 (
lr0) :这是超参数中的“王炸”。对于小数据集,学习率不宜过大,否则容易震荡甚至发散。可以从默认的0.01开始,如果训练初期损失就变成NaN(爆炸),果断调小到0.001甚至0.0001试试。也可以使用--lr0 0.01 --lrf 0.01配合余弦退火,让学习率平滑下降。 - 数据增强 :这是 防止小数据集过拟合的生命线 。
mosaic=1.0是YOLO系列的王牌增强,必须开启。fliplr=0.5(水平翻转)对药品包装盒这种通常水平对称的目标很有用。hsv增强模拟光照和颜色变化。对于小数据集,可以适度增强degrees(旋转)和perspective(透视),模拟拍摄角度变化,但幅度不宜过大,避免产生不真实的图像。mixup和copy_paste等强增强在小数据集上可能带来负面效果,建议先设为0。 - 损失权重 (
box,cls,dfl) :通常保持默认即可。如果你的任务中定位精度远比分类重要(比如需要非常精确的框),可以稍微提高box的权重。 - 图像尺寸 (
imgsz) :更大的尺寸通常能带来更好的精度,但会显著增加显存消耗和训练时间。对于药品包装盒这种目标相对较大的场景,640甚至512可能就足够了。可以先从640开始。
3.3 训练过程监控与评估解读
训练开始后,Ultralytics会在 runs/detect/drug_detection_v8s (你指定的 name )目录下生成大量有用的文件:
- weights/ :存放最佳模型
best.pt和最后模型last.pt。 - events.out.tfevents... :TensorBoard日志文件。使用
tensorboard --logdir runs/detect可以在浏览器中实时查看损失曲线、学习率曲线、验证集mAP等指标,这是监控训练状态最直观的方式。 - args.yaml :保存了本次训练的所有参数,便于复现。
- results.csv 和 results.png :训练结果的表格和图表汇总。
你需要重点关注以下指标:
- 损失曲线 (
train/box_loss,train/cls_loss,val/box_loss,val/cls_loss) :理想的曲线是训练损失和验证损失都平稳下降,并最终趋于平缓。如果 训练损失持续下降,但验证损失在某个点后开始上升 ,这是典型的过拟合信号。此时需要加强数据增强、增加正则化(如weight_decay)、或减少模型复杂度(换更小的模型)。 - mAP (mean Average Precision) :这是目标检测的核心评估指标。
metrics/mAP50(B)表示IoU阈值为0.5时的mAP,metrics/mAP50-95(B)表示IoU阈值从0.5到0.95(步长0.05)的平均mAP。后者更严格,更能综合反映模型性能。我们的目标是看到这些曲线随着训练轮数上升。 - 学习率曲线 :确认学习率按照你设定的调度策略(如余弦退火)正常变化。
踩坑记录 :有一次我在训练一个类似的小数据集时,验证集mAP始终在0.5左右徘徊上不去。检查TensorBoard发现,验证集分类损失 (
val/cls_loss) 一直很高。后来发现,是数据集中有一个类别的少量样本标注有误(类别标错了)。清洗修正数据后,模型性能立刻得到了提升。所以, 当模型性能不符合预期时,第一反应应该是回头检查数据质量 ,而不是盲目调整超参。
4. 模型验证、推理与结果分析
训练完成后,我们得到了一个 best.pt 模型。接下来需要在独立的测试集上评估其泛化能力,并学习如何使用模型进行预测。
4.1 在测试集上进行模型验证
使用YOLOv8的 val 模式,可以方便地计算模型在测试集上的各项指标。
from ultralytics import YOLO
def evaluate_model():
# 加载训练好的最佳模型
model = YOLO('./runs/detect/drug_detection_v8s/weights/best.pt')
# 在测试集上验证
metrics = model.val(
data='./dataset_split/data.yaml',
split='test', # 指定使用测试集。如果data.yaml中未定义test,则用val。
imgsz=640,
batch=16,
save_json=True, # 保存评估结果的JSON文件,可用于进一步分析
save_hybrid=True, # 保存混合标签图,便于可视化
conf=0.001, # 评估时使用的置信度阈值,越低召回率可能越高
iou=0.6, # 评估时使用的NMS IoU阈值
device='0',
name='val_on_test' # 验证结果保存的目录名
)
# 打印关键指标
print(f"mAP50-95: {metrics.box.map:.4f}")
print(f"mAP50: {metrics.box.map50:.4f}")
print(f"Precision: {metrics.box.p:.4f}")
print(f"Recall: {metrics.box.r:.4f}")
# 打印每个类别的AP
for i, class_name in enumerate(metrics.names.values()):
print(f"Class {class_name} AP50-95: {metrics.box.ap[i]:.4f}")
if __name__ == '__main__':
evaluate_model()
运行后,除了在终端输出指标,还会在 runs/detect/val_on_test 目录下生成:
- confusion_matrix.png :混淆矩阵,直观显示模型在每个类别上的分类错误情况。
- F1_curve.png :F1分数随置信度阈值变化的曲线,帮你选择最优的置信度阈值。
- P_curve.png 和 R_curve.png :精确率和召回率曲线。
- PR_curve.png :精确率-召回率曲线,曲线下面积就是AP。
- results.png :指标汇总图。
- labels.jpg 和 labels_correlogram.jpg :标签分布和相关性图。
如何解读结果? 假设我们得到 mAP50-95: 0.85 , mAP50: 0.92 。这表示在IoU阈值为0.5时,模型平均精度达到92%,这是一个非常不错的结果,说明模型能较好地检测出目标。如果 mAP50-95 显著低于 mAP50 ,说明模型对边界框的定位精度还有提升空间,可能需要调整 box 损失权重或使用更精细的锚框。
4.2 使用训练好的模型进行单张图像推理
验证完模型,就可以用它来预测新图片了。
from ultralytics import YOLO
import cv2
def predict_single_image():
model = YOLO('./runs/detect/drug_detection_v8s/weights/best.pt')
# 预测单张图像
results = model('./test_image.jpg', imgsz=640, conf=0.25, iou=0.45, save=True)
# 结果是一个列表,因为可能有多张图,我们取第一张
result = results[0]
# 可视化并保存
annotated_img = result.plot() # 生成带标注框的图像 (BGR格式)
cv2.imwrite('./predicted_result.jpg', annotated_img)
# 打印检测到的目标信息
for box in result.boxes:
cls_id = int(box.cls)
conf = float(box.conf)
xyxy = box.xyxy[0].tolist() # 获取边界框坐标 [x1, y1, x2, y2]
print(f"检测到: {result.names[cls_id]}, 置信度: {conf:.2f}, 位置: {xyxy}")
if __name__ == '__main__':
predict_single_image()
关键推理参数 :
-
conf: 置信度阈值。高于此值的检测框才会被保留。根据你的F1曲线和实际需求调整。要求高召回率(宁可错杀不可放过)就调低,要求高精确率(确保每个框都是对的)就调高。 -
iou: 非极大值抑制 (NMS) 的IoU阈值。用于合并重叠的检测框。值越小,合并越激进,留下的框越少。
4.3 结果分析与常见问题排查
根据测试集评估和单张推理的结果,你可能会遇到以下几种情况,下面提供排查思路:
-
精度低 (mAP < 0.5) :
- 数据问题 :回头用第2.1节的方法,仔细检查测试集图像的标注质量。是否存在大量漏标、错标?训练集和测试集的分布(光照、背景、角度)差异是否过大?
- 模型容量不足 :尝试使用更大的模型,如从YOLOv8n切换到YOLOv8s或YOLOv8m。
- 训练不充分 :增加训练轮数
epochs,并配合学习率热身 (warmup_epochs) 和余弦退火。 - 数据增强不足 :小数据集更容易过拟合,适当增强
degrees,perspective,mixup(小心使用)。
-
过拟合 (训练集精度远高于验证/测试集) :
- 加强正则化 :增加
weight_decay(如从0.0005到0.001)。 - 使用更强的数据增强 :这是对抗过拟合最有效的手段。确保
mosaic=1.0,尝试开启mixup(从0.1开始),增加hsv增强幅度。 - 早停 (Early Stopping) :监控验证集损失,当其在连续多个epoch不再下降时停止训练。Ultralytics内部有简单的早停逻辑,也可以自己写回调实现。
- 减少模型复杂度 :换用更小的模型(如YOLOv8n)。
- 加强正则化 :增加
-
欠拟合 (训练集和验证集精度都低) :
- 模型容量可能不足 :换用稍大的模型。
- 学习率可能太小 :适当增大
lr0。 - 数据增强可能太强 :过强的增强(如大角度的旋转、扭曲)可能会让模型难以学习有效特征,尤其是对于小数据集。尝试减弱增强参数。
- 检查数据路径和标注 :确认
data.yaml中的路径是否正确,训练时是否真的加载了数据和标签。
-
某个类别检测效果特别差 :
- 类别不平衡 :检查该类别的样本数量是否远少于其他类别。如果是,可以考虑对该类别的图像进行过采样,或在计算损失时给该类别的损失增加权重(YOLOv8中可以通过
cls参数间接影响,但更精细的类别权重需要修改源码)。 - 特征难以区分 :板蓝根和999感冒灵包装盒是否在颜色、形状上非常相似?可以尝试在数据增强中更多使用颜色扰动 (
hsv),或者考虑收集更多该类别有区分度的样本。
- 类别不平衡 :检查该类别的样本数量是否远少于其他类别。如果是,可以考虑对该类别的图像进行过采样,或在计算损失时给该类别的损失增加权重(YOLOv8中可以通过
处理这个小数据集的过程,本质上是一个完整的目标检测微调(Fine-tuning)流程的缩影。从数据准备、格式处理、模型选择、训练调优到最后的评估推理,每一步都充满了细节和选择。通过这个实战,你不仅能学会操作YOLOv8这个工具,更能理解数据驱动的AI项目背后“数据-模型-调优”的闭环逻辑。在实际工作中,遇到成千上万张图像的大数据集时,这套方法论同样适用,只是各个环节的复杂度和自动化要求会更高。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)