File size: 14,735 Bytes
bd34ae9
863686e
bd34ae9
863686e
bd34ae9
863686e
 
bd34ae9
 
 
 
 
 
 
 
 
 
 
 
 
 
863686e
bd34ae9
863686e
 
 
 
 
 
 
 
 
 
 
 
 
 
bd34ae9
 
863686e
bd34ae9
 
863686e
 
 
 
bd34ae9
 
 
863686e
 
bd34ae9
863686e
 
 
 
 
 
 
 
 
 
 
 
 
 
bd34ae9
863686e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd34ae9
 
 
863686e
 
bd34ae9
863686e
 
 
bd34ae9
863686e
 
 
bd34ae9
863686e
 
 
bd34ae9
863686e
bd34ae9
863686e
 
 
 
 
 
 
bd34ae9
863686e
 
 
 
bd34ae9
863686e
 
 
 
bd34ae9
863686e
 
 
 
bd34ae9
863686e
 
bd34ae9
863686e
 
 
 
bd34ae9
863686e
 
bd34ae9
863686e
bd34ae9
863686e
 
 
 
 
bd34ae9
863686e
 
 
 
bd34ae9
863686e
 
 
bd34ae9
863686e
 
 
bd34ae9
863686e
bd34ae9
863686e
 
bd34ae9
863686e
bd34ae9
863686e
 
bd34ae9
863686e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd34ae9
 
 
863686e
 
 
bd34ae9
863686e
 
 
 
 
bd34ae9
863686e
 
bd34ae9
863686e
 
bd34ae9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
863686e
 
bd34ae9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
863686e
 
bd34ae9
863686e
bd34ae9
 
863686e
bd34ae9
863686e
bd34ae9
 
 
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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
########################################################################################
#

import gradio as gr
import cv2
import glob
import json
import matplotlib
import matplotlib.cm
import mediapipe as mp
import numpy as np
import os
import struct
import tempfile
import torch

from mediapipe.framework.formats import landmark_pb2
from mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinates
from PIL import Image
from quads import QUADS
from typing import List, Mapping, Optional, Tuple, Union

from utils import colorize, get_most_recent_subdirectory
from MediaMesh import *

class FaceMeshWorkflow:
    LOG = logging.getLogger(__name__)

    IMAGE  = 'image'
    LABEL  = 'label'
    MESH   = 'mesh'
    LO     = 'lo'     
    HI     = 'hi'     
    TO_LO  = 'toLo'   
    TO_HI  = 'toHi'   
    WEIGHT = 'weight'   
    BUTTON = 'button' 

    def __init__(self):
        self.mm = mediaMesh = MediaMesh().demoSetup()

    def demo(self):
        demo = gr.Blocks()
        sources = {source:{} for source in 'mediapipe zoe midas'.split()}
        flat_inn = []
        flat_out = []
        with demo:
            gr.Markdown(self.header())

            # image input and annotated output

            with gr.Row():
                upload_image  = gr.Image(label="Input image", type="numpy", source="upload")
                flat_inn.append( upload_image )
                examples      = gr.Examples( examples=self.examples(), inputs=[upload_image] )
                detect_button = gr.Button(value="Detect Faces")
                faced_image   = self.img('faced image')
                flat_out.append( faced_image )

            # per source widget sets

            for name, source in sources.items():
                with gr.Row():
                    source[ FaceMeshWorkflow.LABEL ] = gr.Label(label=name, value=name)
                with gr.Row():
                    source[ FaceMeshWorkflow.IMAGE ] = self.img(f'{name} depth')
                    with gr.Group():
                        source[ FaceMeshWorkflow.LO     ] = gr.Label( label=f'{name}:Min', value=+33)
                        source[ FaceMeshWorkflow.HI     ] = gr.Label( label=f'{name}:Max', value=-33)
                        source[ FaceMeshWorkflow.TO_LO  ] = gr.Slider(label=f'{name}:Target Min', value=-.11, minimum=-3.3, maximum=3.3, step=0.01)
                        source[ FaceMeshWorkflow.TO_HI  ] = gr.Slider(label=f'{name}:Target Max', value=+.11, minimum=-3.3, maximum=3.3, step=0.01)
                        source[ FaceMeshWorkflow.BUTTON ] = gr.Button(value='Update Mesh')
                    source[ FaceMeshWorkflow.MESH ] = self.m3d(name)

            # the combined mesh with controls 

            weights = []
            with gr.Row():
                with gr.Row():
                    with gr.Column():
                        for name, source in sources.items():
                            source[ FaceMeshWorkflow.WEIGHT ] = gr.Slider(label=f'{name}:Source Weight', value=1, minimum=-1, maximum=1, step=0.01)
                            weights.append( source[ FaceMeshWorkflow.WEIGHT ] )
                        combine_button = gr.Button(value="Combined Meshes")
                    with gr.Column():
                        combined_mesh = self.m3d( 'combined' )
                        flat_out.append( combined_mesh )

            # setup the button clicks 

            outties = {k:True for k in [ FaceMeshWorkflow.MESH, FaceMeshWorkflow.IMAGE, FaceMeshWorkflow.LO, FaceMeshWorkflow.HI]}
            for name, source in sources.items():
                update_inputs = []
                update_outputs = [combined_mesh, source[FaceMeshWorkflow.MESH]]
                for key, control in source.items():
                    if key is FaceMeshWorkflow.BUTTON: 
                        continue
                    if key in outties:
                        flat_out.append( control )
                    else:
                        if not key is FaceMeshWorkflow.LABEL:
                            flat_inn.append( control )
                        update_inputs.append( control )
                source[FaceMeshWorkflow.BUTTON].click( fn=self.remesh, inputs=update_inputs, outputs=update_outputs )

            detect_button.click(  fn=self.detect,   inputs=flat_inn, outputs=flat_out )
            combine_button.click( fn=self.combine,  inputs=weights, outputs=[combined_mesh] )

        demo.launch()

    def detect(self, image:np.ndarray, mp_lo, mp_hi, mp_wt, zoe_lo, zoe_hi, zoe_wt, midas_lo, midas_hi, midas_wt):
        self.mm.detect(image)

        self.mm.weightMap.values[ DepthMap.MEDIA_PIPE   ].weight = mp_wt
        self.mm.weightMap.values[ ZoeDepthSource.NAME   ].weight = zoe_wt
        self.mm.weightMap.values[ MidasDepthSource.NAME ].weight = midas_wt

        self.mm.weightMap.values[ DepthMap.MEDIA_PIPE   ].toLo = mp_lo
        self.mm.weightMap.values[ ZoeDepthSource.NAME   ].toLo = zoe_lo
        self.mm.weightMap.values[ MidasDepthSource.NAME ].toLo = midas_lo

        self.mm.weightMap.values[ DepthMap.MEDIA_PIPE   ].toHi = mp_hi
        self.mm.weightMap.values[ ZoeDepthSource.NAME   ].toHi = zoe_hi
        self.mm.weightMap.values[ MidasDepthSource.NAME ].toHi = midas_hi

        meshes = self.mm.meshmerizing()

        z = self.mm.depthSources[0]
        m = self.mm.depthSources[1]

        ##################################################################

        annotated = self.mm.annotated
        combined_mesh = meshes[MediaMesh.COMBINED][0]

        mp_gray = self.mm.gray
        mp_lo   = str(self.mm.weightMap.values[ DepthMap.MEDIA_PIPE ].lo)
        mp_hi   = str(self.mm.weightMap.values[ DepthMap.MEDIA_PIPE ].hi)
        mp_mesh = meshes[DepthMap.MEDIA_PIPE][0]

        zoe_gray = z.gray
        zoe_lo   = str(self.mm.weightMap.values[z.name].lo)
        zoe_hi   = str(self.mm.weightMap.values[z.name].hi)
        zoe_mesh = meshes[z.name][0]

        midas_gray = m.gray
        midas_lo   = str(self.mm.weightMap.values[m.name].lo)
        midas_hi   = str(self.mm.weightMap.values[m.name].hi)
        midas_mesh = meshes[m.name][0]

        ##################################################################
        # gotta write 'em to disk for some reason

        combined_mesh = self.writeMesh( MediaMesh.COMBINED, meshes[MediaMesh.COMBINED][0] )
        mp_mesh       = self.writeMesh( DepthMap.MEDIA_PIPE, meshes[DepthMap.MEDIA_PIPE][0] )
        zoe_mesh      = self.writeMesh( z.name, meshes[z.name][0] )
        midas_mesh    = self.writeMesh( m.name, meshes[m.name][0] )

        ##################################################################
        # [image, model3d, (image, label, label, model3d), (image, label, label, model3d), (image, label, label, model3d)]

        return annotated, combined_mesh, mp_gray, mp_lo, mp_hi, mp_mesh, zoe_gray, zoe_lo, zoe_hi, zoe_mesh, midas_gray, midas_lo, midas_hi, midas_mesh

    def combine(self, mp_wt, zoe_wt, midas_wt ):
        self.mm.weightMap.values[ DepthMap.MEDIA_PIPE   ].weight = mp_wt
        self.mm.weightMap.values[ ZoeDepthSource.NAME   ].weight = zoe_wt
        self.mm.weightMap.values[ MidasDepthSource.NAME ].weight = midas_wt
        return self.writeMesh(MediaMesh.COMBINED, self.mm.toObj(MediaMesh.COMBINED)[0])

    def kombine(self, image:np.ndarray, mp_lo, mp_hi, mp_wt, zoe_lo, zoe_hi, zoe_wt, midas_lo, midas_hi, midas_wt):
        self.mm.weightMap.values[ DepthMap.MEDIA_PIPE   ].weight = mp_wt
        self.mm.weightMap.values[ ZoeDepthSource.NAME   ].weight = zoe_wt
        self.mm.weightMap.values[ MidasDepthSource.NAME ].weight = midas_wt

        self.mm.weightMap.values[ DepthMap.MEDIA_PIPE   ].toLo = mp_lo
        self.mm.weightMap.values[ ZoeDepthSource.NAME   ].toLo = zoe_lo
        self.mm.weightMap.values[ MidasDepthSource.NAME ].toLo = midas_lo

        self.mm.weightMap.values[ DepthMap.MEDIA_PIPE   ].toHi = mp_hi
        self.mm.weightMap.values[ ZoeDepthSource.NAME   ].toHi = zoe_hi
        self.mm.weightMap.values[ MidasDepthSource.NAME ].toHi = midas_hi

        meshes = self.mm.meshmerizing()

        z = self.mm.depthSources[0]
        m = self.mm.depthSources[1]

        ##################################################################

        annotated = self.mm.annotated
        combined_mesh = meshes[MediaMesh.COMBINED][0]

        mp_gray = self.mm.gray
        mp_lo   = str(self.mm.weightMap.values[ DepthMap.MEDIA_PIPE ].lo)
        mp_hi   = str(self.mm.weightMap.values[ DepthMap.MEDIA_PIPE ].hi)
        mp_mesh = meshes[DepthMap.MEDIA_PIPE][0]

        zoe_gray = z.gray
        zoe_lo   = str(self.mm.weightMap.values[z.name].lo)
        zoe_hi   = str(self.mm.weightMap.values[z.name].hi)
        zoe_mesh = meshes[z.name][0]

        midas_gray = m.gray
        midas_lo   = str(self.mm.weightMap.values[m.name].lo)
        midas_hi   = str(self.mm.weightMap.values[m.name].hi)
        midas_mesh = meshes[m.name][0]

        ##################################################################
        # gotta write 'em to disk for some reason

        combined_mesh = self.writeMesh( MediaMesh.COMBINED, meshes[MediaMesh.COMBINED][0] )
        mp_mesh       = self.writeMesh( DepthMap.MEDIA_PIPE, meshes[DepthMap.MEDIA_PIPE][0] )
        zoe_mesh      = self.writeMesh( z.name, meshes[z.name][0] )
        midas_mesh    = self.writeMesh( m.name, meshes[m.name][0] )

        ##################################################################
        # [image, model3d, (image, label, label, model3d), (image, label, label, model3d), (image, label, label, model3d)]

        return annotated, combined_mesh, mp_gray, mp_lo, mp_hi, mp_mesh, zoe_gray, zoe_lo, zoe_hi, zoe_mesh, midas_gray, midas_lo, midas_hi, midas_mesh

    def remesh(self, label:Dict[str,str], lo:float, hi:float, wt:float):
        name = label[ 'label' ] # hax
        FaceMeshWorkflow.LOG.info( f'remesh {name} with lo:{lo}, hi:{hi} and wt:{wt}' )

        self.mm.weightMap.values[ name ].toLo = lo
        self.mm.weightMap.values[ name ].toHi = hi
        self.mm.weightMap.values[ name ].weight = wt

        mesh     = self.writeMesh(name,               self.mm.singleSourceMesh(name)[0])
        combined = self.writeMesh(MediaMesh.COMBINED, self.mm.toObj(MediaMesh.COMBINED)[0])

        return mesh, combined
    
    def writeMesh(self, name:str, mesh:List[str])->str:
        file = tempfile.NamedTemporaryFile(suffix='.obj', delete=False).name
        out = open( file, 'w' )
        out.write( '\n'.join( mesh ) + '\n' )
        out.close()
        return file

    def detective(self, *args):
        for arg in args:
            wat = 'TMI' if isinstance(arg, np.ndarray) else arg
            #c = '#' if hf_hack else ''
            print( f'hi there {type(arg)} ur a nice {wat} to have ' )
        return None

    def m3d(self, name:str):
        return gr.Model3D(clear_color=3*[0],  label=f"{name} mesh", elem_id='mesh-display-output')

    def img(self, name:str, src:str='upload'):
        return gr.Image(label=name,elem_id='img-display-output',source=src)

    def examples(self) -> List[str]:
        return glob.glob('examples/*png')
        return [
            'examples/blonde-00081-399357008.png',
            'examples/dude-00110-1227390728.png',
            'examples/granny-00056-1867315302.png',
            'examples/tuffie-00039-499759385.png',
            'examples/character.png',
        ]

    def header(self):
        return ("""
                # FaceMeshWorkflow

                The process goes like this:

                1. select an input images
                2. click "Detect Faces"
                3. fine tune the different depth sources
                4. fine tune the combinations of the depth sources
                5. download the obj and have fun

                The primary motivation was that all the MediaPipe faces all looked the same.
                Usually ZoeDepth is usually better, but can be extreme. Midas works sometimes :-P

                The depth analysis is a bit slow. Especially on the hf site, so I recommend running it locally.
                Since the tuning is a post-process to the analysis you can go nuts!

                Quick import result in examples/converted/movie-gallery.mp4 under files
        """)


    def footer(self):
        return ( """
            # Using the Textured Mesh in Blender
            
            There a couple of annoying steps atm after you download the obj from the 3d viewer. 
            
            You can use the script meshin-around.sh in the files section to do the conversion or manually:
            
            1. edit the file and change the mtllib line to use fun.mtl
            2. replace / delete all lines that start with 'f', eg :%s,^f.*,,
            3. uncomment all the lines that start with '#f', eg: :%s,^#f,f,
            4. save and exit
            5. create fun.mtl to point to the texture like:
            
            ```
            newmtl MyMaterial
            map_Kd fun.png
            ```
            
            Make sure the obj, mtl and png are all in the same directory
            
            Now the import will have the texture data: File -> Import -> Wavefront (obj) -> fun.obj
            
            This is all a work around for a weird hf+gradios+babylonjs bug which seems to be related to the version
            of babylonjs being used... It works fine in a local babylonjs sandbox...

			If you forget, the .obj has notes on how to mangle it.
            
            # Suggested Workflows
            
            Here are some workflow ideas.
            
            ## retopologize high poly face mesh
            
            1. sculpt high poly mesh in blender
            2. snapshot the face
            3. generate the mesh using the mediapipe stuff
            4. import the low poly mediapipe face
            5. snap the mesh to the high poly model
            6. model the rest of the low poly model
            7. bake the normal / etc maps to the low poly face model
            8. it's just that easy 😛
            
            Ideally it would be a plugin...
            
            ## stable diffusion integration
            
            1. generate a face in sd
            2. generate the mesh
            3. repose it and use it for further generation

			An extension would be hoopy
            
            May want to expanded the generated mesh to cover more, maybe with
            <a href="https://github.com/shunsukesaito/PIFu" target="_blank">PIFu model</a>.
        """)
 

FaceMeshWorkflow().demo()

# EOF
########################################################################################