Back to projects

AI / Robotics

Looking Alive

An interactive, perceptive Leonardo da Vinci.

A character that doesn't just talk at you. It notices you, and has the restraint to know when not to.

Active Research~26 FPSSingle GPU
Computer VisionGaze EstimationMulti-Person TrackingRestraint PolicyExplainable AIReal-Time

Every decision on the record: who it engaged, who it deliberately passed over, and which signals drove each call. Nothing is a black box, “why not her?” has an answer.

…strong model implementations of the deep neural network and GMM-HMM, with clear optimizations.

Kimberley Merritt · Academic Award of Excellence

The build, in five chapters

One character across four bodies, plus the idea that ties them together. Two chapters have work you can read today: The Mind is built and running, and The Face is underway. The last two are mapped. Open any chapter below.

// chapter 02 · the digital face is running; the physical bust is next

Chapter one decides who to notice and what to say. This chapter makes that judgment visible — a face that turns toward the person it chose, warms its expression, and speaks the line. It runs on screen today. Putting it on a physical bust is what's still ahead.

// the face, responding

Thirty seconds of the character running its authored beats: observe, notice, engage, speak, settle. The engagement state and the spoken line are burned into the frame, because the animation is driven by the same logged decision the audit reads — the face and the record cannot disagree.

Rendered natively on a single RTX 3080. Head-yaw 0–12°, mouth-open 0–0.82, no frame hand-animated.

// the constraint that shaped everything

The identity had to be his, and his documented likeness is a red-chalk drawing in three-quarter view. Every automatic face animator needs a detectable frontal face, and no detector can find one in chalk. That turned a vague goal — “make it look like Leonardo” — into a pass/fail gate: does MediaPipe detect a face in the output? If not, it cannot animate, so it does not ship.

The documented likeness: the red-chalk Turin self-portrait. Three-quarter, no photographic texture — no face detector can crop it, so it cannot be animated directly.

The head crop taken from that drawing, fed straight into image-to-image as the base. His actual pixels, not a text description of him.

Ten seeds at strength 0.70, every one passed through the detector gate. One candidate even read a book. Seed 42 became the working source.

The chosen face: recognisably from the self-portrait — long wavy hair, very long beard, high forehead, heavy brow — and reliably detectable, so it always animates.

// the attempt that was replaced

The first solution generated a photoreal frontal from his documented features. It worked mechanically and failed on the thing that mattered. Kept here rather than quietly dropped, because the path from generic-photoreal to self-portrait-grounded cartoon is the result.

Attempt one: a photoreal frontal generated from his documented features. It animated well.

But the identity was wrong — a generic photoreal old man, not clearly Leonardo. Kept and labelled in the code as a generated approximation, not deleted.

// the research call, and the roadblock

For restyling a specific face while keeping its identity, the literature favours IP-Adapter — which decouples identity from surface style — over InstantID, which entangles them. So IP-Adapter was the correct first choice. It needs a ~2.5 GB image encoder, and two download runs died at the shell timeout with no output. The pivot turned out to be both simpler and more literally grounded: image-to-image straight from the self-portrait uses his actual pixels rather than a CLIP embedding of them, and needs no download at all.

// designed, not trained

None of this face is a learned model. Every channel is a deterministic, hand-designed function of the logged decision and the spoken line — which is the point. It makes the face auditable by construction: the same decision always produces the same frame, with no black box between the reason and the expression.

// lips

A mouth timeline written from the text

Each vowel in the spoken line opens the mouth toward 0.85, each consonant closes it toward 0.25, punctuation rests it at zero — then the whole track is smoothed so it doesn't strobe. No lip-sync model, and the same line always animates identically.

// eyes & brows

Presets that scale with the engagement score

Each decision state carries a set of ARKit weights: relaxed while observing, brows raised and inviting on a bid, warm on engage. The eye-widen and smile weights scale with the logged score, so a person who is closer and more attentive gets a warmer face than a marginal one.

// head & gaze

Geometry, straight from the target's position

Yaw and pitch are a direct function of where the engaged person sits in frame, capped at 28° and 12°, and damped across held targets so the head doesn't jitter. It turns toward the person it chose — not toward the camera.

// the warp

478 landmarks and a thin-plate spline

MediaPipe finds 478 face landmarks on the still, the weights above move the control points, and a thin-plate spline deforms the image per frame — using per-landmark depth for a pseudo-3D turn. A classical interpolation, not a learned renderer.

The control preview: a crude CPU render that verifies the controls before the real one runs.

The result: turned toward the person he engaged, expression warmed, speaking an authored, source-grounded line.

// tuning the range

