前言
1、项目简介
AI换脸是指利用基于深度学习和计算机视觉来替换或合成图像或视频中的人脸。可以将一个人的脸替换为另一个人的脸,或者将一个人的表情合成到另一个人的照片或视频中。算法常常被用在娱乐目上,例如在社交媒体上创建有趣的照片或视频,也有用于电影制作、特效制作、人脸编辑工具和虚拟现实。但也有可能被滥用,用于欺骗、虚假信息传播或隐私侵犯。
随着AI换脸技术的广泛应用,这也引起很多的关注和担忧,因为它可以用于制造虚假的视频内容,可能导致社会和政治问题。AI换脸技术也会引发法律和伦理问题,包括隐私问题和身份验证问题。滥用这些技术可能导致个人的声誉受损,也可能用于欺骗和诈骗。
AI换脸技术不断发展,变得越来越先进的同时,也有研究人员和技术公司努力开发检测和防御AI换脸的方法,以应对滥用和虚假信息传播的问题。
这里结合实现了一些常用的AI换脸技术,从人脸检测到人脸关键点检测,再到AI换脸,然后使用算法进行人脸修复和超分,以便大家更好的了解AI换脸这个智能算法,只能全面的理解才能做到更好的防范。
2.项目效果
注:这里使用视频是Rerender_A_Video这个项目生成的视频。
3.源码与环境配置
源码地址:https://download.csdn.net/download/matt45m/88395491
模型地址:链接:https://pan.baidu.com/s/1ebvB86bvocOUdTCmAp8C8Q 提取码:3ubq
源码环境配置:
conda create -n swapface python=3.10
activate swapface
cd SwapFaceVerify
pip install -r requirements.txt
一、AI换脸预处理
1.人脸检测
做人脸相关处理工作,首先需要获取图像或者视频中的人脸以及坐标,会用于人脸检测相关的算法,人脸检测算法提供人脸位置的矩形框
。以下是一些常用的人脸检测算法:
- Viola-Jones算法:Viola-Jones算法是一种经典的人脸检测算法,基于Haar特征和级联分类器。它具有快速的检测速度,通常用于实时应用。
- 卷积神经网络(CNN):深度学习中的卷积神经网络在人脸检测方面取得了显著的进展。许多现代的人脸检测器,如MTCNN(多任务卷积神经网络)和SSD(单发多框检测器),使用CNN来检测人脸。
- HOG特征和支持向量机(SVM):HOG(方向梯度直方图)特征通常与SVM分类器结合使用,用于人脸检测。这种方法在处理不同光照和尺度条件下的人脸时表现良好。
- 深度学习模型:一些基于深度学习的人脸检测器,如YOLO(You Only Look Once)和Faster R-CNN(区域卷积神经网络),已经被广泛应用于目标检测领域,包括人脸检测。
- 级联分类器:级联分类器是一种组合多个弱分类器的方法,以提高人脸检测的准确性。这种方法被Viola-Jones算法采用,以及其他一些基于AdaBoost的方法。
-
3D人脸检测:除了2D人脸检测,还存在用于检测和识别3D人脸的算法,这些算法通常使用深度传感器或多视角摄像头。
通常轮廓点检测也被称为 Face Alignment。本文的AI换脸实现就是建立在68个人脸轮廓点(Landmarks)的基础之上。而 Face Alignment 必须依赖一个人脸检测器,即 Face Detection。也就是说,本文所介绍的换脸或者其他精细化人脸操作的基础是 Face Detection + Face Alignment。但是,这两个技术并不需要我们亲自开发,dlib已经提供了效果不错、使用简便的第三方库。
2. 人脸关键特征点
人脸图像中的关键特征点(face-landmark),例如眼睛、鼻子、嘴巴、脸颊等。这些特征点通常用于面部识别、表情分析、面部姿势估计等应用。以下是一些常用的人脸关键点检测算法:
Dlib:Dlib 是一个流行的 C++ 库,提供了高效的人脸关键点检测功能。它使用了一种基于 HOG 特征的级联分类器,并使用回归方法来预测关键点位置。Dlib 还提供了 Python 绑定,因此可以方便地用于 Python 环境。
OpenCV:OpenCV 是一个广泛使用的计算机视觉库,它包含了一些用于人脸关键点检测的功能。OpenCV 的面部关键点检测器通常基于级联分类器和形状模型。
Face++:Face++ 是一个商业化的人脸识别平台,提供了人脸关键点检测服务。它使用深度学习技术,包括卷积神经网络(CNN),来检测和定位面部关键点。
dlib + 预训练模型:除了 Dlib 库本身,还可以使用预训练的深度学习模型,例如基于 TensorFlow 或 PyTorch 的模型,来进行面部关键点检测。这些模型通常在大规模数据集上进行训练,能够更准确地检测面部关键点。
MediaPipe Face Detection:Google 的 MediaPipe 框架提供了一种用于实时面部关键点检测的解决方案。它使用了深度学习模型来检测人脸并估计关键点位置,可用于实时应用和移动设备上的部署。
68-Point Facial Landmarks Model:这是一个常见的面部关键点检测模型,它能够检测到脸部的 68 个关键点,包括眼睛、嘴巴、鼻子、脸颊等部位。这种模型通常使用深度学习方法构建。
二、InsightFace算法
1、算法简介
InsightFace算法是一种用于人脸分析和识别任务的深度学习模型,它主要侧重于人脸识别和人脸验证。InsightFace是一个用于2D和3D人脸分析的集成Python库。 InsightFace 有效地实现了各种最先进的人脸识别、人脸检测和人脸对齐算法,并针对训练和部署进行了优化。它支持一系列主干架构,包括 IResNet、RetinaNet、MobileFaceNet、InceptionResNet_v2 和 DenseNet。 除了模型之外,它还可以使用 MS1M、VGG2 和 CASIA-WebFace 等面部数据集。InsightFace算法的基本解析:
骨干网络(Backbone Networks):InsightFace使用深度卷积神经网络(CNN)作为骨干网络,用于从输入图像中提取人脸特征。通常情况下,ResNet和MobileNet等网络结构被用作骨干网络,但InsightFace也支持其他网络结构。
ArcFace损失(ArcFace Loss):InsightFace的一个显著特点是在训练过程中使用ArcFace损失函数。ArcFace损失函数旨在增强特征嵌入的区分能力,使其适用于人脸识别任务。它通过在嵌入空间中对类别之间引入边界(margin)来实现,从而更好地区分不同的身份。
有效的训练(Efficient Training):InsightFace以高效的训练过程著称,包括大规模人脸识别和数据增强等技术。这些方法有助于提高模型的性能,特别是在处理大规模数据集时。
预训练模型(Pretrained Models):InsightFace提供了预训练模型,使开发人员能够轻松开始进行人脸识别任务,而无需从头开始训练模型。这些预训练模型经过大规模的人脸数据集训练,可以用于特定应用的微调。
开源(Open Source):InsightFace是一个开源框架,这意味着开发人员可以访问其代码库,并根据自己的需求进行定制。这导致了一个繁荣的研究和开发社区,不断为其发展做出贡献。
各种应用(Various Applications):InsightFace不仅仅可以用于基本的人脸识别,还可以应用于更广泛的任务,如情感分析、年龄估计、性别分类和人脸属性识别等。
2. 算法优势
InsightFace具有许多优势,使其在人脸识别和相关任务中备受欢迎。以下是InsightFace的主要优势总结:
高精度:InsightFace以其强大的深度学习模型和ArcFace损失函数而闻名,这使得其在人脸识别和人脸验证任务中能够实现高精度的识别结果。
高效性:InsightFace采用了有效的训练技巧,包括大规模人脸识别和数据增强,这有助于提高模型的性能并减少训练时间。
预训练模型:InsightFace提供了预训练模型,使开发人员可以快速开始人脸识别项目,无需从头开始训练模型。这提高了开发效率。
灵活性:InsightFace是一个开源框架,开发人员可以根据自己的需求进行自定义。这意味着它可以适应各种人脸分析任务和应用领域。
多用途:除了基本的人脸识别和验证,InsightFace还可以用于其他多种应用,如情感分析、年龄估计、性别分类和人脸属性识别等。
大规模支持:InsightFace具备处理大规模数据集的能力,因此适用于需要大量训练数据的项目,如人脸识别在大型身份数据库中的应用。
社区支持:由于是开源项目,InsightFace有一个积极的社区,不断贡献代码、文档和改进,使其保持活跃和更新。
强调隐私和安全:InsightFace的使用者可以自己加强安全和隐私措施,以确保合规性和数据保护。
3.算法概述
InsightFace提供用于深度识别的训练数据,网络设置和损失设计。 训练数据包括标准化的 MS1M,VGG2 和 CASIA-Webface 数据集,这些数据集已经以 MXNet 二进制格式打包。 网络主干包括 ResNet,MobilefaceNet,MobileNet,InceptionResNet_v2,DenseNet,DPN。 损失函数包括 Softmax,SphereFace,CosineFace,ArcFace 和 Triplet(Euclidean / Angular)Loss。
InsightFace的方法 ArcFace 最初在 arXiv 技术报告中描述。 通过使用此存储库,可以通过单个模型简单地实现 LFW 99.80%+ 和 Megaface 98%+。
3. 使用InsightFace实现人脸属性分析
import os
import cv2
import insightface
from sklearn import preprocessing
import numpy as np
import onnxruntime
from sklearn.metrics.pairwise import cosine_similarity, paired_distances
import gradio as gr
class DisposeFace:
def __init__(self,face_db,gpu_id=0, threshold=1.24,
det_thresh=0.50, det_size=(640, 640)):
self.gpu_id = gpu_id
self.threshold = threshold
self.det_thresh = det_thresh
self.det_size = det_size
self.face_db = face_db
# 初始化模型
providers = onnxruntime.get_available_providers()
self.face_model = insightface.app.FaceAnalysis(
name="buffalo_l", root="checkpoints", allowed_modules=None, providers=providers)
self.face_model.prepare(ctx_id=self.gpu_id, det_thresh=self.det_thresh, det_size=self.det_size)
# 人脸库的人脸特征
self.faces_embedding = list()
# # 加载人脸库中的人脸
self.load_db_faces(self.face_db)
def load_model(self):
return self.face_model
def load_db_faces(self,face_db_path):
if not os.path.exists(face_db_path):
os.makedirs(face_db_path) #创建人脸数据库
for root,dirs,files in os.walk(face_db_path):
for file in files:
input_image = cv2.imdecode(np.fromfile(os.path.join(root, file), dtype=np.uint8), 1)
user_name = file.split(".")[0]
face = self.face_model.get(input_image)[0]
embedding = np.array(face.embedding).reshape((1, -1))
embedding = preprocessing.normalize(embedding)
self.faces_embedding.append({"user_name": user_name,"feature": embedding})
def detect_face(self,cv_src):
faces = self.face_model.get(cv_src)
faces_results = list()
for face in faces:
result = dict()
#获取人脸属性
result["bbox"] = np.array(face.bbox).astype(np.int32).tolist()
result["kps"] = np.array(face.kps).astype(np.int32).tolist()
result["landmark_3d_68"] = np.array(face.landmark_3d_68).astype(np.int32).tolist()
result["landmark_2d_106"] = np.array(face.landmark_2d_106).astype(np.int32).tolist()
result["pose"] = np.array(face.pose).astype(np.int32).tolist()
result["age"] = face.age
gender = 'man'
if face.gender == 0:
gender = 'female'
result["gender"] = gender
#人脸识别
embedding = np.array(face.embedding).reshape((1, -1))
embedding = preprocessing.normalize(embedding)
result["embedding"] = embedding
faces_results.append(result)
return faces_results
@staticmethod
def feature_compare(feature1, feature2, threshold):
diff = np.subtract(feature1, feature2)
dist = np.sum(np.square(diff), 1)
if dist < threshold:
return True
else:
return False
def face_recognition(self,cv_src):
faces = self.face_model.get(cv_src)
face_results = list()
for face in faces:
# 开始人脸识别
embedding = np.array(face.embedding).reshape((1, -1))
embedding = preprocessing.normalize(embedding)
user_name = "unknown"
for com_face in self.faces_embedding:
result = self.feature_compare(embedding,com_face["feature"],self.threshold)
if result:
user_name = com_face["user_name"]
face_results.append(user_name)
return face_results
def recognition(self,cv_face1,cv_face2):
faces1 = self.face_model.get(cv_face1)
faces2 = self.face_model.get(cv_face2)
if len(faces1) != 1 and len(faces2) != 1:
return "No face detected or multiple faces detected!"
embedding1 = np.array(faces1[0].embedding).reshape((1, -1))
embedding1 = preprocessing.normalize(embedding1)
embedding2 = np.array(faces2[0].embedding).reshape((1, -1))
embedding2 = preprocessing.normalize(embedding2)
return self.feature_compare(embedding1,embedding2,self.threshold)
def face_similarity(self,cv_face1,cv_face2):
faces1 = self.face_model.get(cv_face1)
faces2 = self.face_model.get(cv_face2)
if len(faces1) != 1 and len(faces2) != 1:
return "No face detected or multiple faces detected!"
embedding1 = np.array(faces1[0].embedding).reshape((1, -1))
embedding1 = preprocessing.normalize(embedding1)
embedding2 = np.array(faces2[0].embedding).reshape((1, -1))
embedding2 = preprocessing.normalize(embedding2)
return cosine_similarity(embedding1,embedding2)
#注册人脸
def face_register(self,cv_face,user_name):
faces = self.face_model.get(cv_face)
if len(faces) != 1:
return "No face detected or multiple faces detected!"
embedding = np.array(faces[0].embedding).reshape((1, -1))
embedding = preprocessing.normalize(embedding)
is_exits = False
for com_face in self.faces_embedding:
r = self.feature_compare(embedding, com_face["feature"], self.threshold)
if r:
is_exits = True
if is_exits:
return "The user already exists. You do not need to re-register the user!"
cv2.imencode('.png', cv_face)[1].tofile(os.path.join(self.face_db, '%s.png' % user_name))
self.faces_embedding.append({"user_name": user_name,"feature": embedding})
return "Register face success!"
face_dis = DisposeFace("face_database")
def get_face_feature(image):
# image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
img_3d_point = image.copy()
img_2d_point = image.copy()
results = face_dis.detect_face(image)
for result in results:
color = list(np.random.choice(range(256), size=3))
cv2.rectangle(image, (result["bbox"][0], result["bbox"][1]), (result["bbox"][2], result["bbox"][3]),
(int(color[0]), int(color[1]), int(color[2])))
age = 'age:{}'.format(result["age"])
gender = 'gender:{}'.format(result["gender"])
cv2.putText(image, age, (result["bbox"][0], result["bbox"][1]), cv2.FONT_HERSHEY_COMPLEX, 0.6, (100, 200, 200), 1)
cv2.putText(image, gender, (result["bbox"][0], result["bbox"][1] - 20), cv2.FONT_HERSHEY_COMPLEX, 0.6,
(100, 200, 200), 1)
for p in result["kps"]:
cv2.circle(image, (int(p[0]), int(p[1])), 0, (255, 0, 255), 4)
for p3 in result["landmark_3d_68"]:
cv2.circle(img_3d_point, (int(p3[0]), int(p3[1])), 0, (0, 255, 255), 4)
for p2 in result["landmark_2d_106"]:
cv2.circle(img_2d_point, (int(p2[0]), int(p2[1])), 0, (255, 255, 0), 4)
return image,img_2d_point,img_3d_point
input = gr.Image(label="请输入人脸图像")
output_1 = gr.Image(label="年龄与性别")
output_2 = gr.Image(label="2D关键点")
output_3 = gr.Image(label="3D关键点")
demo = gr.Interface(fn=get_face_feature,inputs=input,outputs=[output_1,output_2,output_3])
demo.launch()
显示结果:
注:人脸是使用Stable Diffusion的模型生成的。
三、InsightFace换脸
输入的人脸可是单张或者多张,然后人脸框的X坐标顺序进行替换人脸:
class InsightFaceSwap():
def __init__(self,in_model_path:str,code_former_index:int,codeformer_model_path:str,face_analyser):
self.model_path = in_model_path
self.codeformer_model_path = codeformer_model_path
self.code_former_index = code_former_index
self.item_dir = os.path.abspath(os.path.dirname(__file__))
self.face_analyser = face_analyser
# providers = onnxruntime.get_available_providers()
# self.face_analyser = insightface.app.FaceAnalysis(
# name="buffalo_l", root=os.path.join(item_path,"checkpoints"),allowed_modules=None,providers=providers)
# self.face_analyser.prepare(ctx_id=self.gpu_id, det_thresh=self.det_thresh, det_size=self.det_size)
model_path = os.path.join(item_path, in_model_path)
self.face_swapper = insightface.model_zoo.get_model(model_path)
if code_former_index == 1:
check_ckpts()
self.upsampler = set_realesrgan()
self.device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")
self.codeformer_net = ARCH_REGISTRY.get("CodeFormer")(dim_embd=512,codebook_size=1024,n_head=8,n_layers=9,
connect_list=["32", "64", "128", "256"],).to(self.device)
checkpoint = torch.load(codeformer_model_path)["params_ema"]
self.codeformer_net.load_state_dict(checkpoint)
self.codeformer_net.eval()
def get_many_faces(self,frame:np.ndarray):
try:
face = self.face_analyser.get(frame)
return sorted(face, key=lambda x: x.bbox[0])
except IndexError:
return None
##target_indexes - 以逗号分隔的目标图像中要交换的面部索引列表(从左到右),从0开始(-1交换目标图像中的所有面部)。
##source_indexes - 以逗号分隔的源图像中要使用的面部索引列表(从左到右),从0开始(-1使用源图像中的所有面部)。
def process(self,face_imgs,src_img,source_indexes,target_indexes):
target_img = cv2.cvtColor(np.array(src_img), cv2.COLOR_RGB2BGR)
target_faces = self.get_many_faces(target_img)
num_target_faces = len(target_faces)
num_source_images = len(face_imgs)
## 要转换的图像中包括人脸
if target_faces is not None:
temp_frame = copy.deepcopy(target_img)
if isinstance(face_imgs, list) and num_source_images == num_target_faces:
print("Replacing faces in target image from the left to the right by order")
for i in range(num_target_faces):
source_faces = self.get_many_faces(cv2.cvtColor(np.array(face_imgs[i]), cv2.COLOR_RGB2BGR))
source_index = i
target_index = i
if source_faces is None:
raise Exception("No source faces found!")
temp_frame = self.swap_face(source_faces,target_faces,source_index,target_index,temp_frame)
elif num_source_images == 1:
# detect source faces that will be replaced into the target image
source_faces = self.get_many_faces(cv2.cvtColor(np.array(face_imgs[0]), cv2.COLOR_RGB2BGR))
num_source_faces = len(source_faces)
if source_faces is None:
raise Exception("No source faces found!")
if target_indexes == "-1":
if num_source_faces == 1:
# print("Replacing all faces in target image with the same face from the source image")
num_iterations = num_target_faces
elif num_source_faces < num_target_faces:
# print("There are less faces in the source image than the target image, replacing as many as we can")
num_iterations = num_source_faces
elif num_target_faces < num_source_faces:
# print("There are less faces in the target image than the source image, replacing as many as we can")
num_iterations = num_target_faces
else:
# print("Replacing all faces in the target image with the faces from the source image")
num_iterations = num_target_faces
for i in range(num_iterations):
source_index = 0 if num_source_faces == 1 else i
target_index = i
temp_frame = self.swap_face(source_faces,target_faces,source_index,target_index,temp_frame)
else:
# print("Replacing specific face(s) in the target image with specific face(s) from the source image")
if source_indexes == "-1":
source_indexes = ','.join(map(lambda x: str(x), range(num_source_faces)))
if target_indexes == "-1":
target_indexes = ','.join(map(lambda x: str(x), range(num_target_faces)))
source_indexes = source_indexes.split(',')
target_indexes = target_indexes.split(',')
num_source_faces_to_swap = len(source_indexes)
num_target_faces_to_swap = len(target_indexes)
if num_source_faces_to_swap > num_source_faces:
raise Exception(
"Number of source indexes is greater than the number of faces in the source image")
if num_target_faces_to_swap > num_target_faces:
raise Exception(
"Number of target indexes is greater than the number of faces in the target image")
if num_source_faces_to_swap > num_target_faces_to_swap:
num_iterations = num_source_faces_to_swap
else:
num_iterations = num_target_faces_to_swap
if num_source_faces_to_swap == num_target_faces_to_swap:
for index in range(num_iterations):
source_index = int(source_indexes[index])
target_index = int(target_indexes[index])
if source_index > num_source_faces - 1:
raise ValueError(
f"Source index {source_index} is higher than the number of faces in the source image")
if target_index > num_target_faces - 1:
raise ValueError(
f"Target index {target_index} is higher than the number of faces in the target image")
temp_frame = self.swap_face(source_faces,target_faces,source_index,target_index,temp_frame)
else:
raise Exception("Unsupported face configuration")
result = temp_frame
else:
print("No target faces found!")
if self.code_former_index == 1:
cf_result = face_restoration(result,True,
True,1,0.5,
self.upsampler,
self.codeformer_net,
self.device)
cf_im_result = Image.fromarray(cf_result)
dis_result = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
else:
cf_im_result = Image.fromarray(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
dis_result = cf_im_result
return dis_result,cf_im_result
def process_video(self, face_imgs, input_video_path, output_video_path,source_indexes, target_indexes):
cap = cv2.VideoCapture(input_video_path)
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
out = cv2.VideoWriter(output_video_path, fourcc, 25, (frame_width, frame_height))
while(True):
ret,frame = cap.read()
if ret == True:
image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
result_image = self.process(face_imgs,image,source_indexes,target_indexes)
img = cv2.cvtColor(np.asarray(result_image), cv2.COLOR_RGB2BGR)
out.write(img)
else:
break
cap.release()
out.release()
def swap_face(self,source_faces,target_faces,source_index,target_index,temp_frame):
source_face = source_faces[source_index]
target_face = target_faces[target_index]
return self.face_swapper.get(temp_frame, target_face, source_face, paste_back=True)
def process_img(self,face_imgs,src_img,output_path,source_indexes,target_indexes):
img = self.process(face_imgs,src_img,source_indexes,target_indexes)
img.save(output_path)
def codeformer_video(self,input_video_path,output_video_path):
cap = cv2.VideoCapture(input_video_path)
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
out = cv2.VideoWriter(output_video_path, fourcc, 25, (frame_width, frame_height))
while (True):
ret, frame = cap.read()
if ret == True:
# image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
cf_result = face_restoration(frame, True,
True, 1, 0.5,
self.upsampler,
self.codeformer_net,
self.device)
img = cv2.cvtColor(cf_result, cv2.COLOR_RGB2BGR)
out.write(img)
else:
break
cap.release()
out.release()
从换完脸之后效果看,所以输出的换脸效果与正常人脸图片对比会有严重的割裂感,这里由于输入的像素值大小在卷积时默认为128 * 128, 为了改进这个效果,这里可以采用codeFormer进行人脸增强,使其输出的图片正常化。
注:人脸是使用Stable Diffusion的模型生成的。
四、CodeFormer人脸修复算法
1.算法概述
CodeFormer是一个基于Transformer的预测网络,它能够有效地对低质量人脸图像进行全局组成和上下文建模,以实现编码预测。即使在输入信息严重缺失的情况下,该网络也能够生成与目标人脸极为接近的自然人脸图像。此外,为了增强对不同类型退化的适应性,还引入了一个可控的特征转换模块,允许在图像保真度和质量之间进行灵活的权衡。
由于使用了带有丰富先验信息的代码簿和整体网络的全局建模能力,CodeFormer 在图像质量和保真度方面一直优于最先进的技术,展现出卓越的退化鲁棒性。最后,通过广泛的合成和真实世界数据集实验结果,充分验证了算法的有效性。
低清图像(LQ)和潜在高清图像(HQ)之间存在多对多的映射关系,如下图所示。这种多解的映射会在网络学习过程中引发困惑,导致难以获得高质量的输出。特别是在输入信息严重退化的情况下,这种适应性问题会更加显著。其中一个挑战是如何降低这种映射的不确定性。
CodeForme首先引入了VQGAN的离散码本空间,以解决前述的两个问题(1和2)。这个有限且离散的映射空间显著减少了复原任务映射的不稳定性(1)。通过VQGAN的自重建训练,码本先验保留了丰富的高清人脸纹理信息,有助于在复原任务中填充真实的人脸纹理细节(2)。
如下图所示,与连续先验空间(d、e)相比,离散码本空间(f、g)能够生成更高质量的结果,消除了伪影,同时保持了面部轮廓的完整性,并呈现出更加真实和精细的纹理。
2.源码与环境部署
我这里测试部署的系统win 10, cuda 11.8,cudnn 8.5,GPU是RTX 3060, 8G显存,使用conda创建虚拟环境。
环境安装:
git clone https://github.com/sczhou/CodeFormer
cd CodeFormer
conda create -n codeformer python=3.8 -y
conda activate codeformer
单独安装pytorch,再安装环境:
conda install pytorch2.0.0 torchvision0.15.0 torchaudio==2.0.0 pytorch-cuda=11.8 -c pytorch -c nvidia
pip install -r requirements.txt
python basicsr/setup.py develop
conda install -c conda-forge dlib
测试代码:
import os
import cv2
import torch
from torchvision.transforms.functional import normalize
from PIL import Image
from basicsr.utils import img2tensor, tensor2img
from basicsr.utils.download_util import load_file_from_url
from facelib.utils.face_restoration_helper import FaceRestoreHelper
from facelib.utils.misc import is_gray
from basicsr.archs.rrdbnet_arch import RRDBNet
from basicsr.utils.realesrgan_utils import RealESRGANer
import gradio as gr
from basicsr.utils.registry import ARCH_REGISTRY
import numpy as np
import sys
item_path = os.path.abspath(os.path.dirname(__file__))
cf_path = "./CodeFormer"
cf_dir = os.path.join(item_path, cf_path)
sys.path.append(cf_dir)
def check_ckpts():
pretrain_model_url = {
'codeformer': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth',
'detection': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/detection_Resnet50_Final.pth',
'parsing': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/parsing_parsenet.pth',
'realesrgan': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/RealESRGAN_x2plus.pth'
}
# download weights
cf_model_path = 'checkpoints/codeformer.pth'
cf_model_dir = os.path.join(item_path, cf_model_path)
dr_model_path = 'checkpoints/detection_Resnet50_Final.pth'
dr_model_dir = os.path.join(item_path, dr_model_path)
pp_model_path = 'checkpoints/parsing_parsenet.pth'
pp_model_dir = os.path.join(item_path, pp_model_path)
re_model_path = 'checkpoints/RealESRGAN_x2plus.pth'
re_model_dir = os.path.join(item_path, re_model_path)
if not os.path.exists(cf_model_dir):
load_file_from_url(url=pretrain_model_url['codeformer'], model_dir=os.path.join(item_path, '/checkpoints'), progress=True, file_name=None)
if not os.path.exists(dr_model_dir):
load_file_from_url(url=pretrain_model_url['detection'], model_dir=os.path.join(item_path, '/checkpoints'), progress=True, file_name=None)
if not os.path.exists(pp_model_dir):
load_file_from_url(url=pretrain_model_url['parsing'], model_dir=os.path.join(item_path, '/checkpoints'), progress=True, file_name=None)
if not os.path.exists(re_model_dir):
load_file_from_url(url=pretrain_model_url['realesrgan'], model_dir=os.path.join(item_path, '/checkpoints'), progress=True, file_name=None)
# set enhancer with RealESRGAN
def set_realesrgan():
half = True if torch.cuda.is_available() else False
model = RRDBNet(
num_in_ch=3,
num_out_ch=3,
num_feat=64,
num_block=23,
num_grow_ch=32,
scale=2,
)
re_model_path = 'checkpoints/RealESRGAN_x2plus.pth'
re_model_dir = os.path.join(item_path, re_model_path)
upsampler = RealESRGANer(
scale=2,
model_path= re_model_dir,
model=model,
tile=400,
tile_pad=40,
pre_pad=0,
half=half,
)
return upsampler
def face_restoration(img, background_enhance, face_upsample, upscale, codeformer_fidelity, upsampler, codeformer_net, device):
"""Run a single prediction on the model"""
try: # global try
# take the default setting for the demo
has_aligned = False
only_center_face = False
draw_box = False
detection_model = "retinaface_resnet50"
background_enhance = background_enhance if background_enhance is not None else True
face_upsample = face_upsample if face_upsample is not None else True
upscale = upscale if (upscale is not None and upscale > 0) else 2
upscale = int(upscale) # convert type to int
if upscale > 4: # avoid memory exceeded due to too large upscale
upscale = 4
if upscale > 2 and max(img.shape[:2])>1000: # avoid memory exceeded due to too large img resolution
upscale = 2
if max(img.shape[:2]) > 1500: # avoid memory exceeded due to too large img resolution
upscale = 1
background_enhance = False
face_upsample = False
face_helper = FaceRestoreHelper(
upscale,
face_size=512,
crop_ratio=(1, 1),
det_model=detection_model,
save_ext="png",
use_parse=True,
)
bg_upsampler = upsampler if background_enhance else None
face_upsampler = upsampler if face_upsample else None
if has_aligned:
# the input faces are already cropped and aligned
img = cv2.resize(img, (512, 512), interpolation=cv2.INTER_LINEAR)
face_helper.is_gray = is_gray(img, threshold=5)
face_helper.cropped_faces = [img]
else:
face_helper.read_image(img)
# get face landmarks for each face
num_det_faces = face_helper.get_face_landmarks_5(
only_center_face=only_center_face, resize=640, eye_dist_threshold=5
)
# align and warp each face
face_helper.align_warp_face()
# face restoration for each cropped face
for idx, cropped_face in enumerate(face_helper.cropped_faces):
# prepare data
cropped_face_t = img2tensor(
cropped_face / 255.0, bgr2rgb=True, float32=True
)
normalize(cropped_face_t, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True)
cropped_face_t = cropped_face_t.unsqueeze(0).to(device)
try:
with torch.no_grad():
output = codeformer_net(
cropped_face_t, w=codeformer_fidelity, adain=True
)[0]
restored_face = tensor2img(output, rgb2bgr=True, min_max=(-1, 1))
del output
torch.cuda.empty_cache()
except RuntimeError as error:
print(f"Failed inference for CodeFormer: {error}")
restored_face = tensor2img(
cropped_face_t, rgb2bgr=True, min_max=(-1, 1)
)
restored_face = restored_face.astype("uint8")
face_helper.add_restored_face(restored_face)
# paste_back
if not has_aligned:
# upsample the background
if bg_upsampler is not None:
# Now only support RealESRGAN for upsampling background
bg_img = bg_upsampler.enhance(img, outscale=upscale)[0]
else:
bg_img = None
face_helper.get_inverse_affine(None)
# paste each restored face to the input image
if face_upsample and face_upsampler is not None:
restored_img = face_helper.paste_faces_to_input_image(
upsample_img=bg_img,
draw_box=draw_box,
face_upsampler=face_upsampler,
)
else:
restored_img = face_helper.paste_faces_to_input_image(
upsample_img=bg_img, draw_box=draw_box
)
restored_img = cv2.cvtColor(restored_img, cv2.COLOR_BGR2RGB)
return restored_img
except Exception as error:
print('Global exception', error)
return None, None
codeformer_model_path = "checkpoints/codeformer.pth"
def cf_face_demo(image):
target_img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
check_ckpts()
upsampler = set_realesrgan()
device = torch.device(
"mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")
codeformer_net = ARCH_REGISTRY.get("CodeFormer")(dim_embd=512, codebook_size=1024, n_head=8, n_layers=9,
connect_list=["32", "64", "128", "256"], ).to(device)
checkpoint = torch.load(codeformer_model_path)["params_ema"]
codeformer_net.load_state_dict(checkpoint)
codeformer_net.eval()
cf_result = face_restoration(target_img, True,
True, 1, 0.5,
upsampler,
codeformer_net,
device)
return cf_result
if __name__ == "__main__" :
input = gr.Image(label="请输入人脸图像")
output_1 = gr.Image(label="人脸超分效果")
demo = gr.Interface(fn=cf_face_demo,inputs=input,outputs=output_1)
demo.launch()
六、人脸验证与识别
1.人脸验证
Face verification(人脸验证)是指通过比对两张人脸图像,判断它们是否属于同一个人。这种技术常用于身份验证、解锁手机等场景。在进行人脸验证时,系统会对比两张图像中的人脸特征,判断它们是否匹配。如果匹配,则验证通过,认为是同一个人;如果不匹配,则验证失败,认为是不同的人。
算法将两张人脸图像的特征进行比对。这个比对过程通常使用算法来计算特征之间的相似性分数。如果相似性分数达到一定的阈值,系统就会认为两张图像属于同一个人,否则认为不是同一个人。
一般求相似度都用余弦相似度(Cosine Similarity),余弦相似度是n维空间中两个n维向量之间角度的余弦。它等于两个向量的点积(向量积)除以两个向量长度(或大小)的乘积。
2.人脸识别
人脸识别学用欧氏距离来衡量人脸特征两个向量之间的相似度。通常情况下,如果两个人脸特征向量的欧氏距离足够小,小于一个预定的阈值(默认是1.24),那么算法会认为这两个人脸图像属于同一个人。
3.测试结果
注:以下所用人脸是使用Stable Diffusion模型生成的。生成人脸底模是:Juggernaut XL这个。
注:人脸是使用Stable Diffusion的模型生成的。
七、视频换脸
视频换脸的效果,如果没有CodeFormer修复人脸的话,人脸周围会有虚影,效果并不是很好,下面是视频换脸的代码:
def process_video(self, face_imgs, input_video_path, output_video_path,source_indexes, target_indexes):
cap = cv2.VideoCapture(input_video_path)
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
out = cv2.VideoWriter(output_video_path, fourcc, 25, (frame_width, frame_height))
while(True):
ret,frame = cap.read()
if ret == True:
image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
dis_result, _ = self.process(face_imgs,image,source_indexes,target_indexes)
img = cv2.cvtColor(np.asarray(dis_result), cv2.COLOR_RGB2BGR)
out.write(img)
else:
break
cap.release()
out.release()
视频效果:
八.总结与优化
1.优化
从运行结果对比来看,如果欧氏距离使用默认值1.24,所换的脸都能通过识别算法,但从余弦相似度的结果来看,使用CodeFormer修复人脸后,人脸的特征还是有一定的损失。在算法没有优化之前,直接换脸结果的余弦相似度都在0.8以上。但现在人脸验证的一般要求余弦相似度要在0.95以,所以如果直接现在的算法是无法通过人脸验证的算法,除非验证算法的阈值设置不合理。我试着去优化部分算法,但目前提升并不明显,如果使用一些盘外招,还是可以冲击一下95%余弦相似度,这里就不展开讲了。文章来源:https://www.toymoban.com/news/detail-718652.html
2.升级
在对视频的处理方面,如果加上CodeFormer人脸修复的话,不管现在的这种方式,还是目前比较主流的单图换脸roop,处理都是很慢。如果要考虑实时性,可以按DeepFaceLive优化一个版本。文章来源地址https://www.toymoban.com/news/detail-718652.html
到了这里,关于一键AI高清换脸——基于InsightFace、CodeFormer实现高清换脸与验证换脸后效果能否通过人脸比对、人脸识别算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!