Files
PoseRenderer/README.md
T
indigo 417ff72850 Add PoseRenderer.mod for Maya module installs
Registers the module as PoseRenderer 1.0.0, auto-adding plug-ins/ to
MAYA_PLUG_IN_PATH and python/ to PYTHONPATH. Verified end-to-end
(module discovery, load-by-short-name, python/ on sys.path,
functional smoke test) on both Maya 2022 and 2024. README documents
this as the recommended install path, with the plain
MAYA_PLUG_IN_PATH route kept as a fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 09:05:53 +08:00

192 lines
9.7 KiB
Markdown

# OpenPose Renderer for Maya
Turns an animated Maya skeleton into OpenPose-style stick-figure images (up
to 25 BODY_25 body/foot + 21+21 hand + 70 face = 137 keypoints), for use
with ControlNet and similar tools. Built entirely on Maya's Python API 2.0.
Targets Maya 2022-2024 (Maya 2022 = Python 3.7, 2023 = Python 3.9, 2024 =
Python 3.10; PySide2 -- PySide6 also detected automatically if present).
Body output supports two OpenPose skeleton formats, selectable per
character (see *Body model* below): **BODY_25** (25 points, includes
MidHip + feet) and **COCO** (18 points, no MidHip/feet, Neck connects
directly to each hip -- OpenPose's original/legacy body model). Both use
the exact same joint mapping; switching formats does not require remapping.
## Install
**Option A -- Maya module (recommended):** copy (or symlink) this whole
`PoseRenderer` folder somewhere on disk, then add its *parent* directory to
`MAYA_MODULE_PATH` (e.g. in `Maya.env`):
```
MAYA_MODULE_PATH = C:\path\to\parent\folder
```
`PoseRenderer.mod` (at the repo root) handles the rest: it registers the
module as `PoseRenderer`, adds `plug-ins/` to `MAYA_PLUG_IN_PATH` and
`python/` to `PYTHONPATH` automatically. In Maya: **Windows >
Settings/Preferences > Plug-in Manager**, find `openposeRenderer.py`, and
check *Loaded* (and *Auto load* if desired) -- it's now loadable by its
short name (`cmds.loadPlugin("openposeRenderer")`) from anywhere.
**Option B -- plug-in path only:** skip the module system and add
`plug-ins/` directly to `MAYA_PLUG_IN_PATH` instead:
```
MAYA_PLUG_IN_PATH = C:\path\to\PoseRenderer\plug-ins
```
The plugin adds its own `python/` folder to `sys.path` automatically at
load time either way (via `MFnPlugin.loadPath()`), so `PYTHONPATH` never
needs setting by hand for the plugin itself to work -- Option A's
`PYTHONPATH` entry only matters if you want `python/openpose_renderer` (or
the bundled `debugpy`) importable outside of loading the plugin, e.g. from
a shelf script.
## Usage
### 1. Map a character
- Run `cmds.openposeCreateCharacter(name="myCharacter")` (or use the
mapping editor's *Create Character* button) to create one
`openposeCharacter` node per person you want to output. Scenes with
multiple `openposeCharacter` nodes render as multi-person OpenPose output.
- Open the mapping editor: `cmds.openposeShowMappingEditor()`.
- Select the character node in the dropdown, select a joint in the
viewport, click *Set from Selected* next to the OpenPose_full slot it
corresponds to. Repeat for as many of the 137 slots as your rig has
joints for -- unmapped slots simply render with zero confidence (no
point/limb drawn), matching how OpenPose itself represents undetected
keypoints. Face and hand slots are optional; body-only rigs work fine.
- Mappings are plain Maya connections (`joint.message ->
openposeCharacter.targetJoint[i]`), so they save with the scene and
survive joint renames/reparenting.
### 2. Viewport preview
With an `openposeCharacter` node in the scene, a colored OpenPose-style
skeleton overlay is drawn live over the mapped joints in every viewport
(toggle per-character via the node's `enableDisplay` attribute; tune with
`jointRadiusScale` / `limbThicknessScale`). This is for QA of the mapping,
independent of rendering.
### Body model (BODY_25 / COCO)
Each `openposeCharacter` node has a `bodyModel` enum attribute (`BODY_25`,
the default, or `COCO`), settable in the mapping editor's *Body Model*
dropdown or directly via `cmds.setAttr("myCharacter.bodyModel", 1)` (0 =
BODY_25, 1 = COCO). It controls both the live viewport overlay and the
rendered output for that character:
- **BODY_25**: all 25 body/foot points -- MidHip, hips/knees/ankles, eyes,
ears, and the 6 foot points (heels + big/small toes).
- **COCO**: the original 18-point OpenPose body model -- no MidHip, no
feet, and the neck connects directly to each hip instead of through a
pelvis point. Slots you mapped for MidHip/feet are simply not drawn in
this mode; nothing needs to be remapped to switch.
Hands and face are unaffected by this setting either way. Rendered from the
same mapped HumanIK-named test rig (`test/openpose_humanik_test.ma`):
| BODY_25 | COCO |
| --- | --- |
| ![BODY_25 output](test/openpose_humanik_test_preview.png) | ![COCO output](test/openpose_humanik_test_preview_coco.png) |
### Face source: facial joints, or ARKit 52 blendshapes
Most facial rigs have no joints at all -- they're driven entirely by
blendShape targets, often named per Apple's ARKit convention (from an
iPhone/TrueDepth mocap pipeline, Live Link Face, or a hand-keyed rig built
to match it). Each `openposeCharacter` has a `faceSource` enum (mapping
editor's *Face Source* dropdown, or `cmds.setAttr("myCharacter.faceSource",
1)`; 0 = `Joints`, 1 = `ARKitBlendshapes`) controlling where its 70 face
keypoints come from:
- **Joints** (default): unchanged from above -- map `Face_*` slots to
facial joints if your rig has them.
- **ARKitBlendshapes**: the 70 face keypoints are instead computed
procedurally from 52 connectable `blendshapeWeight[0..51]` float slots
(in Apple's canonical `ARFaceAnchor.BlendShapeLocation` order/naming --
`eyeBlinkLeft`, `jawOpen`, `mouthSmileLeft`, etc.). Select your
blendShape node (or an ARKit-mocap-driven control) and click *Auto-Connect
ARKit Blendshapes from Selected* in the mapping editor -- it connects
every one of the 52 canonical names that exists as an attribute on the
selected node. The resulting face is anchored at the character's mapped
`Body_Nose` joint and billboards to always face whichever camera is
drawing (viewport or render camera), sized by the `faceScale` attribute
(world units; default 15 -- tune to your scene's unit scale).
**Accuracy caveat:** there is no published, authoritative "which ARKit
weight moves which OpenPose landmark, by how much" table anywhere (unlike
BODY_25/COCO, which OpenPose's own source defines exactly). The neutral
face template and per-blendshape displacement rules in
`face_blendshapes.py` are a hand-authored, geometrically-reasoned
approximation -- each shape nudges only the anatomically obvious nearby
landmarks in an intuitive direction. It produces a recognizable, responsive
OpenPose-style face suitable for ControlNet conditioning, but is not a
biomechanically precise simulation. `jawForward` and `tongueOut` have no
representable effect on a frontal 2D landmark set and are no-ops.
Rendered from `test/openpose_arkit_face_test.ma` (a control node with all 52
ARKit-named attributes, auto-connected -- see that scene for the pattern):
| Neutral | `jawOpen` + smile + raised brows |
| --- | --- |
| ![Neutral face](test/openpose_arkit_face_test_neutral.png) | ![Expression face](test/openpose_arkit_face_test_expression.png) |
### 3. Render OpenPose images
- Switch a viewport panel's **Renderer** menu to **OpenPose** to make it
the active/selectable renderer for that panel (this is the Viewport 2.0
render override integration point).
- To actually generate the OpenPose PNG sequence, run:
```python
cmds.openposeRenderSequence(
startFrame=1, endFrame=48,
camera="renderCam", # optional; defaults to the active view's camera
outputDirectory="C:/out/openpose", # optional; defaults to <workspace>/images/openpose
showInRenderView=True,
)
```
Each frame is projected through the given camera at the scene's render
resolution (`defaultResolution` node), rasterized as a black-background,
OpenPose-colored image (limbs under joints, canonical BODY_25 palette,
per-finger rainbow hand colors, white face dots), written to disk, and
pushed into Maya's Render View so you can watch it render like any other
renderer.
## Debugging (VS Code)
`.vscode/launch.json` provides:
- **`Maya: Attach (debugpy)`** -- attaches to a running, interactive Maya.
`debugpy` is bundled in `python/` (no separate pip install needed --
verified importable under all three target Maya Python versions,
3.7/3.9/3.10). Each session, run `scripts/start_debug_server.py` inside
Maya's Script Editor first (see that file's docstring), then launch this
config from VS Code to hit breakpoints anywhere in `plug-ins/` or
`python/openpose_renderer/` -- including the draw override and render
override callbacks, which only run inside a real Maya viewport.
- **`mayapy 2022/2023/2024: Run current file`** -- runs the file you have
open through that Maya version's own `mayapy.exe` interpreter (with
`python/` on `PYTHONPATH`), for headless scripts that call
`maya.standalone.initialize()` themselves. Useful for quick iteration on
`constants.py`/`projector.py`/`rasterizer.py` without opening Maya's UI.
## Notes / known limitations
- Colors/connectivity: BODY_25 and COCO keypoint layout and limb
connectivity are taken verbatim from OpenPose's own
`POSE_BODY_25_BODY_PART_PAIRS` / COCO section of `POSE_BODY_PART_PAIRS`
(`src/openpose/pose/poseParameters.cpp`). BODY_25's colors are likewise
verbatim from `POSE_BODY_25_COLORS_RENDER_GPU`. COCO limb colors, and all
hand/face colors, are generated from an HSV hue wheel (rainbow per limb
or finger, white face dots) reproducing OpenPose's own visual scheme
rather than a hardcoded transcription of its longer, generated gradient
tables.
- Projection does not attempt to replicate Maya's film-gate/overscan/fit
reconciliation pixel-for-pixel -- for correct results, set the render
camera's film aspect ratio to match the scene's render resolution, as you
would for any Maya render.
- The "OpenPose" Renderer-menu entry uses a standard scene/HUD/present
Viewport 2.0 operation chain (so switching to it always leaves the panel
usable); the actual OpenPose image generation happens via
`openposeRenderSequence`, not via GPU render-target pixel substitution
inside the viewport override.