拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于YOLOv8的武术动作识别系统开发实战:从环境搭建到部署

基于YOLOv8的武术动作识别系统开发实战:从环境搭建到部署

简介:基于YOLOv8的武术动作识别系统是一套完整可运行的毕业设计项目,面向计算机相关专业的在校学生、教师及企业开发者,适用于毕设答辩、课程设计、大作业或项目初期演示,也适合对目标检测与动作识别感兴趣的学习者进阶实践。压缩包共包含97个文件,以Python源码为主,覆盖约70个py脚本,并配有预训练pt权重、xml配置文件、pyc辅助文件以及mp4演示视频,整体仅24.21MB,结构清晰,便于快速部署和二次开发。系统集成了可视化界面、训练与推理模块,运行后能自动产出混淆矩阵、F1分数曲线、精确率-召回率曲线、标签分布图及验证集预测结果等评估内容,有助于从数据角度审视模型效果。附带的README与部署说明提供了完整的环境配置和启动流程,即使基础一般也能按步骤跑通项目。该资源已有73人学习下载,所有代码均经过测试,作为答辩展示或学习参考皆可放心使用。

1. 从毕设答辩现场说起:为什么武术动作识别值得用 YOLOv8 做

如果你正在为毕业设计或课程设计找一个「看起来高级、能演示、还能写进简历」的选题,那「基于 YOLOv8 的武术动作识别系统」确实是个不容易出错的方向。我见过不少学生拿这个标题交差,有的拿模板改一改界面就完事,有的连模型都没训练过、直接用预训练权重跑个视频就当成果。真正能把「数据集 → 训练 → 界面 → 部署」完整打通的人,反而没几个。

这套系统的本质其实不复杂:用 YOLOv8 检测视频里的人物目标,再把检测框内的人体区域交给一个分类模块识别具体武术动作。难点不在模型选型,而在数据准备、界面线程设计和部署环境的兼容性。本文会从环境搭建开始,走完数据标注、训练调参、界面封装和打包发布的全流程,并把最容易翻车的几个坑点写清楚。适合正在做毕设的学生,也适合想快速落地一个动作识别 Demo 的工程师。

2. 先把环境立住:Ubuntu 20.04 跑通 YOLOv8(CPU 版也一样能训)

2.1 为什么选 YOLOv8 而不是 YOLOv5 或更老的版本

武术动作识别的常见做法是把任务拆成「检测人体 + 分类动作」。YOLOv8 本身就能同时输出检测框和目标类别,所以我们只需要把每个武术动作定义成一个类别,比如「冲拳」「蹬腿」「马步架打」,YOLOv8 会直接输出动作类别和置信度,省掉单独做分类模型的步骤。

相比 YOLOv5,v8 的主要改进是把 C3 模块换成了 C2f,neck 部分去掉了上采样后的冗余卷积层,推理速度更快,精度也没有牺牲。还有一个关键点是 Ultralytics 官方仓库对新手极其友好,训练、验证、导出 ONNX 都是一行命令的事,这对毕设周期来说很宝贵。另外 RK3588 这类边缘板子对 v8 的适配也做得比较成熟,后续想部署到嵌入式设备也有路可走。

2.2 Ubuntu 20.04 上安装 CPU 版 YOLOv8 的最小命令集

很多同学的电脑没有独立显卡,或者只有 N 卡但驱动没配好。这种情况完全可以用 CPU 版先跑通流程,只是训练速度慢一些。这里给出我实测过的最小安装命令序列,在 Ubuntu 20.04 全新系统上可以直接执行。

# 1. 安装 Python 3.10 和虚拟环境工具 sudo apt update sudo apt install -y python3.10 python3.10-venv python3-pip # 2. 创建独立虚拟环境,避免污染系统 Python python3.10 -m venv yolov8_env source yolov8_env/bin/activate # 3. 安装 PyTorch CPU 版(必须是 CPU 索引源,否则会装成 CUDA 版) pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 4. 安装 Ultralytics 及其依赖 pip install ultralytics # 5. 验证安装是否成功 python -c "from ultralytics import YOLO; print('YOLOv8 ready')"

