用Python实现一个简单的智能换脸软件
智能換臉軟件
基本信息介紹
軟件名稱
軟件名稱是Picture Faceswap,表示圖片換臉,是一款圖片換臉軟件。
軟件功能
已知一幅A的人臉圖像,新輸入一張B的人臉圖像,按下換臉鍵后,將A的圖像自動地換成B的人臉。
項目地址
項目地址
軟件使用說明
軟件運行依賴庫
OpenCV、dlib、PyQt5、numpy、PIL等庫
軟件使用步驟
運行指令:python main.py
界面
項目地址
效果
測試結果1:
測試結果2:
測試結果3:
關鍵程序和算法
其中1、2為人臉圖像檢測部分,3、4、5、6、7、8為人臉圖像轉換部分
1.獲取圖像中的面部特征點:
# 提取圖像中的面部特征點,將圖像轉換為一個矩陣數組,矩陣中每個元素對應每一行的一個x,y坐標 def acquire_landmarks(image):# 首先從dlib庫中獲取一個人臉檢測器detector = dlib.get_frontal_face_detector()# 然后利用這個人臉檢測器對圖像畫人臉框,返回的是一個人臉檢測矩形框的4點坐標faces = detector(image, 1)# 如果檢測器檢測到人臉的個數為0,說明圖像沒有人臉,拋出一個異常if len(faces) == 0:raise ZeroFaces# 如果檢測器檢測到人臉的個數大于1,說明圖像中不止一個人臉,拋出一個異常if len(faces) > 1:raise MoreThanOneFaces# 從dlib庫中通過已經預訓練好的關鍵點模型,返回一個人臉關鍵點預測器,用來標記人臉關鍵點predictor = dlib.shape_predictor("./predictor.dat")# 定位人臉關鍵點,faces[0]是開始內部形狀預測的邊界框,最后返回關鍵點的位置shape = predictor(image, faces[0])matrix = numpy.matrix([[point.x, point.y] for point in shape.parts()])return matrix首先可以從dlib庫中下載一個預訓練模型,以此構建特征提取器,dlib庫中獲取的人臉檢測器能夠獲取人臉的一個邊界框矩形列表,每個矩形列表代表一個人臉,并作為特征提取器的輸入最后返回特征點矩陣,每個特征點對應圖像的一個x,y坐標。
2.獲取人臉遮罩:
# 得到人臉遮罩 def acquire_shade(image, landmarks):shape = image.shape[:2]image = numpy.zeros(shape, numpy.float64)# 獲取dlib庫中識別眉毛和眼睛的識別點區域brow_and_eye_points = list(range(17, 48))# 獲取dlib庫中識別鼻子和嘴巴的識別點區域nose_and_mouth_points = list(range(27, 35)) + list(range(48, 61))# 利用OpenCV庫中convexHull函數得到眉毛和眼睛的凸包,從而得到眉毛和眼睛的輪廓brow_and_eye_landmarks = cv2.convexHull(landmarks[brow_and_eye_points])# 得到鼻子和嘴巴的凸包,從而得到鼻子和嘴巴的輪廓nose_and_mouth_landmarks = cv2.convexHull(landmarks[nose_and_mouth_points])# 填充眉毛和眼睛、鼻子和嘴巴的輪廓cv2.fillConvexPoly(image, brow_and_eye_landmarks, 1)cv2.fillConvexPoly(image, nose_and_mouth_landmarks, 1)# 將矩陣轉換為圖像表示image = numpy.array([image, image, image]).transpose((1, 2, 0))# 使用高斯濾波對圖像進行降噪處理plume_amount = 11# 畫出的兩個區域的輪廓向遮罩的邊緣外部羽化擴展plume_amount個像素,可以隱藏不連續的區域image = (cv2.GaussianBlur(image, (plume_amount, plume_amount), 0) > 0) * 1.0return cv2.GaussianBlur(image, (plume_amount, plume_amount), 0)在用dlib的庫檢測人臉的特征點時,可以得到一個由68個點組成的映射,將這些映射組合起來,可以識別人臉的不同的部位:
可以看到,其中:
- 點42~47對應左眼;
- 點36~41對應右眼;
- 點22~26對應左眼眉毛;
- 點17~21對應右眼眉毛;
- 點27~34對應鼻子;
- 點48~60對應嘴巴。
那么,遮罩要用到的區域由兩個區域組成,分別是由眉毛和眼睛組成的區域,以及鼻子和嘴巴組成的區域,識別區域分別為點17 ~ 47和點27 ~ 34、48 ~ 60,最后識別出的區域由這兩個區域構成,點為17 ~ 60。
3.獲取仿射變換矩陣:
# 獲取仿射變換矩陣 def acquire_aff_tra_matrix(head_landmarks, face_landmarks):# 獲取要轉換的識別點區域head_landmarks = head_landmarks[list(range(17, 61))]face_landmarks = face_landmarks[list(range(17, 61))]# 先將獲取到的頭部特征點矩陣的數字類型轉換為浮點數,以得到更精確的列均值head_landmarks = head_landmarks.astype(numpy.float64)# 對矩陣的各列求均值,得到各列均值,即矩心col_aver1 = numpy.mean(head_landmarks, 0)# 按照普式分析法,各列減去矩心head_landmarks = head_landmarks - col_aver1# 對矩陣的各列求標準差,得到各列的標準差SD1 = numpy.std(head_landmarks)# 各列除以標準差,按照標準差縮放,減少了有問題的組件的縮放偏差head_landmarks = head_landmarks / SD1# 獲取到的臉部特征點矩陣也按照上面的過程處理face_landmarks = face_landmarks.astype(numpy.float64)col_aver2 = numpy.mean(face_landmarks, 0)face_landmarks = face_landmarks - col_aver2SD2 = numpy.std(face_landmarks)face_landmarks = face_landmarks / SD2col_aver1 = col_aver1.Tcol_aver2 = col_aver2.TSD_div = SD2 / SD1 * 1.0# 使用奇異值分解計算旋轉部分,返回三個矩陣,分別為左奇異值、奇異值、右奇異值head_landmarks_tra = head_landmarks.Tu, s, vh = numpy.linalg.svd(head_landmarks_tra * face_landmarks)# 公式假設矩陣在右邊,有行向量,解決方案要求矩陣在左邊,有列向量,所以進行轉置R = u * vhR = R.T# 返回根據普式分析法得到的仿射變換矩陣return numpy.vstack([numpy.hstack((SD_div * R, col_aver2 - SD_div * R * col_aver1)), numpy.matrix([0., 0., 1.])])得到了臉部圖片和頭部圖片的兩個面部特征點矩陣之后,需要確定一個對應關系,使臉部特征點的向量通過映射轉換之后得到的新向量與頭部特征點的向量盡可能相似,這樣人臉圖像遷移時才會更加準確,轉換為數學問題就是,尋找一個標量s,二維向量T,正交矩陣R,使:
∑i=0n∣∣sRpi+T?qi∣∣2(n=67)\sum\limits_{i=0}^n||sR\mathbf{p_i} + T - \mathbf{q_i}||^2 (n = 67)i=0∑n?∣∣sRpi?+T?qi?∣∣2(n=67)
表達式結果最小(pip_ipi?和qiq_iqi?是兩個面部特征點矩陣的第i行)。
可以通過普式分析法來解決這類問題,通過減去質心,按標準差縮放,然后使用奇異值分解計算旋轉得到仿射變換矩陣[s * R | T],從而解決這一問題。
4、5.仿射變換映射圖片:
def warpAffine_face(face_image, aff_tra_matrix, head_image):warpAffine_image = numpy.zeros(head_image.shape, face_image.dtype)width = head_image.shape[1]height = head_image.shape[0]cv2.warpAffine(face_image, aff_tra_matrix[:2],(width, height), warpAffine_image,borderMode=cv2.BORDER_TRANSPARENT,flags=cv2.WARP_INVERSE_MAP)return warpAffine_image6.修正圖像邊緣函數:
# 修正由于圖像膚色和光線的不同導致臉部覆蓋區域邊緣的不連續問題 def revise_edge(head_image, face_image, head_landmarks):# 取左眼的識別點矩陣的矩心left_eye_points_col_aver = numpy.mean(head_landmarks[list(range(42, 48))], 0)# 取右眼識別點矩陣的矩心right_eye_points_col_aver = numpy.mean(head_landmarks[list(range(36, 42))], 0)# 取兩個矩心之差,取范數后乘以一個模糊量系數得到一個為奇數的高斯內核eye_points_col_aver_sub = left_eye_points_col_aver - right_eye_points_col_averblur_fra = 0.6kernel_size = int(blur_fra * numpy.linalg.norm(eye_points_col_aver_sub)) + 1if kernel_size % 2 == 0:kernel_size = kernel_size - 1# 使用高斯濾波函數得到兩個圖像的高斯模糊值head_image_blur = cv2.GaussianBlur(head_image, (kernel_size, kernel_size), 0)face_image_blur = cv2.GaussianBlur(face_image, (kernel_size, kernel_size), 0)face_image_blur = face_image_blur + 128 * (face_image_blur <= 1.0)# 將使用到的值都轉換為float64類型的face_image = face_image.astype(numpy.float64)head_image_blur = head_image_blur.astype(numpy.float64)face_image_blur = face_image_blur.astype(numpy.float64)# 使用臉部圖片乘以頭部圖片模糊值,再除以臉部圖片模糊值,得到修正邊緣后的圖像return face_image * head_image_blur / face_image_blur可以通過改變臉部圖像的顏色,用RGB縮放顏色,使其更加接近面部頭像,從而解決邊緣問題,這里使用了一個模糊算法,通過一個模糊系數乘以兩眼之間的距離作為高斯內核,得到兩個圖像的高斯模糊值,然后用臉部圖像乘以頭部圖像的高斯模糊值,再除以臉部圖像的高斯模糊值,返回修正邊緣后的圖像。
7、8.和“Swap”按鈕相關聯的換臉進行過程:
# 進行換臉然后顯示結果 def swapFace(self):_translate = QtCore.QCoreApplication.translate# 如果沒有選擇臉部圖片或頭部圖片,那么不換臉if self.FACE_PICTURE_PATH == "" or self.HEAD_PICTURE_PATH == "":self.resultLabel.setStyleSheet("border-image:url(./icons/initial2.png);")self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:20pt; font-weight:600;\">Please choose<br>Face first!</span></p></body></html>"))returntry:# 初始化中間的結果圖片框self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:20pt; font-weight:600;\"></span></p></body></html>"))# 根據圖像的路徑名讀入圖像,將圖像轉為3通道BGR彩色圖像head_image = cv2.imread(self.HEAD_PICTURE_PATH, cv2.IMREAD_COLOR)face_image = cv2.imread(self.FACE_PICTURE_PATH, cv2.IMREAD_COLOR)# 獲取頭部圖片遮罩和臉部圖片遮罩headshade = acquire_shade(head_image, head_landmarks)faceshade = acquire_shade(face_image, face_landmarks)# 獲取仿射變換矩陣aff_tra_matrix = acquire_aff_tra_matrix(head_landmarks, face_landmarks)# 利用仿射變換將臉部圖像遮罩映射到頭部圖片中,生成符合頭部圖片坐標的新臉部遮罩warpAffine_shade = warpAffine_face(faceshade, aff_tra_matrix, head_image)# 利用仿射變換將臉部圖像映射到頭部圖片中warpAffine_image = warpAffine_face(face_image, aff_tra_matrix, head_image)# 得到修正邊緣之后的圖像revise_edge_image = revise_edge(head_image, warpAffine_image, head_landmarks)# 將符合頭部照片的新的臉部圖片遮罩和頭部圖片遮罩結合為一個,盡可能表現出頭部圖片遮罩的特性combined_shade = numpy.max([headshade, warpAffine_shade], 0)# 應用遮罩,輸出換臉圖像result_image = head_image * (1.0 - combined_shade) + revise_edge_image * combined_shade# 在緩存文件夾中保存換臉圖片cv2.imwrite("./tmp/tmp_result.png", result_image)# 設置換臉圖片self.resultLabel.setStyleSheet("border-image:url(./tmp/tmp_result.png);")# 記錄圖片路徑self.RESULT_PICTURE_PATH = "./tmp/tmp_result.png"# 捕捉多于一個人臉的異常except MoreThanOneFaces:_translate = QtCore.QCoreApplication.translateself.resultLabel.setStyleSheet("border-image:url(./icons/initial2.png);")self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:24pt; font-weight:600;\">More than<br>One faces!</span></p></body></html>"))self.RESULT_PICTURE_PATH = ""# 捕捉沒有人臉的異常except ZeroFaces:_translate = QtCore.QCoreApplication.translateself.resultLabel.setStyleSheet("border-image:url(./icons/initial2.png);")self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:30pt; font-weight:600;\">Zero<br>Face!</span></p></body></html>"))self.RESULT_PICTURE_PATH = ""總結
以上是生活随笔為你收集整理的用Python实现一个简单的智能换脸软件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用C++对TINY+语言进行词法分析、
- 下一篇: webbench源码解析