当前位置:   article > 正文

超分辨率:将背景和人脸分离 ,人脸、背景分别做增分后将人脸贴回背景图_facexlib

facexlib

项目场景:再对一张图像做超分辨率,提取人脸单独做人脸超分,最后把结果贴回原图,实现人脸和背景分离的超分结合。

背景(普通自然景物超分辨率)和人脸超分辨率相结合,可以实现更高的超分效果,提升结果的观感。
# 问题描述与原因分析: 对一张有人脸的图片做超分时候,如果单纯是使用一个自然场景的超分辨率网络,背景部分应该可以较好地还原,毕竟模型在训练的时候有大量的自然景物数据集作为支撑,但是对于人脸区域,使用景物的超分网络效果不一定好,因为人脸超分网络需要用大量的人脸(正脸)数据来训练。

解决方案:

总体思想是先将图片中的所有人脸检测出来,单独做人脸区域超分,然后对背景做超分,使用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    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

效果对比
人脸对齐前:
原图

人脸对齐后:

在这里插入图片描述

效果:
原图像,有背景的线条,也有人脸
原图像,

仅使用一个自然场景的超分网络 real-esrgan ,可以看到背景的窗格效果锐化了很多,但是人脸还是不太ok,比如牙齿头发细节不够
在这里插入图片描述

背景和人脸分开做超分,人脸看起来又得到明显加强,例如牙齿部位,脸型轮廓,但还是看起来怪怪的,用的还是今年的sota方法,以后如有更好的算法,也会再回来记录一下。
在这里插入图片描述

自己微调的模型生成的人脸效果
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/362089
推荐阅读
相关标签
  

闽ICP备14008679号