这段命令的关键点有几个。第一,虚拟环境一定要建,因为毕设机器上很可能已经有 TensorFlow 或者其他深度学习库,版本冲突是家常便饭。第二,PyTorch 必须从--index-url指定 CPU 源安装,直接从 PyPI 装会拉取 CUDA 版,虽然也能跑,但会额外占用几个 GB 的磁盘空间,还会在导入时报 CUDA 不可用的警告。第三,Ultralytics 装完后会自动拉取 YOLOv8 的预训练权重,需要联网,如果你在离线环境操作,得提前下载好yolov8n.pt文件。

提示:如果你的机器有 NVIDIA 显卡,直接装 CUDA 版 PyTorch 即可,训练速度能快 20 倍以上。CPU 版只是用来跑通流程和做小规模验证的权宜之计。

2.3 验证环境时必看的三个输出信号

装完环境后不要急着训练,先跑一个最小推理验证,确认整条链路是通的。常见做法是用官方预训练权重检测一张包含人物的图片,看看能不能画出边界框。这里我一般会先下载一张包含单个人物的测试图,然后执行检测脚本。

from ultralytics import YOLO # 加载官方预训练模型,首次运行会自动下载权重 model = YOLO("yolov8n.pt") # 对测试图片执行推理 results = model.predict(source="test.jpg", save=True, conf=0.25) # 打印检测结果 for r in results: for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) print(f"检测到目标,类别ID: {cls_id},置信度: {conf:.2f}")

这段代码里save=True会把标注后的图片保存到runs/detect/predict目录下。你去看这张保存的图片,如果人物被正确框出来,说明模型推理链路正常。如果没有框出来,先检查test.jpg路径是否正确,再看conf=0.25这个阈值是否过高,有些远距离小目标置信度确实会低于 0.25,改成 0.1 试一下。第三个关键信号是看终端打印的推理耗时,CPU 版推理一张 640x640 的图片大约需要 0.3 到 1 秒,如果超过 3 秒,可能是 PyTorch 没用到 AVX 指令集,需要重新编译或者换用官方 wheel 包。

3. 建立武术动作数据集:从 Labelme 标注到 YOLOv8 训练格式

3.1 数据集目录结构设计与标注要点

武术动作识别的核心困难不在模型,而在数据。你得先明确识别哪些动作,然后为每个动作准备足够的视频和图片。一个动作至少需要 200 到 300 张训练图片,质量比数量更重要——有些学生从网上抓了一大堆视频截图,结果动作混乱、背景嘈杂,训练出来的模型精度极低,还以为是模型的问题。

我一般会建议按照下面的目录结构组织数据。这个结构是 YOLOv8 官方约定的标准格式,datasets目录下先按images和labels分训练集与验证集,标注文件用同名 txt 文件存储。

datasets/ ├── wushu/ │ ├── images/ │ │ ├── train/ │ │ │ ├── punch_001.jpg │ │ │ └── kick_001.jpg │ │ └── val/ │ │ ├── punch_010.jpg │ │ └── kick_010.jpg │ ├── labels/ │ │ ├── train/ │ │ │ ├── punch_001.txt │ │ │ └── kick_001.txt │ │ └── val/ │ │ └── ... │ └── wushu.yaml

标注时有一个容易忽略的坑:YOLO 格式的标注框是「归一化到 0~1 的相对坐标」,不是像素坐标。比如一张 1920x1080 的图片里,一个人体框左上角在 (960, 540)、宽高为 600x900,那标注的 txt 内容应该是:类别ID、边界框中心点的相对x、相对y、相对宽、相对高。具体转换脚本放在后面小节里细说。

注意:武术动作标注的关键不是「人」,而是「动作」。同一套拳法里,出拳那一帧和收拳那一帧,人体框的位置和比例差异很大,但类别标签可能都是「冲拳」。标注时要把动作的爆发点帧作为关键帧,千万别把动作的预备姿势和完成姿势混为一谈,否则模型永远学不会区分动作边界。

3.2 Labelme 标注实操:多边形框与矩形框的选择

