
简介基于YOLOv8的武术动作识别系统是一套完整可运行的毕业设计项目面向计算机相关专业的在校学生、教师及企业开发者适用于毕设答辩、课程设计、大作业或项目初期演示也适合对目标检测与动作识别感兴趣的学习者进阶实践。压缩包共包含97个文件以Python源码为主覆盖约70个py脚本并配有预训练pt权重、xml配置文件、pyc辅助文件以及mp4演示视频整体仅24.21MB结构清晰便于快速部署和二次开发。系统集成了可视化界面、训练与推理模块运行后能自动产出混淆矩阵、F1分数曲线、精确率-召回率曲线、标签分布图及验证集预测结果等评估内容有助于从数据角度审视模型效果。附带的README与部署说明提供了完整的环境配置和启动流程即使基础一般也能按步骤跑通项目。该资源已有73人学习下载所有代码均经过测试作为答辩展示或学习参考皆可放心使用。1. 从毕设答辩现场说起为什么武术动作识别值得用 YOLOv8 做如果你正在为毕业设计或课程设计找一个「看起来高级、能演示、还能写进简历」的选题那「基于 YOLOv8 的武术动作识别系统」确实是个不容易出错的方向。我见过不少学生拿这个标题交差有的拿模板改一改界面就完事有的连模型都没训练过、直接用预训练权重跑个视频就当成果。真正能把「数据集 → 训练 → 界面 → 部署」完整打通的人反而没几个。这套系统的本质其实不复杂用 YOLOv8 检测视频里的人物目标再把检测框内的人体区域交给一个分类模块识别具体武术动作。难点不在模型选型而在数据准备、界面线程设计和部署环境的兼容性。本文会从环境搭建开始走完数据标注、训练调参、界面封装和打包发布的全流程并把最容易翻车的几个坑点写清楚。适合正在做毕设的学生也适合想快速落地一个动作识别 Demo 的工程师。2. 先把环境立住Ubuntu 20.04 跑通 YOLOv8CPU 版也一样能训2.1 为什么选 YOLOv8 而不是 YOLOv5 或更老的版本武术动作识别的常见做法是把任务拆成「检测人体 分类动作」。YOLOv8 本身就能同时输出检测框和目标类别所以我们只需要把每个武术动作定义成一个类别比如「冲拳」「蹬腿」「马步架打」YOLOv8 会直接输出动作类别和置信度省掉单独做分类模型的步骤。相比 YOLOv5v8 的主要改进是把 C3 模块换成了 C2fneck 部分去掉了上采样后的冗余卷积层推理速度更快精度也没有牺牲。还有一个关键点是 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(sourcetest.jpg, saveTrue, conf0.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})这段代码里saveTrue会把标注后的图片保存到runs/detect/predict目录下。你去看这张保存的图片如果人物被正确框出来说明模型推理链路正常。如果没有框出来先检查test.jpg路径是否正确再看conf0.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 并用它打开数据集图片目录。第二步对每张图片中的人物画矩形框并在弹出的对话框里输入动作类别名称。第三步导出标注文件。# 安装 LabelImgPython 3.10 环境 pip install labelimg # 启动标注工具 labelimg ./datasets/wushu/images/train ./datasets/wushu/classes.txt启动后界面左侧是图片列表右侧是标注画布。用快捷键W画框D切换到下一张图CtrlS保存。保存的标注文件是 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 是 200xmax 是 600那框宽是 400中心点 x 是 400归一化后就是 400 / 1280 ≈ 0.3125。训练时模型内部会把所有坐标统一到 0~1 区间这样不依赖具体分辨率训练和推理时图片尺寸不同也能正常检测。脚本里class_list的顺序非常重要如果classes.txt里第一个类别是「冲拳」而转换脚本里class_list第一个也是「冲拳」那 ID0 就代表「冲拳」训练和推理必须用同一个映射。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 taskdetect modetrain \ modelyolov8n.pt \ data./datasets/wushu.yaml \ epochs100 \ batch8 \ imgsz640 \ devicecpu \ patience20 \ projectwushu_runs \ nametrain_v1epochs表示训练轮数CPU 版一般设 50 到 100 轮。注意不是轮数越多越好很多动作数据量不大100 轮之后模型开始过拟合训练集损失下降但验证集精度停滞甚至下降。batch是批大小CPU 训练时建议 4 到 8内存不足会直接报错。imgsz是输入图片尺寸640 是速度与精度的折中点不要改成 1280CPU 训练一轮要等一晚上。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], labeltrain box loss) plt.plot(df[epoch], df[val/box_loss], labelval box loss) plt.axhline(ymin(df[val/box_loss]), colorred, linestyle--, linewidth0.8) plt.xlabel(epoch) plt.ylabel(loss) plt.title(YOLOv8 武术动作训练损失曲线) plt.legend() plt.grid(True) plt.savefig(loss_curve.png, dpi150)看曲线有一个核心方法如果训练损失持续下降、验证损失先降后升说明你的模型已经过拟合了应该减少epochs或者增加数据量。如果两条曲线都在高位震荡说明学习率设置不合理或者数据集里不同动作类别的样本数量严重不均衡。常见的调节手段是把冲拳这类动作背景单一的大类图片删掉一点给难区分的动作比如「蹬腿」和「侧踹」多补充样本。4.3 验证集上模型效果的三个必看指标训练结束后要去wushu_runs/train_v1/下看一眼模型在你自己的验证集上的表现而不是只在训练集上自嗨。这里给出单独运行验证的命令。yolo taskdetect modeval \ modelwushu_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显存占用直接爆炸。很多教程默认imgsz640、batch16但六卡以下的学生卡根本扛不住。解决先降batch到 4 再试如果还崩就继续降到 2。注意batch太小时 BN 层的统计量不稳定可以用batch8的基础设置搭配optimizerSGD不要换成 AdamW后者的显存开销更大。5.2 坑二训练不收敛损失函数值全程不动现象训练日志里box_loss和cls_loss始终在一个水平线上不下来损失曲线图是平的。原因学习率过高导致梯度震荡或者数据集的标注文件全部清零了。后面这个原因更容易查——用脚本打开几个 txt 标注文件确认里面真的有数字如果全是 0 0 0 0 0说明转换脚本出错了。解决先在训练命令里加lr00.001并固定随机种子seed42把学习率压下来再看。如果确认标注文件有内容但损失还是平的打开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(formatonnx)导出成功但 ONNX 模型推理的检测框数量比 PyTorch 少了一半。原因导出时opset版本过低导致部分算子在转换时被舍弃或者输入输出的动态维度没有指定。解决在导出命令里加opset12、dynamicFalse并且导出后先用 ONNX Runtime 跑一遍同一张图片对比检测结果。不要指望模型导出后毫发无损做毕设的话如果能接受 5% 以内的精度下降就直接部署 ONNX 版本速度比 PyTorch 快很多。6. 可视化界面与最终部署从 QThread 推理模板到打包发布6.1 用 PySide6 做一个能登录、能选文件、能显示结果的识别界面可视化界面是整个系统最直观的门面也是答辩时老师第一个动手试的东西。常见的做法是用 PySide6Qt 的 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, parentNone): 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, imgsz640, conf0.35, verboseFalse) # 将检测结果绘制到原帧上 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 taskdetect modeexport \ modelwushu_runs/train_v1/weights/best.pt \ formatonnx \ opset12 \ imgsz640导出成功后会在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% 以上训练和调参反而快得多。给正在做这个题目的朋友一句实在话别急着花钱买现成的「毕设源码」自己走一遍标注、训练、部署的流程学到的东西比源码本身值钱得多。希望这篇笔记能帮到你。本文还有配套的精品资源点击获取