三秒变脸:Deep-Live-Cam 初体验
GitHub 93K Star 的开源项目,只需一张照片就能实时换脸。先来看它做了什么,再拆开看看怎么做的。
它到底是什么?
想象你有一个神奇的化妆镜——站在镜子前,你的脸瞬间变成了另一个人。Deep-Live-Cam 就是这个镜子背后的AI 模型:你给它一张照片,它就能把照片里的脸"贴"到你脸上,而且是在实时视频中完成。
单图换脸
只需要一张照片,就能把照片里的人脸替换到视频或摄像头画面中。
实时处理
在摄像头直播中实时换脸,延迟极低,可以用 OBS 推流。
多人换脸
支持同时处理画面中的多张脸,每个人换成不同的人。
安全过滤
内置 NSFW 检测,自动拒绝处理不当内容。
三步上手
就像使用自拍滤镜一样简单——选脸、选摄像头、按 Live。
任何包含清晰人脸的照片都可以,AI 会提取这张脸的"身份信息"
可以选摄像头做实时换脸,也可以选视频文件做后期处理
等待 10-30 秒加载模型,然后就是见证魔法的时刻
项目长什么样?
点击下面的组件来了解项目各部分的作用:
用户界面层
核心处理层
基础设施层
代码解读:程序入口
一切从这里开始。看看 run.py 做了哪些事:
import os, sys
project_root = os.path.dirname(
os.path.abspath(__file__))
os.environ["PATH"] = (
project_root + os.pathsep
+ os.environ.get("PATH", ""))
from modules import core
if __name__ == '__main__':
core.run()
导入操作系统相关的工具库
找到项目根目录的绝对路径
把项目路径加到系统 PATH 里——
这样程序就能找到自带的 ffmpeg 等工具
(空行分隔导入和业务代码)
导入核心模块——真正干活的代码都在 modules/ 里
(空行)
当这个文件被直接运行时(而非被导入),
启动核心流程
入口文件 run.py 只有不到 40 行,真正的逻辑全部在 modules/ 子目录里。这种"入口极简、逻辑分离"的结构让项目维护起来非常清晰——就像公司前台只负责接待,具体业务由各部门处理。
文件地图
了解每个文件的职责,就像认识一个公司里的每个部门:
检验理解
run.py 文件的主要职责是什么?
如果你想修改程序的最大内存限制,应该去哪个文件?
AI 如何认出你的脸
face_analyser.py 是项目的"眼睛"——它用 InsightFace 库找到画面中的人脸,提取关键特征,为后续换脸做准备。
机场安检的类比
想象你在机场过安检。安检人员做三件事:先在人群中找到你(检测),再扫描你的护照(识别身份),如果需要还可能让你摆个姿势拍个照(特征点定位)。face_analyser 做的事一模一样,只不过它的"眼睛"是 AI 模型。
检测:人脸在哪?
识别:这是谁?
定位:五官在哪?
背后的 AI 引擎
项目使用 InsightFace 库,底层运行在 ONNX Runtime 上。
InsightFace 自带的人脸分析套件,包含检测、识别、特征点三个子模型
AI 在这个分辨率下搜索人脸,足够精确又不会太慢
多个线程不会同时初始化模型,避免资源冲突
代码解读:初始化人脸分析器
def get_face_analyser() -> Any:
global FACE_ANALYSER
if FACE_ANALYSER is None:
with FACE_ANALYSER_LOCK:
if FACE_ANALYSER is None:
FACE_ANALYSER = insightface
.app.FaceAnalysis(
name='buffalo_l',
allowed_modules=[
'detection',
'recognition',
'landmark_2d_106'])
定义获取人脸分析器的函数
声明使用全局变量 FACE_ANALYSER
如果分析器还没初始化过...
先加锁——确保只有一个线程能初始化
双重检查:拿到锁后再确认一次
创建 InsightFace 人脸分析器
使用 buffalo_l 模型套件
只加载三个需要的模块:
检测(找到脸在哪)
识别(提取脸的身份特征)
106 个特征点定位(五官精确位置)
这段代码使用了一个经典的并发编程技巧:先快速检查(无锁),再加锁检查(有锁)。这既保证了线程安全,又避免了不必要的锁开销。就像先从窗户看看洗手间有没有人,没人再推门确认。
数据对话:一帧画面的旅程
当一帧画面进入系统,各模块之间是怎么"对话"的?点击按钮观看:
快 vs 精:两种检测模式
实时模式和离线模式对速度的需求完全不同。项目提供了两种检测策略:
快速模式(detect_one_face_fast)
只做检测,跳过识别和特征点。约 10ms/帧。实时直播用这种——不需要知道"这是谁",只需要知道"脸在哪"。
精确模式(get_one_face)
检测 + 识别 + 特征点三合一。约 16ms/帧。离线处理视频时使用,需要完整的身份信息来做映射。
def detect_one_face_fast(frame):
fa = get_face_analyser()
bboxes, kpss = fa.det_model
.detect(frame, max_num=0)
if bboxes.shape[0] == 0:
return None
idx = int(bboxes[:, 0].argmin())
return Face(bbox=bboxes[idx, :4],
kps=kpss[idx])
定义快速人脸检测函数
获取全局人脸分析器(已初始化好的)
只调用检测模型,不碰识别和特征点
扫描画面中所有可能的人脸
如果没检测到任何脸,返回空
选最左边的人脸(argmin)——通常是主目标
返回一个轻量级 Face 对象,只包含
边界框和关键点坐标——够换脸用了
检验理解
实时换脸模式为什么能用"快速检测"跳过识别和特征点?
如果两个线程同时调用 get_face_analyser() 会怎样?
偷天换日:换脸的魔法引擎
face_swapper.py 是整个项目最核心的模块——它使用 inswapper 模型完成"换脸手术",再精细地贴回原画面。
面具制作坊的类比
想象一个顶级的面具制作坊。工匠拿到一张目标脸的照片,然后用源脸的"身份信息"(嵌入向量),制作出一个完美的面具。最后,工匠小心翼翼地把面具贴到目标脸上,边缘要自然过渡,不能看出接缝。
提取源脸身份向量
生成新面孔
精细贴回原画面
后处理(锐化/混合)
inswapper:换脸的核心模型
项目使用的 inswapper 模型是整个换脸过程的核心。它接受两个输入:一张目标脸的图像和源脸的 512 维嵌入向量,输出一张融合后的新面孔。
inswapper_128.onnx
FP32 模型,兼容所有 GPU,精度最高
inswapper_128_fp16.onnx
FP16 半精度模型,显存占用减半,NVIDIA Tensor Core GPU 速度更快
128 x 128
模型输入尺寸——所有脸都先缩放到这个统一大小
512 维向量
源脸的身份特征压缩成 512 个数字
换脸数据流
点击"下一步"追踪一张脸从输入到输出的完整旅程:
代码解读:换脸手术
def swap_face(source_face, target_face,
temp_frame) -> Frame:
face_swapper = get_face_swapper()
bgr_fake, M = face_swapper.get(
temp_frame, target_face,
source_face, paste_back=False)
swapped_frame = _fast_paste_back(
temp_frame, bgr_fake,
_aimg_dummy, M)
return swapped_frame
定义换脸函数,需要三样东西:源脸、目标脸、原始画面
获取已加载的换脸模型
(空行分隔获取模型和使用模型)
让 inswapper 生成"假脸"——融合了源脸身份的新面孔
paste_back=False 表示先不贴回,我们自己来做(更快)
M 是仿射变换矩阵,记录了脸的空间位置关系
用优化的贴回函数把新面孔精确放回原画面
传入原始画面、假脸、辅助信息和位置矩阵
返回处理好的画面
边缘柔化的秘密
直接把生成的脸"硬贴"上去会留下明显的接缝。_fast_paste_back 使用了一个巧妙的技术来解决这个问题:
项目在对齐空间里预先计算了一个边缘羽化模板——中间完全不透明,边缘逐渐透明。这个模板缓存后每帧直接使用,用仿射变换把它"拉伸"到目标脸上。成本从原来跟脸面积的四次方成正比,变成了固定的一次方。
def _get_soft_alpha(size):
if _paste_cache['alpha_size'] != size:
k_erode = max(size // 10, 3)
k_blur = max(size // 20, 3)
mask = np.full((size, size),
255, dtype=np.uint8)
mask = cv2.erode(mask, ...)
mask = cv2.GaussianBlur(mask, ...)
_paste_cache['soft_alpha'] = mask
return _paste_cache['soft_alpha']
定义获取柔化透明度模板的函数
如果缓存尺寸不匹配,重新计算
腐蚀核大小:脸的 1/10(确保边缘有过渡区)
模糊核大小:脸的 1/20(控制羽化柔和程度)
创建全白(完全不透明)的初始模板
让边缘向内收缩一点(腐蚀)
再用高斯模糊柔化边缘(从白渐变到透明)
缓存计算结果,下次直接用
返回缓存的模板
匹配概念
把左侧的换脸概念拖到右侧对应的描述上:
512 个数字组成的"身份指纹",用来告诉 AI 这是哪张脸
数学矩阵,把 128x128 的新面孔精确对齐回画面中的脸部位置
边缘从完全不透明渐变到透明的模板,让贴上去的脸看不出接缝
检验理解
为什么 swap_face 设置 paste_back=False?
预计算羽化模板为什么能大幅提升性能?
实时换脸:从视频到直播
实时换脸比离线处理难得多——每帧只有 16 毫秒的预算。看看项目如何用三线程架构和智能调度达成实时性能。
16 毫秒的挑战
60 帧/秒的视频意味着每帧只有 16.7 毫秒。在这 16 毫秒里,程序需要:读取摄像头画面、检测人脸、运行换脸模型、显示结果。如果任何一步超时,画面就会卡顿。
离线处理视频时,一帧花 100 毫秒也无所谓,大不了整体多等一会儿。但实时模式是"硬性截止"——必须在下一帧到来之前完成,否则就丢帧。这就像限时考试和带回家的作业的区别。
三线程分工协作
解决方案是把工作拆分给三个"工人",让他们同时工作:
只负责从摄像头读帧。用一个大小为 2 的队列,满时就丢掉旧帧。
从采集队列取帧,做人脸检测和换脸。检测不是每帧都做,而是每隔约 80 毫秒检测一次,中间帧复用上一次的检测结果。
主线程用 Tk 的 after() 方法定期取处理好的帧并显示。先缩放到窗口大小再做颜色转换,减少计算量。
帧数据在三个线程间的流转
点击"下一步"看一帧画面如何在三个线程之间流转:
代码解读:智能检测间隔
处理线程中最关键的优化——不是每帧都检测人脸:
det_interval = max(1,
round(camera_fps * 0.08))
det_count += 1
if det_count % det_interval == 0:
if modules.globals.many_faces:
cached_many_faces = (
detect_many_faces_fast(
temp_frame))
else:
cached_target_face = (
detect_one_face_fast(
temp_frame))
计算检测间隔:至少每 1 帧检测一次
公式:帧率 x 0.08秒(约 80ms)。60fps 时每 5 帧检测,30fps 时每 3 帧
(空行)
每处理一帧,计数器加 1
只有到达检测间隔时才做人脸检测
如果是多人模式...
检测所有脸的位置
用快速检测(跳过识别和特征点)
如果只换一个人的脸...
只检测一张脸的位置
同样用快速检测
在 30fps 的视频中,相邻两帧之间只间隔 33ms。一个人的脸在 80ms 内(约 2-3 帧)几乎不会移动太多,所以用旧的检测结果来换脸,视觉上完全看不出差异。这个策略把检测开销降低了 3-5 倍。
VideoCapturer:不只是打开摄像头
video_capture.py 封装了摄像头的复杂操作。它不只是简单地"打开摄像头"——还会自动选择最优后端、测量真实帧率:
def _measure_fps(self, warmup=10,
sample=30,
fallback=30.0):
for _ in range(warmup):
self.cap.read()
t0 = time.perf_counter()
for _ in range(sample):
ret, _ = self.cap.read()
if not ret: return fallback
elapsed = time.perf_counter() - t0
return sample / elapsed
定义实测帧率函数:先读 warmup 帧热身
再读 sample 帧计时
如果测不出来就用默认 30fps
先读 10 帧让摄像头稳定(热身)
记录精确的起始时间
连续读 30 帧
如果读帧失败,返回默认值
计算 30 帧用了多少时间
帧率 = 帧数 / 时间
代码注释写道:"CAP_PROP_FPS 在 DirectShow 上经常撒谎——明明支持 60fps,却报 30fps。"所以项目用实际测量代替信任设备参数。这就像面试时不看简历上的"精通XX",直接让候选人写代码。
找 Bug 挑战
下面是简化版的采集线程代码,有一行可能导致丢帧严重。找出它:
def _capture_thread(cap, queue, stop):
while not stop.is_set():
queue.put_nowait(frame) # 队列满?
ret, frame = cap.read()
if not ret: stop.set()
检验理解
三个线程之间如何传递帧数据?
60fps 摄像头下,人脸检测间隔是多少帧?
性能黑科技:GPU 与优化
从 CUDA Graph 到 CoreML 优化,从内存管道到硬件编码器——项目里藏着一堆让人惊叹的性能技巧。
五大执行提供者
不同硬件需要不同的"驱动"。项目支持五种执行提供者,自动选择最优方案:
CUDA(NVIDIA GPU)
最快选项。支持 CUDA Graph 加速和硬件视频编码(h264_nvenc)。Tensor Core 还能用 FP16 模型。
CoreML(Apple Silicon)
M1-M5 芯片的 Neural Engine + GPU。项目专门做了 ONNX 模型优化和 ANE 分区消除。
DirectML(Windows GPU)
支持 AMD 和 Intel GPU。使用线程锁避免并发问题,配合 AMF 硬件编码器。
OpenVINO(Intel CPU/GPU)
Intel 硬件优化方案,适合没有独立显卡的 Intel 设备。
CPU(通用后备)
任何设备都能跑,但速度最慢。自动设置线程数:CPU 核心数 - 2。
CUDA Graph:GPU 的"录像回放"
正常执行 AI 模型时,CPU 每次都要一个一个地向 GPU 发送指令(内核启动),每次发送都有固定开销。CUDA Graph 的思路是:录制一次,之后无限回放。
def _init_cuda_graph_session(
model_path, swapper):
providers = [(
'CUDAExecutionProvider',
{'enable_cuda_graph': '1'})]
sess = ort.InferenceSession(
model_path, providers=providers)
# Pre-allocate GPU buffers
ort_input = ort.OrtValue
.ortvalue_from_numpy(
dummy_inp, 'cuda', 0)
# First run records the graph
sess.run_with_iobinding(io)
_cuda_graph_session['recorded'] = True
定义初始化 CUDA Graph 会话的函数
接收模型路径和换脸器对象
配置 ONNX Runtime 使用 CUDA
并开启 CUDA Graph 功能
创建推理会话(加载模型到 GPU)
(空行)
预先在 GPU 上分配好内存缓冲区
创建 GPU 端的张量(输入数据的容器)
从 numpy 数组创建,放在 GPU 设备 0 上
(空行)
第一次运行时 CUDA 会'录制'整个执行图
标记为已录制,后续调用直接回放
代码创建了一个 _CudaGraphSessionAdapter 类来包装原始的 ONNX 会话。这样 insightface 的 INSwapper 调用 session.run() 时,实际上走的是 CUDA Graph 回放路径。这种"代理模式"不需要修改 insightface 库的任何代码。
Apple Silicon 专属优化
项目为 Apple M 系列芯片做了大量针对性优化。核心思路:让模型尽可能运行在 Neural Engine (ANE) 上,避免 CPU-ANE 之间的数据往返。
ONNX 模型改写
把 Pad(reflect) 算子替换为 Slice+Concat,让 CoreML 能把整个模型放在一个 ANE 分区里运行。
检测模型走 GPU
人脸检测模型路由到 GPU shader cores,换脸模型走 ANE。两者并行运行,互不干扰。
检测速度 21ms → 4ms
消除 Shape-Gather 动态链条后,RetinaFace FPN 在 M3 Max 上的检测时间从 21ms 降到 4ms。
内存管道:零磁盘 I/O
处理视频时,传统方法是:抽帧存磁盘 → 逐帧处理 → 存回磁盘 → 合成视频。大量时间浪费在磁盘读写上。项目用 FFmpeg 管道实现了全内存处理:
FFmpeg 解码器管道读取视频帧到内存
在内存中逐帧换脸处理
直接写入 FFmpeg 编码器管道
reader_cmd = [
'ffmpeg', '-hide_banner',
'-hwaccel', 'auto',
'-i', target_path,
'-f', 'rawvideo',
'-pix_fmt', 'bgr24',
'-v', 'error',
'-']
构建 FFmpeg 解码命令
静默模式(不显示 banner 信息)
自动选择硬件加速解码
输入文件是目标视频
输出格式为原始视频流
像素格式 BGR24(与 OpenCV 一致)
只显示错误信息
输出到标准输出(管道)
硬件视频编码
视频输出也可以用 GPU 硬件编码,速度远超 CPU 软编码。项目自动检测并选择最佳编码器:
h264_nvenc
NVIDIA GPU 硬件编码,替代 libx264 软编码
hevc_nvenc
NVIDIA GPU H.265 硬件编码,更小体积
h264_amf
AMD/Intel GPU 硬件编码(DirectML 模式)
libx264
通用 CPU 软编码后备方案
代码先尝试硬件编码,失败后自动降级到软件编码,不会崩溃。encoders_to_try 列表里排了两个方案:硬件优先,软件兜底。这种"先试快的,不行用稳的"策略贯穿整个项目。
视频处理中的流水线检测
处理视频文件时,项目使用了"流水线检测"——在处理第 N 帧的同时,提前开始检测第 N+1 帧的人脸。来看这个协作对话: