Nodes/Pixel3DMM ComfyUI Nodes/πŸ”₯ FLAME Parameter Optimizer
ComfyUI Node

πŸ”₯ FLAME Parameter Optimizer

The refinement pass β€” gradient descent on your face parameters

By A043-studiosΒ·Created about a year agoΒ·Updated about a year agoΒ· 10
πŸ”₯ FLAME Parameter Optimizer
  • model
  • image
  • initial_params
  • uv_coordinates
  • surface_normals
  • optimized_params
  • mesh_data
  • status
β—„optimization_steps100β–Ί
β—„learning_rate0.010β–Ί
β—„uv_weight1.0β–Ί
β—„normal_weight1.0β–Ί
β—„regularization_weight0.010β–Ί

FaceReconstructor3D gives you a good-enough first guess at the FLAME parameters. This node is the polish pass. It takes those parameters and refines them with actual gradient descent, optionally using the UV coordinates and surface normals from the other predictor nodes as extra constraints so the fit lands closer to what the photo really shows. If you're chasing a tight, believable reconstruction instead of a rough blockout, this is the node you reach for before you export.

How it works

The mechanism is refreshingly honest: the optimizer takes the initial_params you feed it, makes them trainable, and runs Adam over them for optimization_steps iterations. Each step, it passes the current parameters through the FLAME model to get a mesh, then computes a loss and backprops. The final loss is a weighted sum of three terms:

total = uv_weight * uv_loss + normal_weight * normal_loss
       + regularization_weight * regularization

The UV and normal terms only contribute if you wire in the uv_coordinates (from UVPredictor) and surface_normals (from NormalPredictor) inputs - leave them unconnected and those terms are just zero. The regularization term keeps the parameters from wandering into implausible territory, which matters because FLAME coefficients have meaningful ranges and the optimizer doesn't know that on its own.

The inputs

  • model - the PIXEL3DMM_MODEL container from the loader.
  • image - the original face photo.
  • initial_params - FLAME_PARAMS, normally straight out of FaceReconstructor3D's flame_parameters output.
  • optimization_steps - 10–1000, default 100. The one number you'll actually adjust.

Optional: learning_rate (default 0.01), uv_weight and normal_weight (0–10, default 1 each), regularization_weight (0–1, default 0.01). The defaults are a fine starting point; raise regularization if the mesh starts looking unstable or "jittery" between runs, and raise a constraint weight if you want the fit to lean harder on that map.

The outputs

  • optimized_params (FLAME_PARAMS) - feed these back into a render or export path.
  • mesh_data (MESH_DATA) - the final geometry, ready for MeshExporter.
  • status (STRING) - steps run, final loss, and your weight settings. The final loss number is genuinely useful for comparing runs.

The honest caveat

This is real optimization code doing real Adam updates - but it's only as meaningful as what it optimizes. With the pack's placeholder weights, you're fitting noise to noise: the "optimized" parameters will converge to a lower loss and still have nothing to do with the person in the photo. The loop itself is exactly how the serious research tools (DECA, EMOCA) fit parametric models to images, so it's a great place to learn the mechanics. Just don't judge the optimizer until the base prediction and the constraint maps are coming from real weights.

Start with 100 steps, lr 0.01, all constraint weights at 1, and only touch the knobs when you can see a specific problem in the output - too-rigid fit, wandering geometry, or constraints that aren't being respected. And remember: UV and normal constraints only exist if you actually wire those nodes in.

Installing it

Installed with the whole pack: ComfyUI Manager (search "Pixel3DMM"), or clone https://github.com/A043-studios/comfyui-pixel3dmm into ComfyUI/custom_nodes, run pip install -r requirements.txt, restart. Slow runs? Cut optimization_steps in half - the optimizer is the most expensive part of this pipeline.

CategoryPixel3DMM

Inputs (10)

NameTypeDefaultDescription
modelPIXEL3DMM_MODELβ€”
imageIMAGEβ€”
initial_paramsFLAME_PARAMSβ€”
optimization_stepsINT10010–1000β€”
uv_coordinatesoptUV_COORDSβ€”
surface_normalsoptNORMALSβ€”
learning_rateoptFLOAT0.0100.001–0.1β€”
uv_weightoptFLOAT1.00–10β€”
normal_weightoptFLOAT1.00–10β€”
regularization_weightoptFLOAT0.0100–1β€”

Outputs (3)

NameTypeDescription
optimized_paramsFLAME_PARAMSβ€”
mesh_dataMESH_DATAβ€”
statusSTRINGβ€”