工具方面,Labelme 和 LabelImg 都有人用。LabelImg 更适合画矩形框,操作简单,标注速度快;Labelme 支持多边形,适合标注姿态复杂的人体轮廓。武术动作识别只需要人体级别的框,不是关键点级别的标注,所以直接用 LabelImg 画矩形框就够了,不要过度设计。

标注流程分为三步。第一步,安装 LabelImg 并用它打开数据集图片目录。第二步,对每张图片中的人物画矩形框,并在弹出的对话框里输入动作类别名称。第三步,导出标注文件。

# 安装 LabelImg(Python 3.10 环境) pip install labelimg # 启动标注工具 labelimg ./datasets/wushu/images/train ./datasets/wushu/classes.txt

启动后界面左侧是图片列表,右侧是标注画布。用快捷键W画框,D切换到下一张图,Ctrl+S保存。保存的标注文件是 XML 格式,里面记录的是xmin, ymin, xmax, ymax像素坐标,需要后续转换成 YOLO 的 txt 格式。这个转换是毕设新手最容易卡住的地方,你不能把 XML 直接丢给 YOLOv8 训练,它不认识这种格式。

3.3 把 Labelme 的 XML 转成 YOLO 格式:转换脚本与类别映射

我写过一个标准的 XML 转 YOLO 的脚本,你把这个脚本放在数据集根目录下运行,就能把 LabelImg 生成的标注文件批量转换成训练用的 txt 文件。注意classes.txt里的类别顺序决定了最终的类别 ID,顺序一旦确定,中途不要修改,否则所有标注文件全部错乱。

import os import xml.etree.ElementTree as ET from pathlib import Path def convert_xml_to_yolo(xml_path, output_dir, class_list): """ 将 LabelImg 生成的 XML 标注转为 YOLO 格式 txt class_list: 类别列表,顺序决定类别 ID """ tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) # 读取所有目标框 boxes = [] for obj in root.findall("object"): label = obj.find("name").text if label not in class_list: continue class_id = class_list.index(label) bndbox = obj.find("bndbox") xmin = float(bndbox.find("xmin").text) ymin = float(bndbox.find("ymin").text) xmax = float(bndbox.find("xmax").text) ymax = float(bndbox.find("ymax").text) boxes.append((class_id, xmin, ymin, xmax, ymax)) # 转成 YOLO 格式:类别ID + 中心点相对坐标 + 相对宽高 lines = [] for class_id, xmin, ymin, xmax, ymax in boxes: x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}") # 写入同名 txt txt_name = Path(xml_path).stem + ".txt" with open(os.path.join(output_dir, txt_name), "w") as f: f.write("\n".join(lines)) print(f"已转换: {txt_name}")

这段脚本的核心是把像素坐标换算成相对坐标。比如一张 1280x720 的图片里,人体框的 xmin 是 200,xmax 是 600,那框宽是 400,中心点 x 是 400,归一化后就是 400 / 1280 ≈ 0.3125。训练时模型内部会把所有坐标统一到 0~1 区间,这样不依赖具体分辨率,训练和推理时图片尺寸不同也能正常检测。脚本里class_list的顺序非常重要,如果classes.txt里第一个类别是「冲拳」而转换脚本里class_list第一个也是「冲拳」,那 ID=0 就代表「冲拳」,训练和推理必须用同一个映射。

3.4 配置 wushu.yaml 训练文件的关键字段

有了图片和标注文件之后,需要在数据集根目录下创建一个 YAML 配置文件,告诉 YOLOv8 数据在哪、有几个类别。这个文件的名字随意,但路径一定要写对,我见过太多人把路径写成绝对路径导致换机器就崩。

# wushu.yaml path: ./datasets/wushu train: images/train val: images/val # 类别名必须与 classes.txt 完全一致且顺序一致 names: 0: punch 1: kick 2: horse_step

这个文件里path是数据集根目录,train和val是相对于path的图片目录。YOLOv8 会自动在path下找labels目录,并根据图片的同名 txt 文件去匹配标注。如果你的标注文件与图片文件名不一致,训练时它会报「found no labels」错误,排查思路是先检查 labels 目录里有没有与图片同名的 txt 文件。

4. 训练与调参:损失曲线、精度指标、三个必调参数

