File size: 12,173 Bytes
e3e4e64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import cv2
import numpy as np
import mp_triangles
import time
from PIL import Image

from glibvision.cv2_utils import blend_rgb_images,pil_to_bgr_image,fill_points,crop,paste
from mp_utils import get_pixel_cordinate_list,extract_landmark,get_pixel_cordinate,get_normalized_landmarks,sort_triangles_by_depth,get_landmark_bbox

import numba as nb
@nb.jit(nopython=True, parallel=True)
def blend_rgb_images_numba(image1, image2, mask):
    height, width, _ = image1.shape
    result = np.empty((height, width, 3), dtype=np.float32)
    
    for i in nb.prange(height):
        for j in range(width):
            alpha = mask[i, j] / 255.0
            for k in range(3):
                result[i, j, k] = (1 - alpha) * image1[i, j, k] + alpha * image2[i, j, k]
    
    return result.astype(np.uint8)

@nb.jit(nopython=True, parallel=True)
def blend_rgba_images_numba(image1, image2, mask):
    assert image1.shape[2] == image2.shape[2] , f"Input images must be same image1 = {image1.shape[2]} image2 ={image2.shape[2]}"
    channel = image1.shape[2]
    height, width, _ = image1.shape
    result = np.empty((height, width, channel), dtype=np.float32)
    
    for i in nb.prange(height):
        for j in range(width):
            alpha = mask[i, j] / 255.0
            for k in range(channel):
                result[i, j, k] = (1 - alpha) * image1[i, j, k] + alpha * image2[i, j, k]
    
    return result.astype(np.uint8)


"""
https://stackoverflow.com/questions/6946653/copying-triangular-image-region-with-pil
This topic give me a idea
"""
"""
bug some hide value make white
"""
debug_affinn = False
min_affin_plus = 0.1
def apply_affine_transformation_to_triangle(src_tri, dst_tri, src_img, dst_img):
    
    src_tri_np = np.float32(src_tri)
    dst_tri_np = np.float32(dst_tri)

    assert src_tri_np.shape == (3, 2), f"src_tri_np の形状が不正 {src_tri_np.shape}"
    assert dst_tri_np.shape == (3, 2), f"dst_tri_np の形状が不正 {dst_tri_np.shape}"


    #trying avoid same value,or M will broken
    if (src_tri_np[0] == src_tri_np[1]).all():
        src_tri_np[0]+=min_affin_plus
    if (src_tri_np[0] == src_tri_np[2]).all():
        src_tri_np[0]+=min_affin_plus
    if (src_tri_np[1] == src_tri_np[2]).all():
        src_tri_np[1]+=min_affin_plus
    if (src_tri_np[1] == src_tri_np[0]).all():
        src_tri_np[1]+=min_affin_plus

    if (src_tri_np[1] == src_tri_np[0]).all() or (src_tri_np[1] == src_tri_np[2]).all() or (src_tri_np[2] == src_tri_np[0]).all():
        print("same will white noise happen")
    # 透視変換行列の計算
    M = cv2.getAffineTransform(src_tri_np, dst_tri_np)
    # 画像のサイズ
    h_src, w_src = src_img.shape[:2]
    h_dst, w_dst = dst_img.shape[:2]

    # 元画像から三角形領域を切り抜くマスク生成
    #src_mask = np.zeros((h_src, w_src), dtype=np.uint8)
    #cv2.fillPoly(src_mask, [np.int32(src_tri)], 255)

    # Not 元画像の三角形領域のみをマスクで抽出
    src_triangle = src_img #cv2.bitwise_and(src_img, src_img, mask=src_mask)

    # 変換行列を使って元画像の三角形領域を目標画像のサイズへ変換
   
    
    transformed = cv2.warpAffine(src_triangle, M, (w_dst, h_dst))
    if debug_affinn:
        cv2.imwrite('affin_src.jpg', src_triangle)
        cv2.imwrite('affin_transformed.jpg', transformed)

    #print(f"dst_img={dst_img.shape}")
    #print(f"transformed={transformed.shape}")
    # 変換後のマスクの生成
    dst_mask = np.zeros((h_dst, w_dst), dtype=np.uint8)
    cv2.fillPoly(dst_mask, [np.int32(dst_tri)], 255)

    # 目標画像のマスク領域をクリアするためにデストのインバートマスクを作成
    #dst_mask_inv = cv2.bitwise_not(dst_mask)

    # 目標画像のマスク部分をクリア
    #dst_background = cv2.bitwise_and(dst_img, dst_img, mask=dst_mask_inv)

    # 変換された元画像の三角形部分と目標画像の背景部分を合成
    #dst_img = cv2.add(dst_background, transformed)
    #s = time.time()
    #dst_img = blend_rgb_images(dst_img,transformed,dst_mask)

    use_blend_rgb = False
    if use_blend_rgb:
        if src_img.shape[2] == 3: 
            dst_img = blend_rgb_images_numba(dst_img,transformed,dst_mask)
        else:
            dst_img = blend_rgba_images_numba(dst_img,transformed,dst_mask)
    else:
        dst_mask_inv = cv2.bitwise_not(dst_mask)
        transformed = cv2.bitwise_and(transformed, transformed, mask=dst_mask)
        dst_img = cv2.bitwise_and(dst_img, dst_img, mask=dst_mask_inv)
        dst_img = cv2.add(dst_img, transformed)

    # TODO add rgb mode
    
    
    #print(f"blend {time.time() -s}")
    if debug_affinn:
        cv2.imwrite('affin_transformed_masked.jpg', transformed)
        cv2.imwrite('affin_dst_mask.jpg', dst_mask)
    return dst_img