With no learned parameters, tuning is dialling control weights — two global scales, expression intensity and head-turn range, move the whole feel. Because the same weights feed the warp, a FLAME rig, and a MetaHuman, what gets dialled in here transfers to any renderer.

Default tuning: nine expressions — neutral, warm, brow-raised, wide-eyed, engaged, speaking, glancing — rendered through the real animation warp.

The same nine at intensity ×1.6 and head-turn ×1.8. This is close to the practical ceiling of a 2.5D warp — the range is genuinely gentle, and that's a property of the method, not a bug.

// the 3D upgrade — and knowing when to change tools

A thin-plate spline can only push pixels around a flat still. It turns the head, widens the eyes, and opens the mouth on a timeline — but it cannot build the inside of a mouth or round a cheek into a real smile. Generating a separate image per expression proved the point rather than solving it: to get genuinely new expressions from one identity, you need a 3D model of the head.

The workaround that showed the limit: generate a separate image per expression, and accept that each one drifts into a slightly different person.

// what it cost to stand up

GAGAvatar reconstructs a controllable 3D Gaussian head from a single image, driven by FLAME parameters. It is not a pip install: a Linux and CUDA research stack, so the build ran in WSL2 on the same 3080. A conda environment pinned to PyTorch 2.4.1 / CUDA 12.1 / pytorch3d 0.7.8, and — the crux — compiling their custom 32-channel Gaussian rasterizer from source, which meant matching nvcc 12.1 against gcc-12 by hand because Ubuntu's gcc-15 is too new, and supplying the CUDA C++ core headers. The value here was diagnosis: reading the build scripts and isolating each failure until the renderer compiled.

Source still on the left, the reconstructed 3D head on the right. Near-neutral and forward-facing, he holds his identity cleanly.

Driven to a talking expression: the mouth genuinely opens and the head turns to match. This is the thing the warp could never do — a real new expression on the one identity.

A second expressive frame. The mouth interior is where the domain gap shows: the cartoon source has a closed mouth, so the model has never seen its teeth.

// the honest limit, and the fix that failed

GAGAvatar is trained on real human faces, so a stylised cartoon is out of distribution — and it shows in the mouth interior. The source has a closed mouth, so the model never sees its teeth and can only hallucinate a smear when the jaw opens. Running the same pipeline on a realistic frontal source reconstructs a plausible row of teeth, which confirms the cause is the source rather than a bug. The obvious fix was to regenerate the cartoon with a gentle open-mouth smile — four strengths across four seeds, sixteen candidates. It did not work. His long moustache structurally covers the mouth, so no low-strength pass ever parted the lips. The call was to keep the character and manage the limit at the driving stage instead: drive gentle, mostly closed-mouth expressions — a good fit for a dignified Renaissance sage — and accept the rare smear rather than change the face. A mouth-only inpaint is parked for if open-mouth speech is ever needed.

// a note on what is shown

The 3D frames above are cropped to the source and the reconstruction. The full reenactment figures include a driver panel showing an identifiable real person from the tool's demo footage, which was never consented for publication — so the driver is not shown here, on the same standing rule that de-identified the live frames in chapter one. Re-rendering against a consented or synthetic driver is the clean fix, and it is on the list.

// the debugging that mattered

The first run of the generator produced zero output and exited. No error, no traceback, nothing to search for. Isolating it with stepwise flushed prints showed it died the instant diffusers was imported — but only when MediaPipe had already loaded. The cause was a native OpenMP/DLL clash between the two, order-dependent, and a hard abort at the C level, which is why Python never got to raise anything. The fix was to import torch and diffusers before MediaPipe. The inline smoke test had passed the whole time because it happened to import in the safe order — the exact shape of bug that reads as “works on my machine” until you pin the real cause.

// specs

Identityimg2img from the self-portrait
GateMediaPipe-detectable, every candidate
Animation478-landmark thin-plate-spline warp
3D headGAGAvatar Gaussian splatting
DeterminismSame decision → same frame
HardwareOne RTX 3080, native, no cloud

// models & methods

Everything below is pretrained and run for inference. Nothing in the face path was trained here — the only network trained in this project is the expression reader in chapter one, and it reads a visitor's face, never da Vinci's. It drives none of this.

Identity generationStable Diffusion / dreamshaper-8
img2img, inference only
Face landmarksMediaPipe FaceLandmarker
Google, 478 points
3D head reconstructionGAGAvatar
Chu & Harada, NeurIPS 2024
Head modelFLAME
Li et al., SIGGRAPH Asia 2017
Expression encodingARKit blendshapes
Apple, de-facto standard

// the label that ships with the face