4.1 启动训练的命令与核心参数解析

训练是整个流程中最耗时也最容易让新人玄学化的环节。很多教程都喜欢堆一堆参数,好像每个参数都有奇效,但真正的关键就三个:epochs、batch、imgsz。下面是一个可用的训练命令。

# 在虚拟环境中训练 source yolov8_env/bin/activate cd /path/to/project yolo task=detect mode=train \ model=yolov8n.pt \ data=./datasets/wushu.yaml \ epochs=100 \ batch=8 \ imgsz=640 \ device=cpu \ patience=20 \ project=wushu_runs \ name=train_v1

epochs表示训练轮数,CPU 版一般设 50 到 100 轮。注意不是轮数越多越好,很多动作数据量不大,100 轮之后模型开始过拟合,训练集损失下降但验证集精度停滞甚至下降。batch是批大小,CPU 训练时建议 4 到 8,内存不足会直接报错。imgsz是输入图片尺寸,640 是速度与精度的折中点,不要改成 1280,CPU 训练一轮要等一晚上。patience是早停机制,验证集精度连续 20 轮不提升就自动停止,这个参数能帮你省很多时间。

4.2 损失函数曲线图怎么画、怎么看

训练完成后,Ultralytics 会在wushu_runs/train_v1/下生成results.png,里面包含训练损失、验证损失、精度、召回率等曲线。但默认图看得不够细,我通常会自己写脚本读取训练日志,单独画出每个类别的损失曲线。这里用 CSV 读取结果文件中的数据。

import pandas as pd import matplotlib.pyplot as plt # 读取训练结果(Ultralytics 会生成 results.csv) df = pd.read_csv("wushu_runs/train_v1/results.csv") # 绘制训练损失和验证损失 plt.figure(figsize=(10, 6)) plt.plot(df["epoch"], df["train/box_loss"], label="train box loss") plt.plot(df["epoch"], df["val/box_loss"], label="val box loss") plt.axhline(y=min(df["val/box_loss"]), color="red", linestyle="--", linewidth=0.8) plt.xlabel("epoch") plt.ylabel("loss") plt.title("YOLOv8 武术动作训练损失曲线") plt.legend() plt.grid(True) plt.savefig("loss_curve.png", dpi=150)

看曲线有一个核心方法:如果训练损失持续下降、验证损失先降后升,说明你的模型已经过拟合了,应该减少epochs或者增加数据量。如果两条曲线都在高位震荡,说明学习率设置不合理,或者数据集里不同动作类别的样本数量严重不均衡。常见的调节手段是把冲拳这类动作背景单一的大类图片删掉一点,给难区分的动作(比如「蹬腿」和「侧踹」)多补充样本。

4.3 验证集上模型效果的三个必看指标

训练结束后要去wushu_runs/train_v1/下看一眼模型在你自己的验证集上的表现,而不是只在训练集上自嗨。这里给出单独运行验证的命令。

yolo task=detect mode=val \ model=wushu_runs/train_v1/weights/best.pt \ data=./datasets/wushu.yaml

验证输出的表格里有三个关键指标:mAP50、mAP50-95、Precision和Recall。mAP50是 IoU 阈值为 0.5 时的平均精度,数值达到 0.7 以上才算基本合格,达不到就先别急着部署,回头补数据。Precision表示模型预测的所有动作中有多少是真正正确的,Recall表示所有真实的动作中有多少被正确识别出来。这两个指标会互相制约,如果 Precision 高但 Recall 低,说明模型漏检多,把conf阈值调低一点;如果反过来,就调高一点。

5. 部署中避不开的五个坑:从显存报错到界面卡死的血泪经验

5.1 坑一:训练到一半显存不足直接崩

现象:GPU 训练过程中报CUDA out of memory,程序退出,之前训的轮次全部作废。

原因:batch设置得太大,或者图片尺寸被设成了 1280,显存占用直接爆炸。很多教程默认imgsz=640、batch=16,但六卡以下的学生卡根本扛不住。

解决:先降batch到 4 再试,如果还崩就继续降到 2。注意batch太小时 BN 层的统计量不稳定,可以用batch=8的基础设置搭配optimizer=SGD,不要换成 AdamW,后者的显存开销更大。