from skimage.exposure  import match_histograms
def color_match(base_image,cropped_image,color_match_format="RGB"):
    reference = np.array(base_image.convert(color_match_format))
    target =np.array(cropped_image.convert(color_match_format))
    matched = match_histograms(target, reference,channel_axis=-1)

    return Image.fromarray(matched,mode=color_match_format)

def process_landmark_transform(image,transform_target_image,
                               innner_mouth,innner_eyes,
                               color_matching=False,transparent_background=False,add_align_mouth=False,add_align_eyes=False,blur_size=0):
    image_h,image_w = image.shape[:2]
    align_h,align_w = transform_target_image.shape[:2]
   
    mp_image,image_face_landmarker_result = extract_landmark(image)
    image_larndmarks=image_face_landmarker_result.face_landmarks
    image_bbox = get_landmark_bbox(image_larndmarks,image_w,image_h,16,16)

    mp_image,align_face_landmarker_result = extract_landmark(transform_target_image)
    align_larndmarks=align_face_landmarker_result.face_landmarks
    align_bbox = get_landmark_bbox(align_larndmarks,align_w,align_h,16,16)

    if color_matching:
        image_cropped = crop(image,image_bbox)
        target_cropped = crop(transform_target_image,align_bbox)
        matched = match_histograms(image_cropped, target_cropped,channel_axis=-1)
        paste(image,matched,image_bbox[0],image_bbox[1])
        

    landmark_points = get_normalized_landmarks(align_larndmarks)
   
    mesh_triangle_indices = mp_triangles.mesh_triangle_indices.copy()#using directly sometime share 
    
    #always mix for blur
    mesh_triangle_indices += mp_triangles.INNER_MOUTH
    
    mesh_triangle_indices += mp_triangles.INNER_LEFT_EYES + mp_triangles.INNER_RIGHT_EYES
    #print(mesh_triangle_indices)
    sort_triangles_by_depth(landmark_points,mesh_triangle_indices)
    
    #mesh_triangle_indices = mp_triangles.contour_to_triangles(True,draw_updown_contour) + mp_triangles.contour_to_triangles(False,draw_updown_contour)+ mp_triangles.mesh_triangle_indices
    

    triangle_size = len(mesh_triangle_indices)
    print(f"triangle_size = {triangle_size},time ={0.1*triangle_size}")
    s = time.time()
    
    need_transparent_way = transparent_background == True or blur_size > 0
    if need_transparent_way:# convert Alpha
        transparent_image = np.zeros_like(cv2.cvtColor(transform_target_image, cv2.COLOR_BGR2BGRA))
        h, w = transparent_image.shape[:2]
        cv2.rectangle(transparent_image, (0, 0), (w, h), (0,0,0,0), -1)

        applied_image = transparent_image
        image = cv2.cvtColor(image, cv2.COLOR_BGR2BGRA)
        
    else:
        applied_image = transform_target_image
    
    for i in range(0,triangle_size):#
        triangle_indices = mesh_triangle_indices[i]
        
        
        image_points = get_pixel_cordinate_list(image_larndmarks,triangle_indices,image_w,image_h)
        
        align_points = get_pixel_cordinate_list(align_larndmarks,triangle_indices,align_w,align_h)
        #print(image_points)
        #print(align_points)
        #fill_points(image,image_points,thickness=3,fill_color=(0,0,0,0))
        #s = time.time()
        #print(f"applied_image={applied_image.shape}")
        applied_image=apply_affine_transformation_to_triangle(image_points,align_points,image,applied_image)
        
    print(f"take time {time.time()-s}")
    if need_transparent_way:
        blur_radius = blur_size
        if blur_radius!=0 and blur_radius%2 == 0:
            blur_radius+=1
        
        b, g, r,a = cv2.split(applied_image)
        applied_image = cv2.merge([b,g,r])
        mask = a.copy()
        dilate = blur_radius
        kernel = np.ones((dilate, dilate), np.uint8)
        mask = cv2.erode(mask, kernel, iterations=1)

        if blur_radius>0:
            blurred_image = cv2.GaussianBlur(mask, (blur_radius, blur_radius), 0) #should be odd
        else:
            blurred_image = mask

        if transparent_background:
            #transform_target_image = np.zeros_like(cv2.cvtColor(transform_target_image, cv2.COLOR_BGR2BGRA))
            transform_target_image=cv2.cvtColor(transform_target_image, cv2.COLOR_BGR2BGRA)
            applied_image = cv2.merge([b,g,r,blurred_image])
        else:
            applied_image = blend_rgb_images(transform_target_image,applied_image,blurred_image)

    # after mix
    if not innner_mouth or not innner_eyes or (transparent_background and (add_align_mouth or add_align_eyes)):
        
        import mp_constants
        dst_mask = np.zeros((align_h,align_w), dtype=np.uint8)
        if not innner_mouth or (transparent_background and add_align_mouth):
            mouth_cordinates = get_pixel_cordinate_list(align_larndmarks,mp_constants.LINE_INNER_MOUTH,align_h,align_w)
            cv2.fillPoly(dst_mask, [np.int32(mouth_cordinates)], 255)

            if (transparent_background and not add_align_mouth):
                cv2.fillPoly(transform_target_image, [np.int32(mouth_cordinates)], [0,0,0,0])
        if not innner_eyes or (transparent_background and add_align_eyes):
           
            left_eyes_cordinates = get_pixel_cordinate_list(align_larndmarks,mp_constants.LINE_LEFT_INNER_EYES,align_h,align_w)
            
            cv2.fillPoly(dst_mask, [np.int32(left_eyes_cordinates)], 255)

            right_eyes_cordinates = get_pixel_cordinate_list(align_larndmarks,mp_constants.LINE_RIGHT_INNER_EYES,align_h,align_w)
            cv2.fillPoly(dst_mask, [np.int32(right_eyes_cordinates)], 255)

            if (transparent_background and not add_align_eyes):
                cv2.fillPoly(transform_target_image, [np.int32(left_eyes_cordinates)], [0,0,0,0])
                cv2.fillPoly(transform_target_image, [np.int32(right_eyes_cordinates)], [0,0,0,0])
        #cv2.imwrite("deb_transform_target_image.jpg",transform_target_image)
        #cv2.imwrite("deb_dst_mask.jpg",dst_mask)
        #cv2.imwrite("deb_applied_image.jpg",applied_image)
        applied_image = blend_rgba_images_numba(applied_image,transform_target_image,dst_mask)

    return applied_image



def process_landmark_transform_pil(pil_image,pil_align_target_image,
                                   innner_mouth,innner_eyes,
                                   color_matching=False,transparent_background=False,add_align_mouth=False,add_align_eyes=False,blur_size=0):
    image = pil_to_bgr_image(pil_image)
    align_target_image = pil_to_bgr_image(pil_align_target_image)
    cv_result = process_landmark_transform(image,align_target_image,innner_mouth,innner_eyes,color_matching,transparent_background,add_align_mouth,add_align_eyes,blur_size)
    if transparent_background:
        return Image.fromarray(cv2.cvtColor(cv_result, cv2.COLOR_BGRA2RGBA))
    else:
        return Image.fromarray(cv2.cvtColor(cv_result, cv2.COLOR_BGR2RGB))

if __name__ == "__main__":
    #image = Image.open('examples/00002062.jpg')
    #align_target = Image.open('examples/02316230.jpg')
    image = cv2.imread('examples/02316230.jpg')  # 元画像
    align_target = cv2.imread('examples/00003245_00.jpg')  # 目標画像
    result_img = process_landmark_transform(image,align_target)

    cv2.imshow('Transformed Image', result_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    cv2.imwrite('align.png', result_img)