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

景(自然景物超分辨率)和人脸超分辨率相结合,可以实现更高的超分效果,提升结果的观感。
# 问题描述与原因分析: 对一张有人脸的图片做超分时候,如果单纯是使用一个自然场景的超分辨率网络,背景部分应该可以较好地还原,毕竟模型在训练的时候有大量的自然景物数据集作为支撑,但是对于人脸区域,使用景物的超分网络效果不一定好,因为人脸超分网络需要用大量的人脸(正脸)数据来训练。
解决方案:
总体思想是先将图片中的所有人脸检测出来,单独做人脸区域超分,然后对背景做超分,使用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方法,以后如有更好的算法,也会再回来记录一下。

————————————————
版权声明:本文为CSDN博主「庄仪浩」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_20265015/article/details/121457113

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值