☰
YOLOv8多任务实战:目标检测、语义分割与姿态估计全流程解析
2026/9/29 18:32:55 网站建设 项目流程

简介:本资源面向计算机视觉方向的学习者与开发者,聚焦YOLOv8在目标检测、语义分割与姿态估计三大任务上的完整实现,适合具备一定深度学习基础、希望快速上手或复现多任务模型的中高级用户。压缩包共670个文件,整体约1.94MB,以358个Markdown文档、159个Python脚本、81个YAML配置及少量YML、TXT、Rust、Jupyter Notebook、Shell、C++等文件构成,涵盖模型配置、训练推理代码、环境依赖与说明文档,结构清晰便于按任务模块检索。目前已有4880人学习下载,热度较高。读者可从中获取目标检测、分割与姿态估计的统一代码框架、参数配置模板、推理示例及多平台Dockerfile部署方案,便于对照实验、迁移到自有数据集或作为项目基线,快速搭建可运行的YOLOv8多任务实验环境。

1. 从一次产线误检说起:这套 YOLOv8 资源到底能干什么

上周帮朋友看一条小包装产线,相机拍到的药板里混进了一片颜色接近的铝箔碎屑,传统阈值分割直接把它当成了正常区域,漏检。换成 YOLOv8 的实例分割模型后,碎屑的轮廓被单独抠出来,误检率从 8% 掉到 0.6%。这件事让我重新翻出手里这套 yolov8-目标检测、语义分割、姿态估计的资源包——它不是三个独立脚本的拼盘,而是一套共享 backbone、共享数据加载逻辑、共享训练入口的多任务工程。目标检测负责框出「有没有、在哪」,语义分割负责回答「每个像素属于哪一类」,姿态估计则把人体或物体的关键点坐标拉出来。适合谁?做工业质检、安防行为分析、运动姿态评估的工程师,以及正在找毕业设计落点的学生。你不需要从零搭环境,但需要知道每个任务的数据格式和输出张量长什么样,否则调参就是玄学。

2. 环境搭建与模型选型:CPU 版 Ubuntu 20.04 怎么跑通第一帧

2.1 为什么先锁 CPU 版而不是直接上 GPU

很多教程一上来就让你装 CUDA,结果显卡驱动版本和 PyTorch 对不上,卡在torch.cuda.is_available()返回 False 那一步。我一般建议先在 CPU 上把推理链路跑通,确认模型文件、预处理、后处理都没问题,再切 GPU。Ubuntu 20.04 的 Python 3.8 是官方支持最稳的组合,YOLOv8 依赖的ultralytics包对 3.8 兼容性最好。CPU 版推理一张 640×640 的图大约 200~400ms,足够验证逻辑,训练才需要 GPU。

# 创建独立环境,避免和系统 Python 打架 conda create -n yolov8_cpu python=3.8 -y conda activate yolov8_cpu # 安装 PyTorch CPU 版,注意 index-url 指向 cpu 源 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装 ultralytics,它会自动拉取 opencv、numpy 等依赖 pip install ultralytics # 验证安装,打印版本和可用设备 python -c "import ultralytics; print(ultralytics.__version__); import torch; print(torch.cuda.is_available())"

逻辑说明:conda create隔离环境是血泪经验,系统 Python 里装过 ROS 或 OpenCV 的机器直接 pip 装 ultralytics 大概率冲突。--index-url指定 CPU 源,否则 pip 会默认去拉 CUDA 版 torch,下载 2GB 且装完发现用不了。最后一行输出False是正常的,说明当前是 CPU 模式。

参数说明:python=3.8不要随意升到 3.11,部分旧版 numpy 和 opencv 在 3.11 上编译轮子缺失。ultralytics不指定版本会装最新,如果复现旧项目,建议pip install ultralytics==8.0.x锁版本。

2.2 模型选型:n/s/m/l/x 到底选哪个

YOLOv8 官方提供五种尺度,参数量和推理速度差异很大。我整理了一张实际测过的表,测试环境是 i7-10700 CPU、640×640 输入:

模型参数量CPU 单帧耗时mAP50(COCO)适用场景
yolov8n3.2M180ms37.3边缘设备、实时性优先
yolov8s11.2M320ms44.9普通服务器 CPU 推理
yolov8m25.9M780ms50.2GPU 入门卡、精度均衡
yolov8l43.7M1.4s52.9离线批量处理
yolov8x68.2M2.3s53.9精度极限、不计速度