This face is a generated stylised approximation grounded in Leonardo's self-portrait — not a documented likeness. That label travels with it wherever it is shown.

// still ahead · the physical bust

Take the face off the screen.

// bust concept renderimage coming

The digital face proves the control system: the same logged decision that chooses a person also aims the gaze, sets the expression, and drives the mouth. Those controls are renderer-agnostic by design. The remaining work is making them move servos instead of pixels — a physical Leonardo bust whose eyes lock onto the chosen guest, whose brow and mouth react in persona, and which speaks with a real voice rather than a text-derived timeline.

// done looks like

A guest steps sideways and the eyes follow the person, not the camera.

// what I'll build

Articulated head rig: eyes, brow, and jaw in hardware

Gaze actuation driven by the attention signal that already exists

Real TTS voice, with Audio2Face lip-sync replacing the text rule

Affect read → expression mapping on physical actuators

// the research behind every chapter

The PhD arc is built to bring four researchers' strengths together: a believable, deployable character that perceives and reasons about people, explainably, then steps off the screen into a physical, reactive robot.

Markus Gross

ETH Zürich / Disney Research

Interactive digital characters and the technology that makes them feel present, including projection into physical space.

Joseph Campbell

Purdue, CAMP Lab

Theory of mind, anticipating human intent, and interpretable interaction, the backbone of the “explain every decision” principle.

Heni Ben Amor

Arizona State, Interactive Robotics Lab

Reactive control and robot learning: characters and robots that respond to people in the moment, the engine behind the robotic phase.

Stelian Coros

ETH Zürich, Computational Robotics Lab

Physics-based, expressive character and robot motion, how a believable performance transfers to a body that obeys physics.

// why it matters

The single most repeatable bit of theme-park magic is a character who makes a guest feel seen. Today that depends on a gifted human performer. This builds it as a real-time, repeatable, explainable system: a character that notices the specific guest in front of it, reacts in persona, plays to a crowd, and eventually steps off the screen into the room. Da Vinci is the first host; the perception and decision engine is the product.

// selected references

A curated selection from a maintained annotated bibliography of 60+ sources, the research grounding plus the third-party methods the build stands on.

Research grounding

  1. [1]Wampfler, R., et al. (2025). A Platform for Interactive AI Character Experiences (Digital Einstein). SIGGRAPH Conf. Papers '25.
  2. [2]Campbell, J. & Ben Amor, H. (2017). Bayesian Interaction Primitives: A SLAM Approach to Human-Robot Interaction. CoRL, PMLR 78.
  3. [3]Campbell, J., Stepputtis, S. & Ben Amor, H. (2019). Probabilistic Multimodal Modeling for Human-Robot Interaction Tasks. RSS. arXiv:1908.04955.
  4. [4]Oguntola, I., Campbell, J., Stepputtis, S. & Sycara, K. (2023). Theory of Mind as Intrinsic Motivation for Multi-Agent RL. ICML Workshop. arXiv:2307.01158.
  5. [5]Zhang, X.-J., et al. (2025). Model-Agnostic Policy Explanations with Large Language Models. COLM. arXiv:2504.05625.
  6. [6]Serifi, A., et al. (2024). Robot Motion Diffusion Model (RobotMDM): Motion Generation for Robotic Characters. SIGGRAPH Asia.
  7. [7]Coros, S., et al. (2013). Computational Design of Mechanical Characters. ACM TOG 32(4), SIGGRAPH.
  8. [8]Bates, J. (1994). The Role of Emotion in Believable Agents. Communications of the ACM 37(7).

Methods & systems

  1. [9]Cheng, T., Song, L., Ge, Y., et al. (2024). YOLO-World: Real-Time Open-Vocabulary Object Detection. CVPR. arXiv:2401.17270.
  2. [10]Jocher, G., et al. (2024). Ultralytics YOLO11 (software).
  3. [11]Zhang, Y., Sun, P., Jiang, Y., et al. (2022). ByteTrack: Multi-Object Tracking by Associating Every Detection Box. ECCV. arXiv:2110.06864.
  4. [12]Abdelrahman, A. A., et al. (2022). L2CS-Net: Fine-Grained Gaze Estimation.
  5. [13]Lin, T.-Y., Maire, M., Belongie, S., et al. (2014). Microsoft COCO: Common Objects in Context. ECCV.
  6. [14]Glas, D. F., Shiomi, M., Kanda, T., et al. (2017). Personal Greetings: Personalizing Robot Utterances Based on Novelty of Observed Behavior. Int. J. of Social Robotics.
EOF

Joey Schnepel · Phoenix, AZ · 2026