
简介面向计算机视觉与信息安全研究者的Deepfake视频检测完整项目包基于卷积Vision-TransformerCNNViT架构实现提供从模型训练、评估到预测的一站式框架。压缩包共196个文件约80.29MB包含9个Python源码文件、1个预训练模型pth、13个mp4测试视频、167个jpg图像帧样本以及csv预测结果、json配置、pkl数据等辅助文件结构清晰便于直接复现实验。配套运行说明文档md详细指导环境安装、参数配置和启动流程降低上手门槛。已有400人学习下载适合入门Deepfake检测的研究人员、高校学生及安全领域从业者快速开展实验。内置预训练模型可直接用于视频真伪判定也可基于源码框架自定义数据集训练为学术探索和实际应用提供可靠基线支持。1. 为什么deepfake视频检测要选卷积Vision-Transformer从定位伪造痕迹说起deepfake视频检测这几年最大的变化是从“看像素异常”升级到“看时空一致性”。早期基于CNN的检测器擅长抓肤色不均、边缘锯齿这类低级伪影但生成算法迭代之后这些痕迹越来越少单帧静态特征越来越不可靠。Vision-Transformer这类全局建模结构能把整张人脸当成一个序列来理解注意力机制天然适合捕捉眼部高光、牙齿边界这些跨区域不一致的细节而卷积Token嵌入CvT又在保留CNN局部纹理敏感性的同时让Transformer不再需要海量数据就能收敛。这套资源正好踩在这个交叉点上——用卷积Vision-Transformer做帧级二分类再配合时序策略把单帧分数聚合成视频级判定适合正在做伪造检测、内容审核、安全风控相关项目的开发者拿去做二次开发和复现。源码、预训练权重、运行说明都齐了下面我按拆解过的实际流程来讲。2. 模型结构拆解CvT把图像切成可学习的token伪造痕迹藏在注意力图里2.1 从ViT到CvT卷积token嵌入解决小数据收敛难的问题原始ViT的做法是把224×224图像切成16×16的patch每个patch展平成向量直接过线性投影。问题在于patch之间没有重叠边界信息丢失而且训练数据不足时注意力矩阵学不到有意义的依赖关系模型很容易在验证集上震荡。CvT的改进思路是在token嵌入阶段引入卷积先用3×3卷积做重叠token化再在每层Transformer前加深度可分离卷积做token间信息交互。这样前几层仍然是CNN的归纳偏置主导深层再由全局注意力接管收敛速度和最终精度都比纯ViT稳。# 卷积token嵌入层把视频帧从图像空间投影到token序列 class ConvTokenEmbedding(nn.Module): def __init__(self, in_channels, embed_dim, patch_size, stride): super().__init__() # 重叠卷积做token化stride小于kernel_size保证相邻token有重叠 self.proj nn.Conv2d(in_channels, embed_dim, kernel_sizepatch_size, stridestride, paddingpatch_size // 2) def forward(self, x): # x: [B, C, H, W] - [B, embed_dim, H, W] - [B, N, embed_dim] x self.proj(x) x x.flatten(2).transpose(1, 2) return x这段代码是整个CvT结构的第一块积木。patch_size控制每个token的感受野范围stride小于kernel_size时相邻token覆盖区域有重叠这是与ViT最大的区别——重叠意味着空间连续性被显式建模伪造图像中常见的局部异常如牙齿边缘发糊、发丝断裂更容易被相邻token感知到。我一般会把patch_size设为7、stride设为4这个组合在256分辨率下能保证token数量不过多也不会丢掉细粒度纹理。对比一下三种结构的差异选型时会很清楚结构局部纹理敏感度全局依赖建模小数据收敛难度参数量倾向CNN高弱低中ViT低强高大CvT中高强中中2.2 帧级二分类与视频级判定模型到底在学什么项目的核心任务是二分类给定一帧人脸图像输出它为deepfake的概率。在CvT结构里帧级分类头一般这样组织——最后一层Transformer输出后取class token或对全部token做平均池化再接一个两层MLP映射到2维logits。# CvT分类头把token序列压缩成2维分数 class ClassificationHead(nn.Module): def __init__(self, embed_dim, num_classes2, dropout0.1): super().__init__() self.norm nn.LayerNorm(embed_dim) self.fc nn.Sequential( nn.Linear(embed_dim, embed_dim // 2), nn.GELU(), nn.Dropout(dropout), nn.Linear(embed_dim // 2, num_classes) ) def forward(self, x): # x: token序列取全局平均池化 x x.mean(dim1) x self.norm(x) return self.fc(x)注意dropout这个参数。deepfake检测任务里训练集和测试集常常来自不同生成器过拟合风险比普通图像分类高得多dropout从0.1提高到0.3往往能让AUC涨两三个点。全局平均池化比class token更稳原因是帧内伪造痕迹出现在哪个位置不固定平均池化对空间位置不敏感注意力图即便偶发偏移也不会让分数剧烈抖动。视频级判定则是在帧级分数之上做聚合。源码里默认用平均分数加阈值判断但我更建议用后文第6章的滑动窗口平滑方案原因到时会讲。单帧模型的输出本质上是“这张脸有没有伪造痕迹”而视频级判定要做的是“这段视频是否经过伪造”两者之间隔着时序一致性这道坎。2.3 项目文件结构源码、权重和运行说明分别在哪拿到解压后的目录先看整体布局避免一上来就翻车。典型结构如下路径内容说明src/模型定义与训练脚本cvt.py、train.py、infer.py、dataset.pyweights/预训练权重文件.pth格式PyTorch直接loaddata/样本视频与切帧缓存按real/和fake/分目录configs/训练与推理配置.yaml文件超参统一管理README.md运行说明环境、命令、常见报错权重文件是整个资源里最值钱的部分。deepfake检测的训练成本高在数据收集和清洗FaceForensics级别的高质量数据集动辄几百GB从零训练一个CvT在单卡V100上要跑两到三周有了预训练权重可以直接跳过热启动阶段。运行说明里如果写了具体的验证集AUC建议先用自己的测试视频复核一遍因为不同生成器的伪造风格差异极大指标波动是常态。3. 环境搭建与数据准备先把链路跑通再谈调参3.1 依赖安装与硬件选型整套代码依赖不复杂核心是PyTorch生态。我实测过的组合是Python 3.8 PyTorch 1.10 CUDA 11.3再新一点的版本也没问题但注意不要用PyTorch 2.0以上直接跑旧权重strictFalse加载时容易因为键名变更静默丢掉部分层。# 创建虚拟环境并安装依赖 conda create -n deepfake python3.8 -y conda activate deepfake pip install torch1.10.0 torchvision0.11.0 --index-url https://download.pytorch.org/whl/cu113 pip install opencv-python scikit-learn pandas tqdm pyyaml硬件方面训练阶段建议至少一张12GB显存的卡batch size能开到16以上。推理阶段要求低很多一张GTX 1660 Super就能实时跑30帧以内的视频检测。显存不够时优先降frames_per_video而不是降分辨率分辨率低于160×160会丢失大量伪造痕迹细节AUC掉得比降帧率快得多。3.2 视频切帧与人脸对齐预处理这一步是deepfake检测流程里最容易被低估的环节。原始视频不能直接送进模型需要先切帧、检测人脸、对齐并裁剪。常见的做法是用RetinaFace或MTCNN做人脸检测取检测到的人脸边界框再根据两只眼睛的位置做仿射对齐最后缩放到模型输入尺寸。项目里用的就是这一套预处理流程。# 视频切帧 人脸检测与对齐 import cv2 import numpy as np def extract_faces(video_path, model, max_frames32): cap cv2.VideoCapture(video_path) faces [] count 0 while cap.isOpened() and count max_frames: ret, frame cap.read() if not ret: break # 每两帧取一帧降低相邻帧冗余 if count % 2 0: boxes, landmarks model.detect(frame) if len(boxes) 0: # landmarks是两眼坐标做仿射对齐到固定尺寸 aligned align_face(frame, landmarks[0], output_size224) faces.append(aligned.astype(np.float32) / 255.0) count 1 cap.release() return np.stack(faces) if faces else np.zeros((1, 224, 224, 3))参数上有三个关键选择。max_frames控制每段视频取多少帧32帧覆盖2秒左右的视频内容太短可能漏掉伪造痕迹明显的片段太长则推理耗时线性增长。count % 2的抽帧策略是为了减少相邻帧的时序相关性——同一段伪造区域在连续帧里几乎一模一样全量取帧只会让模型对重复特征过拟合。align_face必须做不做对齐的话人脸位置偏移会让注意力分布变得混乱同一段视频换个人脸框位置分数能波动10%以上。3.3 训练脚本参数与超参表训练脚本的核心超参都在configs/train.yaml里直接改这个文件就行不用动源码。下面是这套源代码里我认为最值得关注的几个参数以及推荐值参数推荐值说明image_size224与预训练权重输入尺寸保持一致batch_size32显存不够时降到16同时把accumulation_steps设为2learning_rate3e-4AdamW cosine退火线性warmup占5%迭代epochs30达到30轮后AUC基本收敛再长容易过拟合mixup_alpha0.2帧级mixup能显著提升跨生成器泛化能力label_smoothing0.05防止模型对训练集置信度过高训练命令很简单但建议在真正启动前先跑一个小规模的冒烟测试——拿8条视频、1个epoch跑通全流程确认数据加载、前向传播、权重保存都没问题后再开正式训练。这一步能省下大量排错时间。# 冒烟测试1个epoch快速验证链路 python src/train.py --config configs/train.yaml --epochs 1 --max_videos 8 # 正式训练 python src/train.py --config configs/train.yaml注意力机制有个特点它不像CNN那样有明确的卷积核可视化规律训练曲线偶尔会出现先降后升的“假收敛”现象。我遇到过不少次验证loss降到最低点后开始反弹而准确率还在涨的情况——这是类别不平衡导致的假象。解决办法是盯AUC而不是ACCAUC对正负样本比例不敏感比准确率诚实得多。4. 推理与评估用预训练权重跑通一条视频看懂指标再调整阈值4.1 推理脚本用法与输出解读推理是这套资源里最快能上手的部分。预训练权重放到位后一条命令就能对单条视频输出预测结果。输出格式通常是每帧一个概率值加一个视频级聚合分数。# 推理单条视频 python src/infer.py --video ./data/test_fake.mp4 \ --weight ./weights/cvt_deepfake.pth \ --config ./configs/infer.yaml \ --output ./results/pred.json输出的JSON结构大概长这样每帧的概率值和视频级判定都在里面{ video: ./data/test_fake.mp4, frame_preds: [0.023, 0.031, 0.012, 0.985, 0.976, ...], video_score: 0.634, video_label: FAKE }解读这个结果有个容易被忽略的点video_score默认是帧级概率的简单平均但伪造片段往往只占视频的一小段平均会把异常分数稀释掉。比如一段10秒的视频里只有2秒是替换过的人脸其余8秒真实平均下来分数可能只有0.4左右按0.5阈值会直接误判为REAL。所以看单帧概率的分布比看视频级分数更可靠——伪造视频的frame_preds通常呈现尖峰状真实视频则整体平坦。4.2 ACC、AUC与混淆矩阵哪个指标会骗人deepfake检测的评估比普通分类要刁钻一些因为正负样本极不平衡。真实场景里你检测的1000条视频可能只有20条是伪造的一个全部输出REAL的模型ACC也能到98%但这个模型没有任何实用价值。这就是为什么要优先看AUC。AUC计算时用到了排序信息随机抽一个正样本和一个负样本模型给正样本打分更高的概率。它不依赖阈值选择因此能更稳定地衡量模型区分能力。0.9以上的AUC在这个任务里属于可用水平0.95以上才算优秀。混淆矩阵则要重点看FAR把真实视频判为伪造的比例和FRR把伪造视频判为真实的比例这两个数值直接影响下游审核策略——内容审核宁可误杀也不该漏放风控场景则相反。评估脚本用法如下python src/evaluate.py --pred ./results/pred.json \ --gt ./data/test_labels.csv \ --metric auc,acc,confusion_matrix4.3 阈值选择从分数分布决定决策边界最后一个关键步骤是选阈值。源码给的默认阈值是0.5但实际部署时这个值很少直接用。正确的做法是先跑一遍验证集画出正负样本的分数分布再根据业务需求决定阈值内容审核场景优先降低FAR阈值可以上调到0.7甚至0.8宁可漏检也不误杀真实内容安全风控场景优先降低FRR阈值下调到0.3宁可错杀也要把伪造内容拦截住均衡场景选约登指数最大的点即tpr - fpr最大的阈值我在实际项目里养成的习惯是阈值确定后还要在独立测试集上再验证一次分布一致性。如果训练集和测试集的分数分布差异很大说明模型泛化能力有问题这时候调阈值只是治标需要回到数据层面找原因。5. 复现避坑五条帧采样、人脸对齐、算子兼容与指标虚高5.1 帧采样不一致导致指标虚高现象在官方测试集上AUC有0.95换到自己的视频数据集上直接掉到0.8以下。原因训练时按固定帧率比如每2帧取1帧采样而你的推理脚本从视频中间连续截取了一个片段。生成器伪造的痕迹在时间轴上分布不均匀连续截取容易漏掉关键帧或者重复取到同一段伪造区域让分数偏高。解决推理脚本严格复用训练时的帧采样策略不要自己另写一套。项目里infer.py默认每2帧取1帧并限制最多32帧和训练保持一致。如果视频帧率不同按时间戳等比采样而不是按帧序号采样。5.2 人脸对齐失败导致漏检或误检现象侧脸、遮挡、低头玩手机这类非正面人脸的视频检测分数异常低或忽高忽低。原因RetinaFace检测到的人脸框在不规则姿态下不稳定仿射对齐到固定尺寸后人脸区域可能被拉伸变形注意力图无法定位到关键五官伪造痕迹自然看不出来。解决预处理中加入姿态筛选单目检测置信度低于0.5的直接丢弃该帧如果整段视频超过一半帧被丢弃则判定为“无法检测”而不是REAL。另一个补救办法是对同一帧做多尺度人脸检测取最大框减少小框带来的对齐误差。5.3 PyTorch算子兼容性导致推理直接报错现象加载权重后前向传播报错提示slow_conv2d_cpu not implemented或维度不匹配。原因PyTorch版本太新旧权重里某个自定义卷积层的实现变了或者权重训练时用的embed_dim和你加载时的配置不一致strictTrue加载直接崩。解决用torch.load(weight_path, map_locationcpu)把权重先加载到CPU打印出state_dict里的键名和模型定义逐层核对加载时设置strictFalse手动处理缺失层。我一般会保留装好旧版本的conda环境不轻易升级PyTorch。5.4 视频编解码差异导致图像质量分布偏移现象测试集是H.265编码的高清视频模型分数整体偏高误判率上升。原因训练数据大多是H.264编码压缩过的视频压缩伪影模式基本一致H.265的压缩算法不同块效应和振铃伪影分布不一样模型会把“没见过”的压缩痕迹误判为伪造痕迹。解决在预处理阶段加一个轻量级质量归一化——把帧缩放到短边256再压缩回JPEG质量90等于把所有测试视频统一到近似的压缩风格。这个操作对真实视频的分数影响很小但能把伪造视频的检测率拉回来几个点。5.5 类别不平衡让ACC虚高部署后现原形现象训练集里REAL和FAKE比例是10:1训练完成后ACC显示0.95但部署时对真实数据的误判率超出预期。原因ACC在样本不平衡时偏向多数类模型学到的决策边界向REAL偏移真实数据的漏判被掩盖了。解决参考4.2节以AUC为核心指标训练时给FAKE类加权重weighted_sampler让每个batch里正负样本比例接近1:1。类别权重这个参数配置在train.yaml里调到class_weight: [1.0, 2.0]看效果再逐步往上试探边界。6. 进阶验证滑动窗口时序平滑把视频级F1再提一档帧级模型的输出是独立概率序列没有利用帧之间的时序关系。实际伪造视频有一个明显特点伪造区域通常在一段连续帧里持续出现而真实视频的帧级分数波动更像是随机噪声。利用这个差异可以用滑动窗口对帧级分数做平滑把孤立的高分误检和孤立的低分漏检都压制下去。# 滑动窗口时序平滑输出视频级判定 import numpy as np def temporal_smooth(scores, window_size5, modemedian): # scores: 帧级概率数组长度N # window_size: 滑动窗口大小对应约0.5秒视频 if len(scores) window_size: return float(np.mean(scores)) smoothed [] half window_size // 2 for i in range(len(scores)): left max(0, i - half) right min(len(scores), i half 1) # median对孤立峰值不敏感比mean更稳 smoothed.append(np.median(scores[left:right])) # 取平滑后序列的中位数作为视频级分数 video_score float(np.median(smoothed)) return video_scorewindow_size5对应大约0.5秒的视频内容中位数滤波的好处是既能消除单帧异常峰值又不会像均值滤波那样把真实的伪造片段拖平。video_score取平滑序列的中位数而不是平均值是因为伪造片段即使只占1秒中位数也能捕捉到它的存在——平均值反而会被大量真实帧稀释。我在部署时通常把平滑后的视频级分数和阈值做比较比直接用原始平均分数要稳得多。选median而不是mean还有一个血泪经验之前用均值平滑时一段真实视频里出现两帧误检高峰均值直接把分数拉过了阈值误报了一次换成中位数后两帧孤立高峰被彻底压掉再没出现同类问题。从那以后我每次部署帧级分类模型都强制走一遍滑动窗口平滑先看平滑前后的分数分布差异再定阈值这个习惯帮我挡掉了不少测试集上看不出来的性能陷阱。希望帮到你。本文还有配套的精品资源点击获取