选型原则:如果目标是 RK3588 或 Hi3516CV610 这类板端部署,直接选 n 或 s,别犹豫。如果是 GTX1660Ti 跑训练,m 是性价比拐点。l 和 x 只在刷榜或离线分析时用。分割和姿态估计的模型命名多一个-seg和-pose后缀,比如yolov8n-seg.pt、yolov8n-pose.pt,下载时注意区分。

from ultralytics import YOLO # 加载预训练权重,首次运行会自动下载到当前目录 model = YOLO("yolov8n.pt") # 推理一张本地图片,save=True 会把结果画框后存到 runs/detect/ 下 results = model("test.jpg", save=True, conf=0.25, iou=0.45) # 打印检测到的类别和坐标 for box in results[0].boxes: print(box.cls, box.xyxy, box.conf)

逻辑说明:YOLO("yolov8n.pt")如果本地没有权重,会从官方地址拉取,国内网络可能慢,可以手动下载后把路径写进去。conf=0.25是置信度阈值,低于这个值的框被丢弃;iou=0.45是 NMS 的 IoU 阈值,重叠度高于它的框会被合并。这两个参数是误检和漏检的主要调节旋钮。

参数说明:save=True会生成可视化图片,调试时有用,批量推理时关掉省 IO。results[0].boxes里的xyxy是左上角和右下角坐标,格式是 tensor,转 numpy 用.cpu().numpy()。

3. 三大任务的数据格式与训练入口:别把分割标签丢给检测

3.1 目标检测:YOLO 格式的 txt 和 data.yaml

检测任务的数据集目录结构是固定的:

dataset/ ├── images/ │ ├── train/ (jpg/png) │ └── val/ ├── labels/ │ ├── train/ (txt) │ └── val/ └── data.yaml

每个 txt 文件对应一张图,每行格式:class_id x_center y_center width height,全部归一化到 0~1。常见翻车点是用 labelme 标完直接导出 COCO JSON,忘了转 YOLO txt。转换脚本我一般这么写:

import json import os # 把 labelme 的 JSON 转成 YOLO txt def labelme_to_yolo(json_path, output_dir, class_map): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_map: continue cls_id = class_map[label] points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] # 计算外接矩形并归一化 x_center = (min(xs) + max(xs)) / 2.0 / img_w y_center = (min(ys) + max(ys)) / 2.0 / img_h w = (max(xs) - min(xs)) / img_w h = (max(ys) - min(ys)) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") base = os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(output_dir, base + ".txt"), "w") as f: f.write("\n".join(lines))

逻辑说明:labelme 的矩形标注shape_type是rectangle,点只有两个对角点,但脚本里用 min/max 兼容多边形。class_map是类别名到 id 的映射,必须和data.yaml里的names顺序一致,否则训练时类别全乱。

参数说明:归一化用 6 位小数足够,YOLO 读取时按 float 解析。如果图片有旋转或 EXIF 方向,先用 PIL 的ImageOps.exif_transpose矫正,否则坐标对不上。

data.yaml内容:

path: /home/user/dataset train: images/train val: images/val nc: 3 names: ["scratch", "dent", "stain"]

训练命令:

yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16

3.2 语义分割:多边形标注和 mask 的取舍

语义分割和实例分割在 YOLOv8 里共用-seg模型,但数据格式不同。语义分割要求每个像素有类别,通常用 labelme 的多边形标注导出,或者用 mask PNG。YOLOv8 的-seg训练实际走的是实例分割路线,每个目标独立成 mask,但推理时可以通过后处理合并成语义图。

数据格式:labelme 多边形标注转 YOLO seg txt,每行class_id x1 y1 x2 y2 ...,坐标归一化。转换脚本和检测类似,只是把外接矩形换成所有点:

# 多边形点直接展开,不做外接矩形 points_norm = [] for x, y in points: points_norm.append(f"{x/img_w:.6f}") points_norm.append(f"{y/img_h:.6f}") lines.append(f"{cls_id} " + " ".join(points_norm))

逻辑说明:YOLOv8 seg 的 txt 每行点数不固定,但同一张图里所有多边形的点数最好一致,否则 dataloader 拼 batch 时会报错。常见做法是重采样到固定点数,或者用collate_fn自定义。

参数说明:imgsz对分割影响比检测大,640 是底线,小目标多建议 1024。batch要相应调小,否则显存爆。

训练命令:

yolo segment train data=seg_data.yaml model=yolov8n-seg.pt epochs=100 imgsz=640 batch=8

3.3 姿态估计:COCO 关键点格式和多人场景

