01

The Big Picture: Camera In, Magic Out

MediaPipe is Google's open-source framework of ready-made, real-time ML perception models β€” hands, face, pose, objects β€” tuned to run at 30+ FPS on ordinary laptops, phones, and even in the browser. Its Hand Landmarker takes a plain RGB webcam frame and returns 21 precise 3D points per hand, every frame, no depth camera and no gloves required.

TouchDesigner is a node-based visual programming environment used for live visuals, installations, and stage shows. It thinks in operator families: TOPs (textures/images), CHOPs (channels of numbers), SOPs (3D geometry), DATs (tables/text). The moment hand landmarks become CHOP channels, they can drive anything β€” particle systems, shader uniforms, lights, sound.

The whole trick of this chapter is one pipeline:

STEP 1
Webcam frame
Plain RGB image, e.g. 1280Γ—720 @ 30 FPS
STEP 2
Palm detector
Finds a box around each palm (not the fingers!)
STEP 3
Landmark model
Regresses 21 keypoints inside the cropped box
STEP 4
21 Γ— (x, y, z)
Normalized 0–1 coords + relative depth
STEP 5
CHOP channels
63 numbers per hand living inside TouchDesigner
STEP 6
Visuals
Pinch scales a circle, palm position steers particles…
WHY THIS COMBO

MediaPipe answers "where is the hand?" extremely well but draws nothing. TouchDesigner draws anything but has no idea what a hand is. Wire them together and you get touchless interfaces, theremin-style instruments, and gallery installations β€” all from a laptop webcam.

02

Meet the 21 Landmarks

Every tracked hand comes back as the same 21 points in the same order, whether the hand is open, fisted, or waving. Index 0 is the wrist; each finger then gets 4 points from knuckle to tip. Memorize just two of them and you can already build gestures: 4 = thumb tip, 8 = index tip.

πŸ‘‰ Hover or tab over any dot in the diagram to see its index and official MediaPipe name:

hover a landmark…
IndicesRegionNames (knuckle β†’ tip)
0WristWRIST β€” the anchor every distance is measured against
1–4ThumbTHUMB_CMC β†’ THUMB_MCP β†’ THUMB_IP β†’ THUMB_TIP
5–8IndexINDEX_MCP β†’ INDEX_PIP β†’ INDEX_DIP β†’ INDEX_TIP
9–12MiddleMIDDLE_MCP β†’ MIDDLE_PIP β†’ MIDDLE_DIP β†’ MIDDLE_TIP
13–16RingRING_MCP β†’ RING_PIP β†’ RING_DIP β†’ RING_TIP
17–20PinkyPINKY_MCP β†’ PINKY_PIP β†’ PINKY_DIP β†’ PINKY_TIP
COORDINATES DECODED
  • x, y β€” normalized 0 to 1 across the image. (0,0) is the top-left; TouchDesigner UVs use bottom-left, so you'll often compute 1 - y.
  • z β€” depth relative to the wrist, roughly the same scale as x. Smaller = closer to camera. It is not millimeters!
  • A second output, world landmarks, gives approximate meters centered on the hand β€” better for physics, worse for screen mapping.
03

Under the Hood: Two Models, One Sneaky Shortcut

Hand detection sounds like one neural network, but MediaPipe splits it into two specialists β€” and that split is why it's fast enough for live visuals.

Model 1: Palm detector

A single-shot detector (BlazePalm) scans the whole frame for palms. Palms are rigid squares β€” way easier to detect than wiggly fingers β€” so the model stays tiny. It outputs an oriented bounding box.

Cost: the expensive step. Runs on the full image.

Model 2: Landmark model

Takes only the cropped, rotated palm box and regresses the 21 keypoint positions directly β€” no per-pixel heatmaps. It also outputs a confidence score and left/right handedness.

Cost: cheap. The crop is small and pre-aligned.

THE SHORTCUT

Here's the trick that buys the frame rate: once a hand is found, MediaPipe stops running the palm detector. On the next frame it simply predicts the crop box from the previous frame's landmarks and jumps straight to Model 2. The full-frame detector only wakes up again when the landmark model's confidence drops β€” i.e., the hand left the frame or got occluded. Tracking a steady hand is therefore dramatically cheaper than finding a new one.