5.2 坑二:训练不收敛,损失函数值全程不动

现象:训练日志里box_loss和cls_loss始终在一个水平线上不下来,损失曲线图是平的。

原因:学习率过高导致梯度震荡,或者数据集的标注文件全部清零了。后面这个原因更容易查——用脚本打开几个 txt 标注文件确认里面真的有数字,如果全是 0 0 0 0 0,说明转换脚本出错了。

解决:先在训练命令里加lr0=0.001并固定随机种子seed=42,把学习率压下来再看。如果确认标注文件有内容但损失还是平的,打开wushu.yaml检查names字段,类别数量和标注文件里的最大值是否匹配,ID 对不上会导致类别损失恒为奇数。

5.3 坑三:推理速度极慢,CPU 版加载模型就卡五秒

现象:模型加载完成但推理一帧画面耗时 2 秒以上,视频根本没法看。

原因:CPU 版本的 PyTorch 默认使用 OpenMP 多线程,线程切换开销巨大,尤其在 Windows 上表现明显。另一个元凶是输入图片没有缩放,直接拿 1920x1080 的原始分辨率喂给模型推理。

解决:推理之前统一用letterbox函数把图片缩放到 640x640,推理完再把检测框坐标映射回原图尺寸。同时设置torch.set_num_threads(4),这能在大多数场景下让推理提速 30%。

5.4 坑四:PySide6 界面在推理时假死,窗口直接未响应

现象:点击界面上的「开始识别」按钮后,窗口立刻变成未响应状态,等推理结束才恢复。

原因:推理过程放在主线程中,而 PySide6 的主线程需要不断处理界面事件,一旦被推理阻塞,窗口就无法刷新。

解决:把推理逻辑放到 QThread 子线程中。这个改动虽然简单,但很多毕设代码并没有这么做。后面第 6 章会给一段完整的 QThread 推理模板,这里先记住一点:界面和模型推理永远不要在同一个线程。

5.5 坑五:导出 ONNX 模型后精度骤降

现象:用model.export(format="onnx")导出成功,但 ONNX 模型推理的检测框数量比 PyTorch 少了一半。

原因:导出时opset版本过低,导致部分算子在转换时被舍弃;或者输入输出的动态维度没有指定。

解决:在导出命令里加opset=12、dynamic=False,并且导出后先用 ONNX Runtime 跑一遍同一张图片,对比检测结果。不要指望模型导出后毫发无损,做毕设的话,如果能接受 5% 以内的精度下降,就直接部署 ONNX 版本,速度比 PyTorch 快很多。

6. 可视化界面与最终部署:从 QThread 推理模板到打包发布

6.1 用 PySide6 做一个能登录、能选文件、能显示结果的识别界面

可视化界面是整个系统最直观的门面,也是答辩时老师第一个动手试的东西。常见的做法是用 PySide6(Qt 的 Python 绑定)做一个窗口,左侧是操作按钮和参数面板,右侧是实时检测画面,底部是日志输出区。界面结构不必复杂,关键是把推理线程做对。

下面是 QThread 推理线程的模板代码,你只需要把predict_frame方法里的模型加载和推理逻辑换成自己的就行。这段代码处理了线程生命周期和信号传递两个核心问题。

# inference_thread.py from PySide6.QtCore import QThread, Signal from ultralytics import YOLO import cv2 class InferenceThread(QThread): # 定义信号:帧数据、检测结果、异常信息 frame_ready = Signal(object) result_ready = Signal(dict) error_occurred = Signal(str) def __init__(self, model_path, source_path, parent=None): super().__init__(parent) self.model_path = model_path self.source_path = source_path self.running = True self.model = None def run(self): try: # 模型必须在子线程中加载,首次加载会较慢 self.model = YOLO(self.model_path) cap = cv2.VideoCapture(self.source_path) # 逐帧读取并推理 while self.running: ret, frame = cap.read() if not ret: break # 缩放推理以获得稳定速度 results = self.model.predict(frame, imgsz=640, conf=0.35, verbose=False) # 将检测结果绘制到原帧上 annotated = results[0].plot() self.frame_ready.emit(annotated) # 统计当前帧的类别信息 cls_counts = {} for box in results[0].boxes: cls_name = self.model.names[int(box.cls[0])] cls_counts[cls_name] = cls_counts.get(cls_name, 0) + 1 self.result_ready.emit(cls_counts) cap.release() except Exception as e: self.error_occurred.emit(str(e)) def stop(self): self.running = False self.wait()