姿态估计的数据格式是 COCO keypoints 的变体,每个实例有 17 个关键点(COCO 定义),每个点x y v,v=0 表示未标注,1 表示标注但不可见,2 表示可见。YOLOv8 pose 的 txt 格式:class_id x_center y_center w h px1 py1 v1 px2 py2 v2 ...。

多人姿态估计的坑在于:一张图里多个人,每个人的框和关键点要对应。用 labelme 标关键点很痛苦,常见做法是用 COCO 格式的 JSON 直接转,或者用专门的 keypoint 标注工具。转换时注意num_keypoints字段,少于 17 个点的实例在训练时会被忽略或补零。

# COCO JSON 转 YOLOv8 pose txt 的核心逻辑 for ann in coco["annotations"]: if ann["num_keypoints"] < 5: # 点太少直接跳过 continue bbox = ann["bbox"] # xywh kps = ann["keypoints"] # [x,y,v, x,y,v, ...] x_center = (bbox[0] + bbox[2]/2) / img_w y_center = (bbox[1] + bbox[3]/2) / img_h w = bbox[2] / img_w h = bbox[3] / img_h kp_str = " ".join([f"{kps[i]/img_w:.6f} {kps[i+1]/img_h:.6f} {int(kps[i+2])}" for i in range(0, len(kps), 3)]) lines.append(f"0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f} {kp_str}")

逻辑说明:num_keypoints < 5的实例通常是遮挡严重或标注不全,强行训练会拉低精度。kps里 v=0 的点坐标可能是 0,归一化后变成 0,训练时 loss 会忽略 v=0 的点,但坐标 0 会参与计算,所以最好把 v=0 的坐标也置 0。

参数说明:flip_idx在data.yaml里要配,否则水平翻转增强时左右关键点会错位。COCO 的 17 点 flip_idx 是[0,2,1,4,3,6,5,8,7,10,9,12,11,14,13,16,15]。

训练命令:

yolo pose train data=pose_data.yaml model=yolov8n-pose.pt epochs=100 imgsz=640 batch=16

4. 训练参数怎么调:从损失曲线看模型是不是在学

4.1 损失函数曲线图的读法

YOLOv8 训练完会在runs/detect/train/下生成results.csv,里面记录了每个 epoch 的 box_loss、cls_loss、dfl_loss。画图用 pandas + matplotlib:

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") df.columns = df.columns.str.strip() # 列名可能有空格 fig, axes = plt.subplots(1, 3, figsize=(15, 4)) axes[0].plot(df["epoch"], df["train/box_loss"], label="train") axes[0].plot(df["epoch"], df["val/box_loss"], label="val") axes[0].set_title("box_loss") axes[0].legend() axes[1].plot(df["epoch"], df["train/cls_loss"], label="train") axes[1].plot(df["epoch"], df["val/cls_loss"], label="val") axes[1].set_title("cls_loss") axes[1].legend() axes[2].plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP50") axes[2].plot(df["epoch"], df["metrics/mAP50-95(B)"], label="mAP50-95") axes[2].set_title("mAP") axes[2].legend() plt.savefig("loss_curve.png", dpi=150)

逻辑说明:train loss 持续下降但 val loss 在某个 epoch 后抬头,是过拟合的典型信号,需要加 dropout 或减 epochs。box_loss 震荡大通常是学习率太高或 batch 太小。mAP50 曲线平台期超过 20 个 epoch 不涨,说明模型容量到顶了,换大模型或加数据。

参数说明:results.csv的列名带空格,strip()是必须的。dpi=150保证论文里能看清。

4.2 学习率和 batch 的联动

YOLOv8 默认用 SGD,初始 lr 0.01,warmup 3 个 epoch。如果 batch 从 16 降到 8,lr 要相应降到 0.005 左右,否则梯度更新太猛,loss 直接飞。常见做法是线性缩放:lr = 0.01 * batch / 16。

# 自定义学习率和 batch yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=8 lr0=0.005 lrf=0.01 warmup_epochs=3

参数说明:lr0是初始学习率,lrf是最终学习率因子(最终 lr = lr0 * lrf),warmup_epochs是预热轮数。如果 loss 在前 5 个 epoch 就 NaN,先把 lr0 砍半。

4.3 数据增强的开关和边界

YOLOv8 默认开 mosaic、mixup、copy-paste。小数据集(<1000 张)建议全开,大数据集可以关掉 mixup 省时间。但 mosaic 对分割任务有时会破坏 mask 的连续性,如果分割边缘一直学不好,试试mosaic=0.5降低概率。

yolo segment train data=seg_data.yaml model=yolov8n-seg.pt epochs=100 mosaic=0.5 mixup=0.1 copy_paste=0.1