DETECT ONCE TRACK FOREVER* 30+ FPS

*until you hide your hand behind your coffee mug, at which point the palm detector clocks back in.

04

Setup Path A: The MediaPipe Plugin (Recommended)

The fastest route is the free, open-source MediaPipe TouchDesigner plugin by Torin Blankensmith & Dom Scott (github.com/torinmb/mediapipe-touchdesigner). It runs MediaPipe's GPU web runtime inside a Web Render TOP, so there is zero Python installation pain. Steps:

  1. Download the latest release.zip from the plugin's GitHub Releases page and unzip it. Keep the .tox files together with their folders.
  2. Drag MediaPipe.tox into your TouchDesigner network. A component appears with its own camera selector.
  3. On the MediaPipe COMP's parameter page, pick your webcam and wait for the preview β€” you should see your camera feed with landmark overlays.
  4. Drag hand_tracking.tox in next to it, and wire the MediaPipe COMP's output into it.
  5. Attach a Null CHOP to the hand tracking output and turn on its viewer. Wave. Numbers dance. πŸŽ‰

Because real TouchDesigner screenshots don't survive our comic printing press, here are faithful redrawn network views of what you'll see at each step:

videodevin1 hand detected βœ“ MediaPipe Camera: Webcam-01
πŸ“Έ Step 2–3 β€” MediaPipe.tox dropped into the network. Its built-in viewer confirms the camera and draws the landmark overlay for free.
MediaPipe hand_tracking h1:wrist_x0.512 h1:wrist_y0.684 h1:thumb_tip_x0.431 h1:index_tip_x0.445 h1:index_tip_y0.322 h1:pinch0.061 … 63 channels/hand null_hands
πŸ“Έ Step 4–5 β€” hand_tracking.tox wired in, with a Null CHOP viewer showing the live landmark channels. This Null is the tap everything else drinks from.
WHY A NULL CHOP?

TouchDesigner convention: always export from a Null, never from the source operator. When you later swap the tracker or insert a filter, every downstream reference keeps working because it points at null_hands, not at whatever sits behind it.

05

Setup Path B: Python Script + OSC (Full Control)

Prefer to own the whole pipeline, tweak model options, or run detection on a second machine? Run MediaPipe in a plain Python process and beam landmarks into TouchDesigner over OSC (a tiny UDP protocol TouchDesigner speaks natively). Don't try to pip install mediapipe into TouchDesigner's bundled Python β€” version mismatches make that a rabbit hole; a separate process sidesteps it entirely.

Step 1 β€” install the sender's dependencies (system Python 3.10+):

TERMINAL
pip install mediapipe opencv-python python-osc

Step 2 β€” the sender script. Capture, detect, send each landmark as /hand/<index>:

hands_osc.py
# Detect hands with MediaPipe, stream landmarks to TouchDesigner via OSC. import cv2 import mediapipe as mp from pythonosc.udp_client import SimpleUDPClient client = SimpleUDPClient("127.0.0.1", 7000) hands = mp.solutions.hands.Hands( max_num_hands=1, model_complexity=1, min_detection_confidence=0.6, min_tracking_confidence=0.5) cap = cv2.VideoCapture(0) while cap.isOpened(): ok, frame = cap.read() if not ok: break # MediaPipe wants RGB; OpenCV gives BGR result = hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) if result.multi_hand_landmarks: lm = result.multi_hand_landmarks[0].landmark for i, p in enumerate(lm): client.send_message(f"/hand/{i}", [p.x, 1.0 - p.y, p.z]) client.send_message("/hand/found", 1) else: client.send_message("/hand/found", 0)

Note the 1.0 - p.y β€” that single subtraction converts MediaPipe's top-left origin into TouchDesigner's bottom-left world, saving you a Math CHOP later.

Step 3 β€” receive in TouchDesigner. Drop an OSC In CHOP, set Network Port to 7000, and run the script. Channels named hand/0:0, hand/0:1… appear the instant data flows. Wire it to a Null, same convention as before.