这个模板有三个方面值得你学习。第一点是frame_ready信号携带的是object类型,这样可以避免频繁复制图像数据导致的内存开销激增;第二点是模型的加载放在run()方法里,如果放在__init__中,界面启动时就会卡住;第三点是定义了error_occurred信号,推理线程里任何异常都能传回主界面显示,排查问题的时候不用去翻终端日志。主窗口那边只需要连接这三个信号,把图像更新到 QLabel 上即可。

注意:results[0].plot()返回的数组是 BGR 格式,在 PySide6 中显示前要转成 RGB,否则画面偏蓝。

6.2 导出模型与部署:CPU 部署、ONNX Runtime 的提速对比

界面能跑通后,接下来就是部署。毕设答辩现场一般不允许你有太多时间等待推理,所以建议在交付前把模型从 PyTorch 导出为 ONNX 格式,用 ONNX Runtime 做 CPU 推理,速度能提升不少。这里给出导出与推理的完整对照。

# 导出 ONNX 模型 yolo task=detect mode=export \ model=wushu_runs/train_v1/weights/best.pt \ format=onnx \ opset=12 \ imgsz=640

导出成功后会在best.pt同目录下生成best.onnx文件。接着在推理线程里改用 ONNX Runtime 加载模型,省略掉 PyTorch 的逐步加载过程,加载耗时从原来的 3 秒降到 0.5 秒以内。

6.3 打包发布:把 Python 脚本变成一份可交付的 exe

毕设交付时你不能要求老师装 Python 环境再跑你的代码,所以打包是最后一步。常见的打包工具是 PyInstaller,但用它打包 PySide6 + Ultralytics + ONNX Runtime 的组合会遇到很多坑,这里给出经过验证的打包命令。

pip install pyinstaller pyinstaller --noconfirm \ --windowed \ --name WushuRecognition \ --add-data "best.onnx:models" \ --hidden-import cv2 \ --hidden-import ultralytics \ --hidden-import PySide6 \ main.py

--windowed表示不显示控制台窗口,适合交付给非技术用户。--add-data把模型文件打包进 exe,注意路径后面的冒号是 Linux 路径分隔符,Windows 下要改成;。打包完成后在dist/WushuRecognition/目录下找到 exe。如果运行时报找不到模型文件,检查sys._MEIPASS路径下是否真的存在models/best.onnx,这是 PyInstaller 临时解压目录,代码里要用os.path.join(sys._MEIPASS, "models", "best.onnx")去引用资源文件。

CPU 部署时可以把torch.set_num_threads(4)加到main.py的最顶部,多线程推理在 ONNX Runtime 下会自适配。实测 CPU 推理 640x640 的单帧耗时能压到 80 到 150 毫秒,流畅度已经足够支撑演示。

6.4 最后的性能验证与交付建议

模型打包完成后,我习惯用一段包含多个动作的短视频做端到端测试,记录三件事:检测框的稳定性、动作类别的切换延迟、界面长时间运行后的内存占用。如果检测框闪烁严重,把conf阈值从 0.35 提高到 0.5,虽然会牺牲一部分召回率,但演示效果更稳。如果动作类别切换延迟超过一秒,检查两类相似动作的训练样本量是否均衡,在数据层面解决比在代码层面硬调要好。

回到最开始说的那句话——这个项目的价值不在技术难度,而在工程完整性。数据标注的时间通常占整个项目周期的 60% 以上,训练和调参反而快得多。给正在做这个题目的朋友一句实在话:别急着花钱买现成的「毕设源码」,自己走一遍标注、训练、部署的流程,学到的东西比源码本身值钱得多。希望这篇笔记能帮到你。

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

返回列表