参数说明:mosaic=0.5表示 50% 的概率做马赛克增强,不是强度。copy_paste对实例分割有效,对语义分割意义不大。

5. 避坑与排查:那些让我重训三次的坑

5.1 现象:训练 loss 正常但 mAP 一直是 0

原因:data.yaml里的names顺序和 txt 里的 class_id 对不上,或者 val 路径写错导致验证集为空。YOLOv8 不会报错,只会静默跳过验证。

解决:训练前用yolo detect train ... val=False先跑一个 epoch,确认 dataloader 能读到图。然后手动检查labels/val/下有没有 txt,以及 txt 里的 class_id 是否在nc范围内。

5.2 现象:CPU 推理一张图要 5 秒以上

原因:模型加载时默认用了half=True或输入尺寸被自动放大到 1280。CPU 不支持 FP16,会回退到 FP32 但多一次转换开销。

解决:推理时显式指定imgsz=640和half=False:

results = model("test.jpg", imgsz=640, half=False, device="cpu")

5.3 现象:分割 mask 边缘锯齿严重

原因:imgsz太小,mask 上采样时插值方式默认是最近邻。或者训练时overlap_mask=False,实例 mask 被裁剪。

解决:推理时加retina_masks=True,训练时overlap_mask=True。如果还不行,把imgsz提到 1024。

5.4 现象:姿态估计关键点全挤在框中心

原因:关键点坐标没有归一化,或者归一化时用了错误的 img_w/img_h(比如用了 resize 后的尺寸而不是原图尺寸)。

解决:检查转换脚本里img_w和img_h是否来自原图 JSON 的imageWidth/imageHeight。YOLOv8 内部会自己 resize,你只需要提供原图归一化坐标。

5.5 现象:多卡训练报 NCCL 错误

原因:batch设置不是卡数的整数倍,或者device参数写成了0,1但实际只有一张卡。

解决:batch设为卡数的倍数,device=0,1确认nvidia-smi能看到两张卡。单卡调试时直接device=0。

6. 进阶:把三个任务的输出拼成一张业务图

实际项目里很少只跑一个任务。我最近做的一个健身动作评估,需要同时输出人体框、关键点、以及器械的像素级分割。做法是加载三个模型,共享同一张输入图,后处理时把结果画在同一张画布上。

from ultralytics import YOLO import cv2 import numpy as np det_model = YOLO("yolov8n.pt") seg_model = YOLO("yolov8n-seg.pt") pose_model = YOLO("yolov8n-pose.pt") img = cv2.imread("gym.jpg") canvas = img.copy() # 检测:画框 det_res = det_model(img, verbose=False)[0] for box in det_res.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0].tolist()) cv2.rectangle(canvas, (x1, y1), (x2, y2), (0, 255, 0), 2) # 分割:叠加半透明 mask seg_res = seg_model(img, verbose=False)[0] if seg_res.masks is not None: masks = seg_res.masks.data.cpu().numpy() for m in masks: m = cv2.resize(m, (img.shape[1], img.shape[0])) color = np.random.randint(0, 255, 3).tolist() canvas[m > 0.5] = canvas[m > 0.5] * 0.5 + np.array(color) * 0.5 # 姿态:画关键点和骨架 pose_res = pose_model(img, verbose=False)[0] if pose_res.keypoints is not None: kps = pose_res.keypoints.data.cpu().numpy() for person in kps: for i, (x, y, conf) in enumerate(person): if conf > 0.5: cv2.circle(canvas, (int(x), int(y)), 3, (0, 0, 255), -1) cv2.imwrite("fused_result.jpg", canvas)

逻辑说明:三个模型独立推理,互不干扰。分割 mask 的 resize 必须用原图尺寸,因为seg_res.masks.data是模型输入尺寸下的 mask。姿态关键点的conf是每个点的置信度,低于 0.5 的点不画,避免噪声。

参数说明:verbose=False关掉每帧的日志输出,批量处理时能省不少终端刷屏。np.random.randint给每个 mask 随机颜色,实际项目里应该按类别固定颜色。

验证方法:拿一张有两个人、一个器械的图,跑完看输出。如果分割 mask 盖住了人但没盖住器械,说明seg_model的类别里没有器械,需要在自己的数据上微调。如果关键点画在了框外,检查pose_res.keypoints的坐标系是否和原图一致——YOLOv8 返回的是原图坐标,不需要额外缩放。

从那以后我每次做多任务融合,都强制先单独跑一遍每个模型,确认各自的输出坐标系和尺寸,再写融合代码。希望帮到你。

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

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

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

立即咨询