hands_osc.py (external process) UDP β†’ :7000 oscin1 null_hands Network Port: 7000
πŸ“Έ Path B β€” the external Python process (dashed) streams over UDP into an OSC In CHOP. TouchDesigner never has to load MediaPipe itself.
βœ… Pick Path A when…
  • You want results in 10 minutes
  • One machine, GPU available
  • You also want face/pose/objects β€” the plugin bundles them all
πŸ”§ Pick Path B when…
  • You need custom model options or preprocessing
  • Detection should run on a different machine than visuals
  • You want to log/replay landmark data yourself
06

Gesture Math: From 63 Numbers to "They Pinched!"

Raw landmarks are just coordinates. Gestures are relationships between landmarks β€” and the three classics need nothing fancier than a distance formula.

🀏 Pinch = thumb tip close to index tip

PINCH
import math def pinch_amount(lm): # distance between THUMB_TIP (4) and INDEX_TIP (8) d = math.hypot(lm[4].x - lm[8].x, lm[4].y - lm[8].y) # normalize by palm size so it works near AND far from camera palm = math.hypot(lm[0].x - lm[9].x, lm[0].y - lm[9].y) return d / palm # < 0.25 β†’ pinching

That normalization by palm size (wrist 0 β†’ middle knuckle 9) is the single most-forgotten trick: without it, stepping toward the camera reads as "un-pinching".

✊ Fist = all fingertips curled below their knuckles

FIST
def is_fist(lm): # tip landmark vs its PIP joint, per finger (y grows downward) return all(lm[tip].y > lm[pip].y for tip, pip in [(8, 6), (12, 10), (16, 14), (20, 18)])

πŸ–οΈ Open palm = the exact opposite

Same comparison, flipped: every tip above its PIP joint. Combine with a stable pinch value of ~1.0 and you have a reliable "reset" gesture.

SMOOTH IT OR REGRET IT

Raw landmarks jitter a few pixels every frame. Before driving visuals, pass channels through a Lag CHOP (0.1–0.2 s) or a Filter CHOP. For on/off gestures add hysteresis: trigger pinch below 0.22 but only release above 0.32 β€” one threshold means flicker at the boundary.

07

Playground: Drive a Circle TOP With Your (Virtual) Hand

No webcam handy? Same math, simulated. Pick a pose β€” the skeleton animates between real landmark configurations, the pinch formula from Section 06 runs live on those 21 points, and its output drives the "Circle TOP" on the right, exactly the way a CHOP export would in TouchDesigner.

OPEN
chan pinch_raw  = 1.000
chan circle_r   = 70.0
chan is_fist    = 0
IN REAL TOUCHDESIGNER

This wiring is literally: null_hands β†’ Select CHOP (grab thumb/index tips) β†’ Script CHOP or Expression CHOP (pinch formula) β†’ Lag CHOP (smoothing) β†’ export to the Circle TOP's Radius parameter. Five nodes, touchless volume knob.

08

Performance Tips & Troubleshooting

SymptomLikely causeFix
No channels appear (Path A)Camera claimed by another app, or plugin files separated from their foldersClose Zoom/browser tabs using the cam; re-unzip the release keeping folder structure
No channels appear (Path B)Port mismatch or firewallOSC In CHOP port must equal the script's (7000); allow Python through the firewall
Hand found, landmarks jitteryNormal sensor noiseLag/Filter CHOP downstream; never smooth before gesture thresholds with hysteresis
Tracking dies on fast motionTracker lost the hand; detector re-armingRaise camera FPS, lower resolution, raise min_tracking_confidence slightly
Y axis feels upside-downMediaPipe origin is top-left, TouchDesigner is bottom-leftUse 1 - y in the sender, or a Math CHOP: multiply βˆ’1, add 1
Everything works, then FPS tanks over minutesUnthrottled viewer windows and probe overlaysClose operator viewers you're not watching; overlays cost real GPU time
21 POINTS ZERO GLOVES PINCH β†’ PIXELS
END OF HOW-14