赞
踩
总体思想是先将图片中的所有人脸检测出来,单独做人脸区域超分,然后对背景做超分,使用Mask的方式将人脸再贴到结果图片。
第一步是检测人脸,我这里用的是 facexlib 这个人脸检测库,因为找到类似的代码就直接用了,还有一个很著名的dlib人脸检测库可以使用,它们的都是基于模型学习的方法来检测人脸,检测出的人脸框出并resize到 512*512 分辨率。
pip install facexlib from facexlib.utils.face_restoration_helper import FaceRestoreHelper ##定义一个检测器 face_helper = FaceRestoreHelper( upscale, face_size=512, crop_ratio=(1, 1), det_model='retinaface_resnet50', save_ext='png', device=self.device) ## 开始检测 输入的是一张图片 def enhance_with_face(self,img,paste_back = True) self.face_helper.clean_all() self.face_helper.read_image(img) # get face landmarks for each face self.face_helper.get_face_landmarks_5(only_center_face=only_center_face) # align and warp each face self.face_helper.align_warp_face() ''' 通过以上步骤就可以获取到图片中的对齐人脸图片 512*512,脸不够大的会自行resize() 以及对齐信息 (对齐其实就是一个图像旋转操作,对齐有助于恢复,恢复完需要将图像旋转回去才能贴回原图) ''' # face restoration for cropped_face in self.face_helper.cropped_faces: # prepare data ''' 这里的cropped_face 是可以用openCV 直接保留下来的 numpy uint8 格式的数据 下面是一些操作,升维,归一化,数据类型变为float,转为tensor ,要把输入变为符合对应人脸超分模型的输入的格式 ''' ## todo 有空的时侯 把升维度,归一化,bgr转rgb等操作改为可判断操作 重构一下代码 cropped_face_t = img2tensor(cropped_face / 255., 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(self.device) try: ##TODO here can change the mote sota algorithm TO get higher quality face output_img = self.model(cropped_face_t) except RuntimeError as error: print(f'\tFailed inference for GFPGAN: {error}.') output_img = cropped_face # restored_face = restored_face.astype('uint8') output_img = output_img.data.squeeze().float().cpu().clamp_(0, 1).numpy() output_img = np.transpose(output_img[[2, 1, 0], :, :], (1, 2, 0)) output_img = (output_img * 255.0).round().astype(np.uint8) # cv2.imwrite('1.png',output_img) ''' 这里的output_img 是可以cv2进行保存的 uint8格式''' self.face_helper.add_restored_face(output_img) ## here handle the background with real-esrgan,bg_upsampler 是一个自然场景的超分网络 ## always paste_back == True if paste_back: if self.bg_upsampler is not None: # Now only support RealESRGAN bg_img = self.bg_upsampler.enhance(img, outscale=self.scale)[0] else: bg_img = None self.face_helper.get_inverse_affine(None) '''将裁出来的人脸,增分后,旋转回原来的角度,(之前水平对齐过,保留了旋转参数) 再贴回到原图片中 这是一个比较复杂的操作 , 大概是 output = Mask * face + (1-Mask) * background 为了避免像拼图,裁出来人脸拼接回去时有明显的裂缝,需要对背景做一个加模糊和腐蚀的操作 具体流程在最下方 paste_faces_to_input_image方法中 ''' restored_img = self.face_helper.paste_faces_to_input_image(upsample_img=bg_img) return self.face_helper.cropped_faces, self.face_helper.restored_faces, restored_img else: return self.face_helper.cropped_faces, self.face_helper.restored_faces, None ''' 主要思路就是 人脸图片的旋转,定义一个带模糊和腐蚀操作的mask,然后将人脸贴回到原图中 ''' def paste_faces_to_input_image(self, save_path=None, upsample_img=None): h, w, _ = self.input_img.shape h_up, w_up = int(h * self.upscale_factor), int(w * self.upscale_factor) if upsample_img is None: # simply resize the background upsample_img = cv2.resize(self.input_img, (w_up, h_up), interpolation=cv2.INTER_LANCZOS4) else: upsample_img = cv2.resize(upsample_img, (w_up, h_up), interpolation=cv2.INTER_LANCZOS4) assert len(self.restored_faces) == len( self.inverse_affine_matrices), ('length of restored_faces and affine_matrices are different.') for restored_face, inverse_affine in zip(self.restored_faces, self.inverse_affine_matrices): # Add an offset to inverse affine matrix, for more precise back alignment if self.upscale_factor > 1: extra_offset = 0.5 * self.upscale_factor else: extra_offset = 0 inverse_affine[:, 2] += extra_offset inv_restored = cv2.warpAffine(restored_face, inverse_affine, (w_up, h_up)) mask = np.ones(self.face_size, dtype=np.float32) inv_mask = cv2.warpAffine(mask, inverse_affine, (w_up, h_up)) # remove the black borders inv_mask_erosion = cv2.erode( inv_mask, np.ones((int(2 * self.upscale_factor), int(2 * self.upscale_factor)), np.uint8)) pasted_face = inv_mask_erosion[:, :, None] * inv_restored total_face_area = np.sum(inv_mask_erosion) # // 3 # compute the fusion edge based on the area of face w_edge = int(total_face_area**0.5) // 20 erosion_radius = w_edge * 2 inv_mask_center = cv2.erode(inv_mask_erosion, np.ones((erosion_radius, erosion_radius), np.uint8)) blur_size = w_edge * 2 inv_soft_mask = cv2.GaussianBlur(inv_mask_center, (blur_size + 1, blur_size + 1), 0) if len(upsample_img.shape) == 2: # upsample_img is gray image upsample_img = upsample_img[:, :, None] inv_soft_mask = inv_soft_mask[:, :, None] if len(upsample_img.shape) == 3 and upsample_img.shape[2] == 4: # alpha channel alpha = upsample_img[:, :, 3:] upsample_img = inv_soft_mask * pasted_face + (1 - inv_soft_mask) * upsample_img[:, :, 0:3] upsample_img = np.concatenate((upsample_img, alpha), axis=2) else: upsample_img = inv_soft_mask * pasted_face + (1 - inv_soft_mask) * upsample_img if np.max(upsample_img) > 256: # 16-bit image upsample_img = upsample_img.astype(np.uint16) else: upsample_img = upsample_img.astype(np.uint8) if save_path is not None: path = os.path.splitext(save_path)[0] save_path = f'{path}.{self.save_ext}' imwrite(upsample_img, save_path) return upsample_img
效果对比
人脸对齐前:
人脸对齐后:
效果:
原图像,有背景的线条,也有人脸
仅使用一个自然场景的超分网络 real-esrgan ,可以看到背景的窗格效果锐化了很多,但是人脸还是不太ok,比如牙齿头发细节不够
背景和人脸分开做超分,人脸看起来又得到明显加强,例如牙齿部位,脸型轮廓,但还是看起来怪怪的,用的还是今年的sota方法,以后如有更好的算法,也会再回来记录一下。
自己微调的模型生成的人脸效果
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。