简介:面向计算机相关专业毕业设计的人脸伪造/深度伪造检测项目,提供配套的视频数据集说明、Python源代码与文档。数据基础来自Faceforensics(约1000个真实视频与经DF伪造生成的约1000个视频),后续更新补充Celeb-DF v1/v2两个版本内容;代码使用mediapipe提取468维人脸关键点,支持批量处理文件夹内视频,直接用于模型训练前的特征工程。压缩包共8个文件,含6个Python脚本(人脸关键点提取、校准工具等)、1个Markdown说明文档及辅助文件,整体仅12KB,轻量易用。已有210人学习下载。源码经实测运行通过,上传者反馈毕业设计答辩评审平均分达96分,适合计算机、人工智能、通信工程等专业学生作为毕设项目、课程设计或作业参考,也可在此基础上二次开发。
1. 毕设模型训练别急着跑代码:先把数据集、源代码和文档说明当成一套交付物去设计
答辩翻车的大多数情况,不是最终模型精度不够高,而是整个项目不可复现:导师换台电脑运行你的源代码,数据路径直接报错,文档里写的命令和实际代码脱节,辛辛苦苦训出来的权重被当成黑匣子。标题里的“数据集+源代码+文档说明”看起来是三样独立的东西,实际上是一套闭环交付物——数据集决定模型能力的上限,源代码决定别人能不能复现你的实验,文档说明决定答辩时别人能不能看懂你的思路。这套方案适合做图像分类、目标检测、语义分割、OCR 这类深度学习毕业设计的学生,也适合需要向导师交付完整工程的横向项目。下面按照拿到题目后的实际推进顺序来讲:先定数据,再选代码,然后训练调参,最后把文档补成能自证的交付物。
2. 数据集选型与制作:先定任务格式,再动手收集和标注
很多人拿到毕设题目第一步就是 git clone 一个模型仓库,然后开始下载预训练权重,最后才想起来数据还没有。正确顺序应该是先回答三个问题:任务是什么、数据从哪来、标注格式是什么。任务类型决定了数据集的组织方式,分类任务就是一套文件夹结构,目标检测就是一套坐标标注体系,分割任务又要换一套掩码格式。格式不统一,后面所有训练代码都会卡在数据读取上。
2.1 选数据集的三条路:公开数据集、导师数据、自建数据怎么权衡
公开数据集是最省力的一条路,前提是题目本身允许使用。做鸟类识别系统,CUB-200-2011 是经典选择,细粒度分类方向每年都有人拿它做毕设,官方标注齐全,换 ResNet34 或者预训练模型做迁移学习都顺手。做目标检测,VOC 和 COCO 适合当预训练来源,但毕设题目往往带具体场景,比如燃气管道图像数据集,导师手里的工业数据和公开集差距很大,直接用公开集训练出来的模型无法在真实场景里落地。遥感旋转目标检测用 DOTA 数据集,配合 mmrotate 这类专门框架;自动驾驶方向用 SemanticKITTI 做语义分割;高光谱方向可以考虑 ICVL 公开数据集做压缩或分类实验。做 NLP 方向,用 Hugging Face 的 datasets 库一条命令就能拉取数据,再搭配 roberta 中文预训练模型做微调;图神经网络方向则可以从公开来源获取 blogcatalog 这类论文常用数据集。
导师提供的数据要单独处理。首先确认数据是否脱敏、是否允许写进论文、是否允许作为正式训练集发布,不要因为数据版权问题在答辩前被卡住。最稳妥的做法是和数据提供方签一个简单说明,写明数据用途仅限于毕业论文实验。自建数据则要控制质量,用手机拍摄或现场采集时,尽量覆盖不同光照、不同角度、不同距离,类别数量要均衡,检测任务里每个类别至少要有几百个标注框,否则训练时模型容易偏向样本多的类别。
2.2 把分类/检测数据整理成训练能吃的格式:VOC标注转YOLO的转换脚本
数据集拿到手之后,第一件事不是训练,而是把标注格式统一成目标框架认识的格式。做图像分类,最简单的结构是按 train/val/test 三个子目录分好,每个类别的图片放在对应类别名目录下,PyTorch 的 ImageFolder 可以直接读。做目标检测则有三种常见格式:VOC 的 xml、COCO 的 json、YOLO 的 txt。三者之间的转换脚本在每个毕设项目里都要写一遍,YOLO 格式因为被 Ultralytics 等现代框架广泛支持,通常作为最终落地的格式。
# voc2yolo.py # 把VOC标注的xml转成YOLO格式的txt,坐标归一化到[0,1] import os import xml.etree.ElementTree as ET from tqdm import tqdm classes = ["pipe", "weld", "leak"] # 改成你自己的类别,顺序一旦确定就不要改 def convert(img_dir, xml_dir, out_dir): os.makedirs(out_dir, exist_ok=True) xml_files = [f for f in os.listdir(xml_dir) if f.endswith(".xml")] for xml_file in tqdm(xml_files): tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) lines = [] for obj in root.iter("object"): cls = obj.find("name").text if cls not in classes: continue box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) # 归一化并转为中心点宽高格式 cx = (x1 + x2) / 2.0 / img_w cy = (y1 + y2) / 2.0 / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h lines.append(f"{classes.index(cls)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(os.path.join(out_dir, xml_file[:-4] + ".txt"), "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": convert("images", "annotations/xml", "annotations/labels")这段脚本的逻辑是:遍历 xml 文件,先读图片真实宽高,再遍历每个目标对象,取出 xmin、ymin、xmax、ymax 四个角点坐标,转换成中心点加宽高的归一化表示。关键参数是 classes 列表,它的顺序决定了模型输出层的类别编号,训练和预测阶段必须保持一致。img_w 和 img_h 一定要取标注文件里记录的原始尺寸,不能拿缩略图尺寸替代,否则所有框都会偏移。脚本只处理简单嵌套结构的 VOC 文件,如果标注里有旋转框或更复杂的物体,需要换用专门工具。
数据转换完之后还要做训练集和验证集的划分。常见做法是写一个 8:1:1 的划分脚本,确保同一个目标不会同时出现在训练和验证里,然后固定随机种子。
2.3 数据增强:先做翻转和色彩抖动,别一上来就上生成模型
见过不少学生一开口就是“我用扩散模型生成数据”,但实际效果往往不如老老实实把真实数据用好。生成模型做数据补全,应该放在常规数据增强之后,作为最后的弥补手段。常规增强里,左右翻转、随机亮度对比度、随机裁剪这几样就能覆盖大部分真实场景差异,而且实现成本低、不会引入分布偏差。
# augment.py # 对训练图片做轻量级增强,标注框同步变化 import albumentations as A import cv2 import glob transform = A.Compose([ A.HorizontalFlip(p=0.5), A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.5), A.Cutout(num_holes=8, max_hole_size=24, fill_value=0, p=0.3) ], bbox_params=A.BboxParams(format="yolo", label_fields=["class_labels"], min_visibility=0.3)) for img_path in glob.glob("train/images/*.jpg"): img = cv2.imread(img_path) label_path = img_path.replace("images", "labels").replace(".jpg", ".txt") bboxes = [] class_labels = [] with open(label_path) as f: for line in f: parts = line.strip().split() bboxes.append([float(x) for x in parts[1:5]]) class_labels.append(int(parts[0])) augmented = transform(image=img, bboxes=bboxes, class_labels=class_labels) cv2.imwrite("aug/" + img_path.split("/")[-1], augmented["image"]) with open("aug/" + label_path.split("/")[-1], "w") as f: for bbox, cls in zip(augmented["bboxes"], augmented["class_labels"]): f.write(f"{cls} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}\n")核心参数是 bbox_params 里的 min_visibility=0.3,它表示增强后目标框与原始框的交并比小于 0.3 时直接丢弃该目标,避免翻转或裁剪把目标切剩一个角。用 albumentations 的好处是它会在翻转时自动把 x 中心坐标重新计算,不需要手工处理。还要注意,YOLO 格式里 class 信息不写在 bbox 里,必须通过 label_fields 传进去。这个离线增强脚本适合分类任务或者没有内置增强的框架;用 YOLOv8 训练时,官方在线增强里的 mosaic、hsv 已经很强,离线增强加太多反而会让模型过拟合到增强噪声上。
3. 源代码选型与最小改造:从 git clone 到 first run,四步拿到可复现基线
代码选型这件事,很多人只看 star 数。但毕设代码要的不是功能最多,而是“能在毕业前稳定跑完”。选错了框架,改 bug 的时间比训练时间还长。常见的可靠路径是:目标检测用 Ultralytics YOLO 系列,yolov5、yolov8、yolov11 都有官方训练脚本和文档,适合训练自己的数据集;旋转目标检测用 mmrotate 配 DOTA;OCR 用 EasyOCR,支持训练和加载自定义模型;文本分类用 transformers 框架加载 roberta 中文预训练模型;图像分类想用经典结构,torchvision 官方 ResNet34 预训练权重直接拿来微调。如果只是快速验证想法,Teachable Machine 这类无代码工具确实方便,但毕业设计要交付可复现的源代码,不建议把它作为主交付物。
3.1 代码选型的三个判断点:官方维护、依赖干净、任务匹配
判断一段源代码能不能用于毕设,看三点。第一,官方维护状态,看最近一次 commit 时间,超过一年没更新的仓库慎用,因为依赖很容易和最新环境冲突;第二,依赖是否干净,requirements.txt 里如果全是基础包,环境重建成本就低,如果带一堆自定义算子,换机器后编译环节会劝退你;第三,任务匹配度,分类任务不需要硬上检测框架,文本任务也不需要套视觉增强。一个简单的判断办法:在 GitHub 搜索框里用你的任务加模型名字检索,比如"yolov8 train custom data",优先选有官方文档且有训练参数解释的仓库,这决定了后面调参时能不能搜到答案。
3.2 最小跑通:创建环境、下载预训练权重、做一次推理
代码选好之后,不要直接训练,先把环境跑通。用 conda 创建独立环境,Python 版本选 3.9 或 3.10,然后安装依赖。
# 创建独立环境,避免污染系统Python conda create -n bishe python=3.9 -y conda activate bishe # 安装CPU版或GPU版PyTorch,先装torch再装ultralytics pip install torch==2.1.0 torchvision==0.16.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics==8.1.0 # 下载yolo预训练模型并做一次推理验证环境 yolo predict model=yolov8n.pt source='https://ultralytics.com/images/bus.jpg'第一次推理成功后,会在当前目录生成 runs/detect/predict 结果图,说明模型下载和推理链路正常。yolo 命令的几个关键参数里,model 指定权重文件,首次运行时如果本地没有,会自动从官方地址下载预训练模型并缓存到用户目录;source 支持图片路径、视频路径、摄像头设备号或者 URL;可以加 conf=0.25 和 iou=0.7 控制置信度阈值和 NMS 阈值,这两个值直接决定检测框的多少,置信度设太低会输出一堆误检框。环境跑通之后,训练才有意义,否则后面报错你分不清是代码问题还是安装问题。
3.3 训练自己的数据集:yaml配置、类别数、训练命令和恢复训练
环境正常后,先准备数据配置。Ultralytics YOLO 系列用 yaml 描述数据路径和类别。
# gas_pipeline.yaml train: ./data/train/images val: ./data/val/images nc: 3 names: ["pipe", "weld", "leak"]yaml 里 train 和 val 写的是图片目录路径,YOLO 会自动在同级目录下寻找 labels 文件夹。nc 是类别数,names 是类别名列表,顺序必须和标注 txt 里的 class id 一致。训练命令写成:
yolo train model=yolov8n.pt data=gas_pipeline.yaml \ epochs=100 imgsz=640 batch=8 \ project=runs name=gas_v8n \ patience=20这里 model 用 yolov8n.pt 作为预训练权重,框架会在训练开始前把最后一层分类数自动改写成 nc 的值。epochs 设 100,配合 patience=20 实现早停——连续 20 个 epoch 验证集指标不提升就自动停止,避免无意义的时间消耗。imgsz 设 640 是速度与精度的平衡点,显存不够可以降到 320;batch 设 8 是普通显卡的安全值,如果显存充足可以加大,但不要一次拉到显存上限,训练中途爆显存会丢进度。project 和 name 控制输出目录,runs/gas_v8n 下面会有 weights 文件夹存放 best.pt 和 last.pt,训练中断后可以直接用 last.pt 恢复。
用自己的数据训练时,最常见的翻车点不是模型,而是路径和类别号对不上。用 yolov8 新版本几乎不用手工改模型结构,老版本代码可能需要手动修改 model.yaml 里的 nc 值,具体看仓库文档。训练命令里的 batch、imgsz 是要跟着显卡变化的,后面第 4 章会专门展开。
4. 训练日志与调参判断:看懂 loss 曲线和 mAP,模型训练才能不玄学
训练过程对很多学生来说是个黑匣子,跑起来就开始刷手机,等结果看运气。实际上训练日志里的每个数字都有明确含义,看懂之后,大部分调参问题都能自己判断。模型训练前传和反传的基本逻辑是:前向传播计算预测结果和 loss,反向传播更新梯度,日志里的 loss 值和 mAP 值就是这一过程的外在表现。
4.1 训练日志和 results 图里哪些数字能信
YOLO 系列训练完成后会在输出目录生成 results.png,里面画了多条曲线。最重要的一条是 metrics/mAP50-95(B),它表示在不同 IoU 阈值下 mAP 的平均值,指标越接近 1 越好。毕设场景里如果目标本身不大,mAP50 更能反映实际可用性,因为 50-95 对框的定位精度要求很高,很多小目标在 IoU 0.75 下天然吃亏。loss/box 是边框回归损失,loss/cls 是分类损失,loss/dfl 是分布焦点损失。看 loss 曲线时,如果训练 loss 一直下降但验证 loss 拐头向上,说明过拟合开始了,早停会在这个点附近停下来;如果训练 loss 和验证 loss 都不下降,先检查数据是不是空的,再检查学习率是不是太大。
4.2 必调参数清单:一张表格说清影响和推荐值
训练一个模型需要调的参数其实不多,把几个关键参数吃透,比乱试一组 grid search 更有效。
| 参数 | 常见默认值 | 影响 | 毕设推荐 |
|---|---|---|---|
| imgsz | 640 | 输入分辨率,越大细节越清晰但显存占用越高 | 显存吃紧就用 320,目标小用 640 |
| batch | 8-16 | 每步训练的样本数,影响梯度稳定性和显存 | 显卡占用控制在 70% 左右 |
| epochs | 100 | 训练轮数 | 100 起步,配 patience=20 早停 |
| lr0 | 0.01 | 初始学习率 | 小数据集用 0.005 更稳 |
| weight_decay | 0.0005 | 正则化强度,防过拟合 | 默认值即可 |
| mosaic | 1.0 | 四图拼一图增强 | 数据少于 2000 张时建议关掉或设 0.5 |
| close_mosaic | 10 | 最后 10 个 epoch 关闭 mosaic | 保持默认,让模型适应真实比例 |
| workers | 8 | 数据加载线程数 | Windows 下设为 2 或 4,设太高容易卡死 |
mosaic 是双刃剑。它能让模型看到更丰富的上下文,但小数据集强化增强反而会把分布带偏。数据少时把 mosaic 关掉,直接用原始图和简单翻转训练,效果往往更好。学习率参数 lr0 在从预训练权重微调时不要设太大,0.01 对初学者来说已经偏高,用 0.005 更安全。
4.3 训练中断、显存不足、loss 为 NaN 的现场处理
训练到一半中断是最常见的事,断电、显卡驱动崩溃、手动 Ctrl+C 都可能发生。YOLO 系列在 weights 目录下保存 last.pt,可以直接恢复:
yolo train resume model=runs/gas_v8n/weights/last.ptresume 模式会读取上次训练的参数和 epoch 数,继续跑而不是从头开始。显存不足报错时,先降 batch,batch 降到 4 还不行就降 imgsz 到 320,这两个参数对显存占用影响最大。注意不要同时开太多训练任务,Windows 下数据加载 workers 设太高也会吃到额外显存。loss 变 NaN 的常见原因是学习率太高或者数据里有空的标注文件,先去 labels 目录检查有没有 0 字节的 txt 文件,把它删掉再重新划分数据集。如果学习率问题,把 lr0 降到 0.001 再试。
还有一种隐蔽的翻车点:训练时打开了预训练模型自带的类别数,和自己的数据类别不一致,导致 NaN。YOLO 系列会自动覆盖,但如果你用的是老版本源码或自定义网络,需要手动确认最后一层的输出通道数等于自己的 nc 值。
5. 文档说明与交付的4个常见坑:README、数据说明和环境记录
很多学生觉得文档说明就是写个 README,两三句话完事。但实际上,毕业论文的“文档说明”通常指三层内容:README 告诉别人怎么跑;数据说明告诉别人数据从哪来、格式是什么、怎么预处理;复现说明记录环境和参数,让实验可以原样跑回来。这三层都齐了才算合格交付。这里不讲怎么写长篇大论,只记录我在实际交付中踩过的四个典型坑。
5.1 坑一:README 里的入口脚本和代码对不上
现象:导师拿到代码后按 README 里的命令运行,提示找不到入口脚本,或者运行到一半报模块不存在。
原因:项目开发过程中改过目录结构或入口文件名,README 没有同步更新,甚至有些同学直接把别的项目的 README 改了个标题就交上来了。
解决:文档里的每一条命令,都要在一个全新环境里照着跑一遍再写进去。具体做法是新建一个干净的 conda 环境,按 README 从安装依赖开始逐步执行,任何一步报错都要修正文档,直到跑通结果。最终把入口命令原样粘进 README,比如yolo train data=gas_pipeline.yaml model=yolov8n.pt,并注明使用的 Python 版本、PyTorch 版本和依赖清单。
5.2 坑二:数据目录改名后训练报 FileNotFoundError
现象:训练命令能执行,但跑到数据加载阶段报找不到图片或标签文件,检查后发现数据集目录在本地叫 gas,在压缩包里叫 gas_pipeline,yaml 里写死的是原名。
原因:用压缩包传输代码和数据后,目录被解压改名,yaml 和脚本里的绝对路径或硬编码路径失效。
解决:所有路径尽量用相对路径,并且启动脚本里做一个路径自动校正。比如在 train 命令前加一行cd $(dirname $0)把工作目录切到脚本所在目录,yaml 里写./data/train/images而不是D:/projects/gas/data/train/images。交付时把目录结构写在文档里,注明 data 目录和代码目录的层级关系。
5.3 坑三:同一次实验两次训练结果差很多,导师说不可复现
现象:同一个数据集、同一个训练命令,第一次跑 mAP 到 0.72,第二次只有 0.64,换台机器差距更大。
原因:数据划分用了未固定的随机种子,训练过程中也没有固定随机数。PyTorch 默认情况下随机初始化权重,数据加载顺序也和随机种子挂钩,所以结果天然有波动。
解决:在训练入口脚本里显式固定种子,写torch.manual_seed(42)、numpy.random.seed(42),数据划分脚本里设置random.seed(42)并固定划分比例。同时在文档里记录训练时的随机种子值。很多框架没有提供种子参数,最稳妥的做法是把数据集划分脚本放到 tools 目录,让它先生成 train.txt、val.txt,再把这两个文件的列表写进文档,这样数据划分就和随机种子无关了。
5.4 坑四:换一台机器就 OOM 或装不上依赖
现象:代码在自己笔记本上跑得好好的,导师换到另一台机器上跑直接显存溢出,或者安装依赖时版本冲突报错一堆。
原因:环境的差异没写在文档里。训练参数里 batch 和 imgsz 是针对自己的显卡设置的,换一张显存小的卡自然跑不动;requirements.txt 没有锁版本,新机器安装到了最新版 PyTorch,和代码不兼容。
解决:导出环境的具体版本信息写进文档,用pip freeze > requirements_lock.txt记录完整依赖,再用nvidia-smi和python -c "import torch; print(torch.version.cuda)"记录显卡和 CUDA 版本。在文档里明确说明:显存不足时先按第 4 章的参数表下调 batch 和 imgsz。如果代码依赖特定 PyTorch 版本,给出安装命令而不是只写 requirement 文件。
6. 验证与答辩演示:把训练结果变成看得见、跑得动的东西
模型训练完不等于毕业设计做完,还要用一套验证脚本证明结果是可信的。先跑一次标准验证,得到测试集上的精确率、召回率和每个类别的 AP,这些数字是答辩时最有力的支撑。
# evaluate.py # 用训练好的best.pt在测试集上做最终验证 from ultralytics import YOLO model = YOLO("runs/gas_v8n/weights/best.pt") metrics = model.val(data="gas_pipeline.yaml", split="test") print(f"mAP50: {metrics.box.map50:.4f}") print(f"mAP50-95: {metrics.box.map:.4f}")这段脚本会输出最终的 mAP 值,并且自动生成混淆矩阵和 PR 曲线图到验证输出目录。这些图表可以放进毕业论文的实验章节。如果想让答辩演示更直观,可以用 Gradio 写一个十行左右的可视化页面,上传一张图片就能显示检测结果:
# demo.py import gradio as gr from ultralytics import YOLO model = YOLO("runs/gas_v8n/weights/best.pt") def detect(img): results = model(img, conf=0.25) return results[0].plot() gr.Interface(fn=detect, inputs="image", outputs="image").launch()这个 demo 不用写前端,本地运行后浏览器打开就是上传窗口。如果题目是边缘部署,可以试试把训练好的模型导出成 ONNX,再用 NCNN 或 TensorRT 跑在树莓派 5 这类开发板上,这条链路本身就是答辩加分项。还有一个更实用的技巧:把所有操作封装成 run_all.sh,从环境安装、数据划分、训练到验证一条命令跑完,导师能直接复现,代码评阅的得分往往比模型精度更高。
我自己的教训是,第一年做毕设时只交了一个 Jupyter Notebook,训练过程全在内存里,导师打开全是报错,最后花了两个通宵补环境。从那以后,我每次训练前都在项目根目录建一个 README,里面只有三样东西:能跑通的完整命令、数据格式说明、训练参数表。每次改动代码就顺手更新文档,不等到最后一天补。毕设模型训练做到最后,拼的不是谁的网络结构更花哨,而是谁的工程能在别人的机器上跑起来。希望帮到你。
本文还有配套的精品资源,点击获取