前言
1、項目簡介
AI換臉是指利用基于深度學(xué)習(xí)和計算機(jī)視覺來替換或合成圖像或視頻中的人臉??梢詫⒁粋€人的臉替換為另一個人的臉,或者將一個人的表情合成到另一個人的照片或視頻中。算法常常被用在娛樂目上,例如在社交媒體上創(chuàng)建有趣的照片或視頻,也有用于電影制作、特效制作、人臉編輯工具和虛擬現(xiàn)實。但也有可能被濫用,用于欺騙、虛假信息傳播或隱私侵犯。
隨著AI換臉技術(shù)的廣泛應(yīng)用,這也引起很多的關(guān)注和擔(dān)憂,因為它可以用于制造虛假的視頻內(nèi)容,可能導(dǎo)致社會和政治問題。AI換臉技術(shù)也會引發(fā)法律和倫理問題,包括隱私問題和身份驗證問題。濫用這些技術(shù)可能導(dǎo)致個人的聲譽(yù)受損,也可能用于欺騙和詐騙。
AI換臉技術(shù)不斷發(fā)展,變得越來越先進(jìn)的同時,也有研究人員和技術(shù)公司努力開發(fā)檢測和防御AI換臉的方法,以應(yīng)對濫用和虛假信息傳播的問題。
這里結(jié)合實現(xiàn)了一些常用的AI換臉技術(shù),從人臉檢測到人臉關(guān)鍵點檢測,再到AI換臉,然后使用算法進(jìn)行人臉修復(fù)和超分,以便大家更好的了解AI換臉這個智能算法,只能全面的理解才能做到更好的防范。
2.項目效果
注:這里使用視頻是Rerender_A_Video這個項目生成的視頻。
3.源碼與環(huán)境配置
源碼地址:https://download.csdn.net/download/matt45m/88395491
模型地址:鏈接:https://pan.baidu.com/s/1ebvB86bvocOUdTCmAp8C8Q 提取碼:3ubq
源碼環(huán)境配置:
conda create -n swapface python=3.10
activate swapface
cd SwapFaceVerify
pip install -r requirements.txt
一、AI換臉預(yù)處理
1.人臉檢測
做人臉相關(guān)處理工作,首先需要獲取圖像或者視頻中的人臉以及坐標(biāo),會用于人臉檢測相關(guān)的算法,人臉檢測算法提供人臉位置的矩形框
。以下是一些常用的人臉檢測算法:
- Viola-Jones算法:Viola-Jones算法是一種經(jīng)典的人臉檢測算法,基于Haar特征和級聯(lián)分類器。它具有快速的檢測速度,通常用于實時應(yīng)用。
- 卷積神經(jīng)網(wǎng)絡(luò)(CNN):深度學(xué)習(xí)中的卷積神經(jīng)網(wǎng)絡(luò)在人臉檢測方面取得了顯著的進(jìn)展。許多現(xiàn)代的人臉檢測器,如MTCNN(多任務(wù)卷積神經(jīng)網(wǎng)絡(luò))和SSD(單發(fā)多框檢測器),使用CNN來檢測人臉。
- HOG特征和支持向量機(jī)(SVM):HOG(方向梯度直方圖)特征通常與SVM分類器結(jié)合使用,用于人臉檢測。這種方法在處理不同光照和尺度條件下的人臉時表現(xiàn)良好。
- 深度學(xué)習(xí)模型:一些基于深度學(xué)習(xí)的人臉檢測器,如YOLO(You Only Look Once)和Faster R-CNN(區(qū)域卷積神經(jīng)網(wǎng)絡(luò)),已經(jīng)被廣泛應(yīng)用于目標(biāo)檢測領(lǐng)域,包括人臉檢測。
- 級聯(lián)分類器:級聯(lián)分類器是一種組合多個弱分類器的方法,以提高人臉檢測的準(zhǔn)確性。這種方法被Viola-Jones算法采用,以及其他一些基于AdaBoost的方法。
-
3D人臉檢測:除了2D人臉檢測,還存在用于檢測和識別3D人臉的算法,這些算法通常使用深度傳感器或多視角攝像頭。
通常輪廓點檢測也被稱為 Face Alignment。本文的AI換臉實現(xiàn)就是建立在68個人臉輪廓點(Landmarks)的基礎(chǔ)之上。而 Face Alignment 必須依賴一個人臉檢測器,即 Face Detection。也就是說,本文所介紹的換臉或者其他精細(xì)化人臉操作的基礎(chǔ)是 Face Detection + Face Alignment。但是,這兩個技術(shù)并不需要我們親自開發(fā),dlib已經(jīng)提供了效果不錯、使用簡便的第三方庫。
2. 人臉關(guān)鍵特征點
人臉圖像中的關(guān)鍵特征點(face-landmark),例如眼睛、鼻子、嘴巴、臉頰等。這些特征點通常用于面部識別、表情分析、面部姿勢估計等應(yīng)用。以下是一些常用的人臉關(guān)鍵點檢測算法:
Dlib:Dlib 是一個流行的 C++ 庫,提供了高效的人臉關(guān)鍵點檢測功能。它使用了一種基于 HOG 特征的級聯(lián)分類器,并使用回歸方法來預(yù)測關(guān)鍵點位置。Dlib 還提供了 Python 綁定,因此可以方便地用于 Python 環(huán)境。
OpenCV:OpenCV 是一個廣泛使用的計算機(jī)視覺庫,它包含了一些用于人臉關(guān)鍵點檢測的功能。OpenCV 的面部關(guān)鍵點檢測器通常基于級聯(lián)分類器和形狀模型。
Face++:Face++ 是一個商業(yè)化的人臉識別平臺,提供了人臉關(guān)鍵點檢測服務(wù)。它使用深度學(xué)習(xí)技術(shù),包括卷積神經(jīng)網(wǎng)絡(luò)(CNN),來檢測和定位面部關(guān)鍵點。
dlib + 預(yù)訓(xùn)練模型:除了 Dlib 庫本身,還可以使用預(yù)訓(xùn)練的深度學(xué)習(xí)模型,例如基于 TensorFlow 或 PyTorch 的模型,來進(jìn)行面部關(guān)鍵點檢測。這些模型通常在大規(guī)模數(shù)據(jù)集上進(jìn)行訓(xùn)練,能夠更準(zhǔn)確地檢測面部關(guān)鍵點。
MediaPipe Face Detection:Google 的 MediaPipe 框架提供了一種用于實時面部關(guān)鍵點檢測的解決方案。它使用了深度學(xué)習(xí)模型來檢測人臉并估計關(guān)鍵點位置,可用于實時應(yīng)用和移動設(shè)備上的部署。
68-Point Facial Landmarks Model:這是一個常見的面部關(guān)鍵點檢測模型,它能夠檢測到臉部的 68 個關(guān)鍵點,包括眼睛、嘴巴、鼻子、臉頰等部位。這種模型通常使用深度學(xué)習(xí)方法構(gòu)建。
二、InsightFace算法
1、算法簡介
InsightFace算法是一種用于人臉分析和識別任務(wù)的深度學(xué)習(xí)模型,它主要側(cè)重于人臉識別和人臉驗證。InsightFace是一個用于2D和3D人臉分析的集成Python庫。 InsightFace 有效地實現(xiàn)了各種最先進(jìn)的人臉識別、人臉檢測和人臉對齊算法,并針對訓(xùn)練和部署進(jìn)行了優(yōu)化。它支持一系列主干架構(gòu),包括 IResNet、RetinaNet、MobileFaceNet、InceptionResNet_v2 和 DenseNet。 除了模型之外,它還可以使用 MS1M、VGG2 和 CASIA-WebFace 等面部數(shù)據(jù)集。InsightFace算法的基本解析:
骨干網(wǎng)絡(luò)(Backbone Networks):InsightFace使用深度卷積神經(jīng)網(wǎng)絡(luò)(CNN)作為骨干網(wǎng)絡(luò),用于從輸入圖像中提取人臉特征。通常情況下,ResNet和MobileNet等網(wǎng)絡(luò)結(jié)構(gòu)被用作骨干網(wǎng)絡(luò),但I(xiàn)nsightFace也支持其他網(wǎng)絡(luò)結(jié)構(gòu)。
ArcFace損失(ArcFace Loss):InsightFace的一個顯著特點是在訓(xùn)練過程中使用ArcFace損失函數(shù)。ArcFace損失函數(shù)旨在增強(qiáng)特征嵌入的區(qū)分能力,使其適用于人臉識別任務(wù)。它通過在嵌入空間中對類別之間引入邊界(margin)來實現(xiàn),從而更好地區(qū)分不同的身份。
有效的訓(xùn)練(Efficient Training):InsightFace以高效的訓(xùn)練過程著稱,包括大規(guī)模人臉識別和數(shù)據(jù)增強(qiáng)等技術(shù)。這些方法有助于提高模型的性能,特別是在處理大規(guī)模數(shù)據(jù)集時。
預(yù)訓(xùn)練模型(Pretrained Models):InsightFace提供了預(yù)訓(xùn)練模型,使開發(fā)人員能夠輕松開始進(jìn)行人臉識別任務(wù),而無需從頭開始訓(xùn)練模型。這些預(yù)訓(xùn)練模型經(jīng)過大規(guī)模的人臉數(shù)據(jù)集訓(xùn)練,可以用于特定應(yīng)用的微調(diào)。
開源(Open Source):InsightFace是一個開源框架,這意味著開發(fā)人員可以訪問其代碼庫,并根據(jù)自己的需求進(jìn)行定制。這導(dǎo)致了一個繁榮的研究和開發(fā)社區(qū),不斷為其發(fā)展做出貢獻(xiàn)。
各種應(yīng)用(Various Applications):InsightFace不僅僅可以用于基本的人臉識別,還可以應(yīng)用于更廣泛的任務(wù),如情感分析、年齡估計、性別分類和人臉屬性識別等。
2. 算法優(yōu)勢
InsightFace具有許多優(yōu)勢,使其在人臉識別和相關(guān)任務(wù)中備受歡迎。以下是InsightFace的主要優(yōu)勢總結(jié):
高精度:InsightFace以其強(qiáng)大的深度學(xué)習(xí)模型和ArcFace損失函數(shù)而聞名,這使得其在人臉識別和人臉驗證任務(wù)中能夠?qū)崿F(xiàn)高精度的識別結(jié)果。
高效性:InsightFace采用了有效的訓(xùn)練技巧,包括大規(guī)模人臉識別和數(shù)據(jù)增強(qiáng),這有助于提高模型的性能并減少訓(xùn)練時間。
預(yù)訓(xùn)練模型:InsightFace提供了預(yù)訓(xùn)練模型,使開發(fā)人員可以快速開始人臉識別項目,無需從頭開始訓(xùn)練模型。這提高了開發(fā)效率。
靈活性:InsightFace是一個開源框架,開發(fā)人員可以根據(jù)自己的需求進(jìn)行自定義。這意味著它可以適應(yīng)各種人臉分析任務(wù)和應(yīng)用領(lǐng)域。
多用途:除了基本的人臉識別和驗證,InsightFace還可以用于其他多種應(yīng)用,如情感分析、年齡估計、性別分類和人臉屬性識別等。
大規(guī)模支持:InsightFace具備處理大規(guī)模數(shù)據(jù)集的能力,因此適用于需要大量訓(xùn)練數(shù)據(jù)的項目,如人臉識別在大型身份數(shù)據(jù)庫中的應(yīng)用。
社區(qū)支持:由于是開源項目,InsightFace有一個積極的社區(qū),不斷貢獻(xiàn)代碼、文檔和改進(jìn),使其保持活躍和更新。
強(qiáng)調(diào)隱私和安全:InsightFace的使用者可以自己加強(qiáng)安全和隱私措施,以確保合規(guī)性和數(shù)據(jù)保護(hù)。
3.算法概述
InsightFace提供用于深度識別的訓(xùn)練數(shù)據(jù),網(wǎng)絡(luò)設(shè)置和損失設(shè)計。 訓(xùn)練數(shù)據(jù)包括標(biāo)準(zhǔn)化的 MS1M,VGG2 和 CASIA-Webface 數(shù)據(jù)集,這些數(shù)據(jù)集已經(jīng)以 MXNet 二進(jìn)制格式打包。 網(wǎng)絡(luò)主干包括 ResNet,MobilefaceNet,MobileNet,InceptionResNet_v2,DenseNet,DPN。 損失函數(shù)包括 Softmax,SphereFace,CosineFace,ArcFace 和 Triplet(Euclidean / Angular)Loss。
InsightFace的方法 ArcFace 最初在 arXiv 技術(shù)報告中描述。 通過使用此存儲庫,可以通過單個模型簡單地實現(xiàn) LFW 99.80%+ 和 Megaface 98%+。
3. 使用InsightFace實現(xiàn)人臉屬性分析
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) #創(chuàng)建人臉數(shù)據(jù)庫
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關(guān)鍵點")
output_3 = gr.Image(label="3D關(guān)鍵點")
demo = gr.Interface(fn=get_face_feature,inputs=input,outputs=[output_1,output_2,output_3])
demo.launch()
顯示結(jié)果:
注:人臉是使用Stable Diffusion的模型生成的。
三、InsightFace換臉
輸入的人臉可是單張或者多張,然后人臉框的X坐標(biāo)順序進(jìn)行替換人臉:
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 - 以逗號分隔的目標(biāo)圖像中要交換的面部索引列表(從左到右),從0開始(-1交換目標(biāo)圖像中的所有面部)。
##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)
## 要轉(zhuǎn)換的圖像中包括人臉
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()
從換完臉之后效果看,所以輸出的換臉效果與正常人臉圖片對比會有嚴(yán)重的割裂感,這里由于輸入的像素值大小在卷積時默認(rèn)為128 * 128, 為了改進(jìn)這個效果,這里可以采用codeFormer進(jìn)行人臉增強(qiáng),使其輸出的圖片正?;?br>
注:人臉是使用Stable Diffusion的模型生成的。
四、CodeFormer人臉修復(fù)算法
1.算法概述
CodeFormer是一個基于Transformer的預(yù)測網(wǎng)絡(luò),它能夠有效地對低質(zhì)量人臉圖像進(jìn)行全局組成和上下文建模,以實現(xiàn)編碼預(yù)測。即使在輸入信息嚴(yán)重缺失的情況下,該網(wǎng)絡(luò)也能夠生成與目標(biāo)人臉極為接近的自然人臉圖像。此外,為了增強(qiáng)對不同類型退化的適應(yīng)性,還引入了一個可控的特征轉(zhuǎn)換模塊,允許在圖像保真度和質(zhì)量之間進(jìn)行靈活的權(quán)衡。
由于使用了帶有豐富先驗信息的代碼簿和整體網(wǎng)絡(luò)的全局建模能力,CodeFormer 在圖像質(zhì)量和保真度方面一直優(yōu)于最先進(jìn)的技術(shù),展現(xiàn)出卓越的退化魯棒性。最后,通過廣泛的合成和真實世界數(shù)據(jù)集實驗結(jié)果,充分驗證了算法的有效性。
低清圖像(LQ)和潛在高清圖像(HQ)之間存在多對多的映射關(guān)系,如下圖所示。這種多解的映射會在網(wǎng)絡(luò)學(xué)習(xí)過程中引發(fā)困惑,導(dǎo)致難以獲得高質(zhì)量的輸出。特別是在輸入信息嚴(yán)重退化的情況下,這種適應(yīng)性問題會更加顯著。其中一個挑戰(zhàn)是如何降低這種映射的不確定性。
CodeForme首先引入了VQGAN的離散碼本空間,以解決前述的兩個問題(1和2)。這個有限且離散的映射空間顯著減少了復(fù)原任務(wù)映射的不穩(wěn)定性(1)。通過VQGAN的自重建訓(xùn)練,碼本先驗保留了豐富的高清人臉紋理信息,有助于在復(fù)原任務(wù)中填充真實的人臉紋理細(xì)節(jié)(2)。
如下圖所示,與連續(xù)先驗空間(d、e)相比,離散碼本空間(f、g)能夠生成更高質(zhì)量的結(jié)果,消除了偽影,同時保持了面部輪廓的完整性,并呈現(xiàn)出更加真實和精細(xì)的紋理。
2.源碼與環(huán)境部署
我這里測試部署的系統(tǒng)win 10, cuda 11.8,cudnn 8.5,GPU是RTX 3060, 8G顯存,使用conda創(chuàng)建虛擬環(huán)境。
環(huán)境安裝:
git clone https://github.com/sczhou/CodeFormer
cd CodeFormer
conda create -n codeformer python=3.8 -y
conda activate codeformer
單獨安裝pytorch,再安裝環(huán)境:
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(人臉驗證)是指通過比對兩張人臉圖像,判斷它們是否屬于同一個人。這種技術(shù)常用于身份驗證、解鎖手機(jī)等場景。在進(jìn)行人臉驗證時,系統(tǒng)會對比兩張圖像中的人臉特征,判斷它們是否匹配。如果匹配,則驗證通過,認(rèn)為是同一個人;如果不匹配,則驗證失敗,認(rèn)為是不同的人。
算法將兩張人臉圖像的特征進(jìn)行比對。這個比對過程通常使用算法來計算特征之間的相似性分?jǐn)?shù)。如果相似性分?jǐn)?shù)達(dá)到一定的閾值,系統(tǒng)就會認(rèn)為兩張圖像屬于同一個人,否則認(rèn)為不是同一個人。
一般求相似度都用余弦相似度(Cosine Similarity),余弦相似度是n維空間中兩個n維向量之間角度的余弦。它等于兩個向量的點積(向量積)除以兩個向量長度(或大?。┑某朔e。
2.人臉識別
人臉識別學(xué)用歐氏距離來衡量人臉特征兩個向量之間的相似度。通常情況下,如果兩個人臉特征向量的歐氏距離足夠小,小于一個預(yù)定的閾值(默認(rèn)是1.24),那么算法會認(rèn)為這兩個人臉圖像屬于同一個人。
3.測試結(jié)果
注:以下所用人臉是使用Stable Diffusion模型生成的。生成人臉底模是:Juggernaut XL這個。
注:人臉是使用Stable Diffusion的模型生成的。
七、視頻換臉
視頻換臉的效果,如果沒有CodeFormer修復(fù)人臉的話,人臉周圍會有虛影,效果并不是很好,下面是視頻換臉的代碼:
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()
視頻效果:
八.總結(jié)與優(yōu)化
1.優(yōu)化
從運行結(jié)果對比來看,如果歐氏距離使用默認(rèn)值1.24,所換的臉都能通過識別算法,但從余弦相似度的結(jié)果來看,使用CodeFormer修復(fù)人臉后,人臉的特征還是有一定的損失。在算法沒有優(yōu)化之前,直接換臉結(jié)果的余弦相似度都在0.8以上。但現(xiàn)在人臉驗證的一般要求余弦相似度要在0.95以,所以如果直接現(xiàn)在的算法是無法通過人臉驗證的算法,除非驗證算法的閾值設(shè)置不合理。我試著去優(yōu)化部分算法,但目前提升并不明顯,如果使用一些盤外招,還是可以沖擊一下95%余弦相似度,這里就不展開講了。文章來源:http://www.zghlxwxcb.cn/news/detail-718652.html
2.升級
在對視頻的處理方面,如果加上CodeFormer人臉修復(fù)的話,不管現(xiàn)在的這種方式,還是目前比較主流的單圖換臉roop,處理都是很慢。如果要考慮實時性,可以按DeepFaceLive優(yōu)化一個版本。文章來源地址http://www.zghlxwxcb.cn/news/detail-718652.html
到了這里,關(guān)于一鍵AI高清換臉——基于InsightFace、CodeFormer實現(xiàn)高清換臉與驗證換臉后效果能否通過人臉比對、人臉識別算法的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!