InsightFace 本地人脸替换实践:贴回遮罩、身份增强与姿态保护
技术笔记 · Windows / Python / InsightFace / ONNX Runtime
本文只讨论本地图像处理实现细节,不涉及任何违规用途。请合法合规使用相关技术。
写在前面
InsightFace 的inswapper系列模型,是目前本地做人脸区域替换较常见的方案之一。网上示例大多直接:
result=swapper.get(target_img,target_face,source_face,paste_back=True)能出图,但工程上常会碰到:
- 结果里残留目标人脸的妆容/唇色等局部颜色
- 「源脸身份」不够强,观感仍偏目标脸
- 为了更像源脸,又把源脸裁剪硬贴上去,角度不一致时出现「贴纸感」
- 桌面程序点击停止后界面假死(CUDA/ONNX 推理难以中途强杀)
下面按实现顺序记录一版可落地的处理思路与关键代码。完整工程资料可另行查阅公开站点文档(文末),正文以代码为主。
1. 最小推理骨架
frominsightface.appimportFaceAnalysisimportinsightfaceimportcv2 app=FaceAnalysis(name="buffalo_l",providers=["CUDAExecutionProvider","CPUExecutionProvider"],)app.prepare(ctx_id=0,det_size=(640,640))swapper=insightface.model_zoo.get_model("inswapper_128.onnx",providers=["CUDAExecutionProvider","CPUExecutionProvider"],)src=cv2.imread("source.jpg")tgt=cv2.imread("target.jpg")sf=sorted(app.get(src),key=lambdaf:f.bbox[0])[0]tf=sorted(app.get(tgt),key=lambdaf:f.bbox[0])[0]# 建议先拿模型输出,自己决定贴回策略bgr_fake,M=swapper.get(tgt,tf,sf,paste_back=False)说明:
buffalo_l:检测 + 关键点 + 特征inswapper_128.onnx:在对齐空间生成替换结果- 有 NVIDIA 显卡时优先 CUDA;CPU 也能跑,只是更慢
- 模型建议放到项目本地目录,减少对系统缓存盘的依赖
2. 默认paste_back=True为什么会「漏颜色」
官方贴回逻辑大致是:把生成脸与对齐后的目标脸做差值,差值小的区域继续保留目标图像素。口红、腮红等局部颜色,如果与生成结果接近,就容易被当成「无需替换」留在最终图上。
更稳的做法是用几何脸罩(覆盖眼鼻嘴区域)贴回,而不是只信差值遮罩:
importnumpyasnpimportcv2defpaste_face(target_img,bgr_fake,M):h,w=target_img.shape[:2]size=bgr_fake.shape[0]mask=np.zeros((size,size),np.float32)cv2.ellipse(mask,(size//2,int(size*0.46)),(int(size*0.41),int(size*0.50)),0,0,360,1.0,-1,)# 下巴以下渐隐,减轻脖子接缝y_fade=np.ones(size,np.float32)cut=int(size*0.82)ifcut<size:y_fade[cut:]=np.linspace(1.0,0.0,size-cut)mask*=y_fade[:,None]mask=cv2.GaussianBlur(mask,(7,7),0)IM=cv2.invertAffineTransform(M)fake_w=cv2.warpAffine(bgr_fake,IM,(w,h),borderValue=0.0)mask_w=cv2.warpAffine(mask,IM,(w,h),borderValue=0.0)[...,None]out=mask_w*fake_w+(1.0-mask_w)*target_img.astype(np.float32)returnnp.clip(out,0,255).astype(np.uint8)3. 增强源脸身份,同时保留目标姿态
inswapper输出本身带有目标脸的姿态与光影。若把源脸norm_crop后以过高权重整块混入,证件正脸 × 目标侧脸时,很容易出现「正脸贴纸」。
更稳的策略:
- 用五官点估计左右偏转差,角度差大则降低几何混入上限
- LAB 色度(a/b)向源脸靠,亮度尽量保留模型输出
- 仅混入少量源脸高频细节,不整脸替换几何结构
defyaw_proxy(kps):le,re,nose=kps[0],kps[1],kps[2]mid=(float(le[0])+float(re[0]))*0.5eye_w=abs(float(re[0])-float(le[0]))+1e-6returnabs((float(nose[0])-mid)/eye_w)defpose_mismatch(src_face,tgt_face):return(abs(yaw_proxy(src_face.kps)-yaw_proxy(tgt_face.kps))+max(yaw_proxy(src_face.kps),yaw_proxy(tgt_face.kps))*0.35)defboost_identity(bgr_fake,source_bgr,source_face,target_face,strength=0.75):frominsightface.utilsimportface_align size=bgr_fake.shape[0]src_crop,_=face_align.norm_crop2(source_bgr,np.asarray(source_face.kps,dtype=np.float32),size)# 角度差大 → 几何硬贴上限下降geom_cap=0.42m=pose_mismatch(source_face,target_face)ifm>0.18:geom_cap=0.22ifm>0.32:geom_cap=0.12f_lab=cv2.cvtColor(bgr_fake,cv2.COLOR_BGR2LAB).astype(np.float32)s_lab=cv2.cvtColor(src_crop,cv2.COLOR_BGR2LAB).astype(np.float32)# 在五官权重区域内:a/b 向源脸统计量靠拢(具体 mask 可自建椭圆权重)detail=src_crop.astype(np.float32)-cv2.GaussianBlur(src_crop.astype(np.float32),(0,0),1.35)# base = base + detail * (face_mask * strength * 0.4)# 几何混入必须有硬顶:strength * geom_cap# return mix(src_adj, base, weight=face_mask * strength * geom_cap)参数经验:
- 身份强度常用0.70~0.85,比直接拉满更稳
- 「匹配目标亮度」类后处理默认可关;开太高容易重新偏向目标脸观感
- 输入尽量双方正脸近景、角度接近,比反复堆后处理更有效
4. GUI 停止:用任务代数作废结果
ONNX/CUDA 单次推理通常无法优雅中断。如果界面要等当前步结束才解锁,体验会像卡死。
更友好的做法:
- 点击停止时立刻
cancel_event.set(),并递增任务代数_job_gen - UI 马上恢复可点
- 后台线程结束后若代数已变,丢弃结果不回写界面
importthreading self._job_gen=0self._cancel_event=threading.Event()self._engine_lock=threading.Lock()defstop_job(self):self._cancel_event.set()self._job_gen+=1self.set_busy(False)defworker(self,job_id):withself._engine_lock:ifself._cancel_event.is_set():returnresult=run_pipeline(cancel_event=self._cancel_event)ifself._job_gen!=job_id:return# 已停止,丢弃self.ui_apply(result)换脸主流程里仍建议在检测前后、贴回前后多次检查cancel_event,便于尽快跳出 Python 层循环。
5. PyInstaller 打包后的资源路径
目录模式(onedir)打包后,资源可能在 exe 同级,也可能在_internal。建议统一查找:
frompathlibimportPathimportsysdefresource_path(*parts:str)->Path:ifgetattr(sys,"frozen",False):roots=[Path(sys.executable).resolve().parent]ifhasattr(sys,"_MEIPASS"):roots.append(Path(sys._MEIPASS))else:roots=[Path(__file__).resolve().parent]forrootinroots:p=root.joinpath(*parts)ifp.exists():returnpreturnroots[0].joinpath(*parts)含模型与 CUDA 相关动态库时,体积会较大;发目录包比单文件更省事,也更利于排错。
6. 输入质量检查清单
| 情况 | 建议 |
|---|---|
| 双方正脸、光线接近 | 效果通常最好 |
| 正脸硬贴明显侧脸 | 降低几何混入,或换图 |
| 遮挡/墨镜/面具 | 先换清晰图 |
| 手机屏幕翻拍 | 尽量改用原图文件 |
后处理再强,也补不齐「输入角度差过大」带来的结构矛盾。
7. 小结
本地人脸区域替换可以拆成四层:
- 检测与对齐(InsightFace)
- 生成(InSwapper)
- 贴回策略(完整脸罩,减少颜色泄漏)
- 身份增强(保姿态、限几何、迁色度)
再补上取消任务与打包路径,就接近一个可日常使用的本地小工具了。
参考与延伸
- InsightFace 官方仓库与模型说明
- ONNX Runtime 的 CUDA / CPU Provider 文档
- 若需对照更完整的工程组织方式,可在公开技术站点检索相关实践文档:zhixuanxingqiu.com
请仅将相关技术用于学习、研究与合法授权场景。