A pose model that works can still look broken: the overlay shows seventeen separate dots per person, and the lines that should join them are missing. The points are right; the joins are missing because nothing tells the renderer which landmarks to connect. That instruction lives in the model's JSON configuration as a Connections map — for each keypoint index, the indices it connects to. This guide gives the full 17-landmark COCO mapping, shows where the block belongs inside the POST_PROCESS section, works the arithmetic that turns 17 points into a 12-bone skeleton, and names the two editing mistakes that make a working model suddenly report it is not found in the model zoo. You finish able to wire the skeleton and keep the model loading.
The 17 indices come from the dataset
The order of the landmarks is not the runtime's choice. A pose model trained on COCO-Pose returns the same 17 keypoints per person in a fixed order, on any hardware and through any loader: index 5 is always the left shoulder, index 16 always the right ankle. One-stage models such as YOLOv8-pose detect the person and place the landmarks in the same pass, so every detection arrives with all 17 points already attached — there is no separate cropping step to wire.
| Index | Landmark | Index | Landmark |
|---|---|---|---|
| 0 | nose | 9 | left wrist |
| 1 | left eye | 10 | right wrist |
| 2 | right eye | 11 | left hip |
| 3 | left ear | 12 | right hip |
| 4 | right ear | 13 | left knee |
| 5 | left shoulder | 14 | right knee |
| 6 | right shoulder | 15 | left ankle |
| 7 | left elbow | 16 | right ankle |
| 8 | right elbow |
Left and right mean the person's, not the image's: on a subject facing the camera, the left shoulder lands on the right side of your frame. A skeleton that looks mirrored usually is not.
The Connections map: index pairs, not coordinates
The renderer needs exactly one thing: which pairs of indices to join with a line. The Connections object says it directly. Each key is a landmark index as a JSON string; each value lists the indices that keypoint joins to. A bone named from both ends is drawn twice along the same pixels, so the redundancy costs nothing.
This is the standard map for a COCO person model:
"Connections": {
"5": [7, 11, 6],
"6": [8, 12, 5],
"7": [9, 5],
"8": [10, 6],
"9": [7],
"10": [8],
"11": [13, 5, 12],
"12": [14, 6, 11],
"13": [15, 11],
"14": [16, 12],
"15": [13],
"16": [14]
}
Read one entry against the table above. Key "5" is the left shoulder, and [7, 11, 6] joins it to the left elbow, the left hip and the right shoulder — the three bones that start at that joint. Key "9", the left wrist, joins only the left elbow. Indices 0 through 4 appear in no key, so the five face landmarks render as dots.
Where the block belongs in the JSON
Connections is a key inside the POST_PROCESS entry, beside the keys the pose post-processor already reads. The working block for a single-class person model looks like this:
"POST_PROCESS": [
{
"OutputPostprocessType": "PoseDetectionYoloV8",
"OutputNumClasses": 1,
"OutputNumLandmarks": 17,
"OutputConfThreshold": 0.3,
"Connections": {
"5": [7, 11, 6], "6": [8, 12, 5], "7": [9, 5], "8": [10, 6],
"9": [7], "10": [8], "11": [13, 5, 12], "12": [14, 6, 11],
"13": [15, 11], "14": [16, 12], "15": [13], "16": [14]
}
}
]
OutputNumLandmarks must equal your model's real landmark count, 17 for COCO. OutputPostprocessType must be PoseDetectionYoloV8 for one-stage YOLOv8-pose models. Leave PRE_PROCESS as the model card ships it: 640 × 640 input with letterbox padding, whose side effects on coordinates are worth knowing before you plot anything — see what happens to a 16:9 image under YOLO letterbox padding.
Worked example: turning 17 COCO keypoints into 12 bones
The inputs, with their sources: 17 landmarks per person (the COCO-Pose convention, declared in the model JSON as OutputNumLandmarks 17); the 12-entry Connections map from the previous section; and the renderer's rule, one line per index pair.
Add the list lengths entry by entry: 3 + 3 + 2 + 2 + 1 + 1 + 3 + 3 + 2 + 2 + 1 + 1 = 24 endpoint entries. Each entry names one end of a bone, and in this map every bone is named from both of its ends, so the bone count is 24 ÷ 2 = 12.
The result is the standard stick figure: four torso lines (5-6 across the shoulders, 11-12 across the hips, 5-11 and 6-12 down the sides), four arm bones (5-7-9 and 6-8-10) and four leg bones (11-13-15 and 12-14-16). The five head landmarks stay dots.
Decision: keep this map as it stands for person detection, because joints connected and the head uncluttered is what an operator reads fastest. If your cell judges head position — helmet checks, gaze direction — add entries for 0 through 4 yourself, with 0 joining 1 and 2 as the minimum. The cost of the whole map is nil next to the model: the runtime reads it once at load and then draws 12 line segments per person per frame, against a 640 × 640 × 3 input frame of 1,228,800 bytes, about 1.2 MB.
Reading the landmarks back from the results
The overlay is for operators; your program needs coordinates, and they are already in the result. Every inference result object carries all keypoint locations, which you can dump with print(inference_result.results): each detected person arrives with the pixel coordinates of all 17 landmarks. Guidance code consumes that list directly and treats the skeleton as a sanity check.
When you keep image records, write the annotated frame out as a file any PC can open instead of screenshotting a preview — the overlay-to-file method covers it. If the built-in line style does not suit your HMI, draw the same pairs yourself and pick the stroke deliberately; line thickness for machine-vision overlays deserves one thought before defaults decide.
Not found in the model zoo after an edit: three checks
The failure looks absurd: the model loaded yesterday, the folder still shows every file, and today the runtime insists the model is not found in model zoo. In practice the JSON broke and the loader reports the missing name instead of the broken file. Work the checks in order.
- Parse the file. Run it through a strict JSON parser — python -m json.tool your_model.json prints the first syntax error with its line number. The usual culprit is a Connections paste that stopped halfway and left an array or brace open.
- Check placement. The object belongs inside the POST_PROCESS entry. At the top level the pose post-processor never reads it, and the mangled paste that put it there is often the same damage that broke the load.
- Match names byte for byte. The model name you pass to the load call must equal the JSON file's name without its extension, and ModelPath inside MODEL_PARAMETERS must equal the compiled model file's name in the folder. Double hyphens are the trap: a file name copied through a chat window or a slide deck can lose a hyphen or grow a long dash, and the lookup fails on a name that never existed.
Then fix once: start from the shipped JSON, add the block inside POST_PROCESS, parse, reload — not five keys at a time. A configuration that lives only in the loaded session disappears the same way camera settings do after a job load; camera configuration lost after job load shows that recovery, and auto-loading a job on startup makes the corrected file permanent.
If the failure follows the model rather than your edit — uploads or loads failing in a notebook-based setup — the YOLOv5, v7 and v8 upload-failure fixes for notebooks walk the same checks from the file-transfer side.
Choosing the controller that runs the model
The skeleton costs nothing; the inference does not. A pose model wants controller-class compute sitting next to the I/O, with cameras on LAN ports and the overlay drawn where the model runs. Three vision controllers span the range:
| Specification | MV-VC3301H-128G66 | MV-VC4712-256G10-2TH | MV-VC5525-256G70-NN |
|---|---|---|---|
| CPU | Intel® Core i3-8100 | Intel® Core Ultra 7 processor 255H | Intel® Core i5-12400 |
| Memory | 8 GB | 2 × DDR5 6400 MHz non-ECC SO-DIMM memory slots; Up to 48 GB per slot, Up to 96 GB with two slots | 32 GB |
| Storage | 128G SSD | 1 × 7-pin SATA3.0, 1 × M.2 SATA 2280, 1 × M.2 NVMe 2280; 1 × 2.5"/3.5" drive bay | 256 GB SSD |
| GPU | Intel® HD Graphics 630 | Intel® Arc 140T GPU | Intel® UHD Graphics 730 |
| Operating system | Windows 10 64-bit, Not activated | Windows 11 64-bit (not activated) | Unactivated Windows 10 |
| Encryption method | VM6200Pro license key | MV-VC47**-**6-**: VM6200pro encryption supported; MV-VC47**-**0-**: encryption not supported | – |
| Video output | HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz | Independent dual display supported; 1× HDMI port, Up to 1920 × 1080 @ 60 Hz; 1× VGA port, Up to 1920 × 1080 @ 60 Hz | HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz |
| Digital I/O | 8 digital isolated inputs supported; 16 opto-isolated outputs supported (NPN/PNP switchable) | 18 digital isolated inputs supported; 18 isolated digital outputs | Opto-isolated input × 8; Opto-isolated output × 12 ( supports NPN/PNP switching), digital-isolated low-side driver output × 8; Non-isolated bi-directional differential I/O × 4 |
| Network interface | 6× Gigabit Ethernet ports | 4× Gigabit Ethernet ports; PoE power supported, 15 W per port, 25 W total across 4 ports | GigE interface × 7, including interface for PoE × 4 |
| USB Interface | USB 2.0 × 4; USB 3.0 × 4; Built-in USB 2.0 × 1 | 4× USB 2.0 ports; 2× USB 3.0 ports; 1 × internal USB2.0 port | USB 2.0 × 4, USB 3.0 × 4, built-in USB 2.0 × 1 |
| Serial Port | 2× full-function RS-232, RS-485/RS-422 switchable | 2 × configurable RS-232/RS-422/RS-485 | RS-232 × 2 by default, supports switching to RS-485 and RS-422 |
| Light interface | 4-channel 24 V constant-voltage light drive, Max. power per channel: 72 W, Max. power (4 channels): 150 W | SMR-03V-BC female; 6-channel 24 V constant-voltage light drive, Max. power per channel: 72 W, Max. total power: 110 W | 24 VDC constant voltage × 8; (Max. power consumption of single channel is 72 W, total max. power consumption 190 W) |
| Price | $2,419.00 | $6,049.00 · quoted | $4,479.00 · quoted |
All 3 share: Certification CE, KC. Values as printed in each product's data sheet; open a part for its drawing and full specifications.
The MV-VC4712-256G10-2TH pairs an Ultra 7 255H with PoE camera ports, 6 built-in light channels and PCIe expansion.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- PoE
- PoE camera ports
- Light outputs
- 6 built-in channels
That is the multi-camera pose cell in one box: cameras and lights terminate at the controller, and the model, the connections map and the overlay all live with the trigger I/O.
For a single-camera bench, the MV-VC2140-128G40-1T runs the identical software layout on an Intel N97 with 8 GB of memory, 4 built-in light channels and 3-4 LAN ports.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
Between those two, the MV-VC3301H-128G66 covers a single camera with headroom to spare and arrives with its vision software licensed, and the MV-VC5525-256G70-NN holds the multi-camera end of the table above. The camera contributes little to the choice: pick it in the smart cameras category by shutter and optics — global shutter for moving people — and let the controller carry the model. Size the whole chain in the configurator:
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
When the COCO map is the wrong answer
Three cases change the answer. A custom model first: once you fine-tune a YOLO pose model on your custom keypoints, the index order follows your training data, OutputNumLandmarks moves to your count, and the COCO map above is a syntax template only — write pairs from your own landmark table.
Robot guidance second: the robot consumes landmark coordinates transformed into its own frame, not drawn lines, so the skeleton is a commissioning aid and nothing more. The hand-off arithmetic is the same as any 2D vision-guided pick and place cell, overlay or no overlay.
A wrong-looking skeleton is the third case, and the map is rarely the cause. Pull the suspicious frames for review before editing connections (finding pose frames for review); if the model places joints badly, no pair of indices will repair it.
Checks before you ship
- Connections sits inside POST_PROCESS, keys quoted as strings, values as arrays of integers — a top-level object renders nothing.
- OutputNumLandmarks equals your model's real landmark count, 17 for a COCO model.
- The load-time model name equals the JSON file name byte for byte, long dashes and double hyphens included.
- One live frame inspected: the results list prints 17 landmarks per person before you trust the overlay.
- Sanity pose on screen: wrists end the arm chains, ankles end the leg chains, hips close the torso square.
- The corrected JSON is the file the cell auto-loads after a power cycle.
Wire the map, parse once, reload once, and the dots become a figure. If a file still refuses to load after the three checks above, bring the JSON to the contact page and we will read it line by line with you.
Questions
Does every keypoint need an entry in the Connections map?
No. Only joints that start a bone need entries, and this map leaves indices 0 through 4 — nose, eyes and ears — out entirely, so the head renders as five dots. A keypoint can also appear only inside another entry's list, like the wrists do. Every pair you add becomes a line, so keep the map to the bones that help an operator read the figure.
Why do my plotted landmarks sit inside a gray border?
Raw model outputs are in the network's 640 × 640 letterboxed frame, not in your source image. The letterbox pads the short side and shifts the content, so points plotted from raw outputs land offset in the padding. Read coordinates from the post-processed results list, which the runtime maps back to source pixels; plot raw outputs only when you recreate the padding math yourself.
Can I use the same syntax for a custom model with a different landmark count?
Yes. The Connections syntax is independent of the count: set OutputNumLandmarks to your model's real number of landmarks and write entries in your model's own index order. The COCO map works as a syntax template only. A four-corner gasket model, for instance, might join 0 to 1 and 3, and 1 to 2, drawing the two edges you actually inspect.
What confidence threshold suits a person-pose model?
The reference configuration in this guide ships with OutputConfThreshold at 0.3, which holds most standing adults while discarding weak detections on cluttered frames. Push toward 0.5 when half-visible people at distance produce flickering skeletons, and drop lower only if real people are being missed. Watch the results list across a few hundred production frames before you fix a number.
Can I draw the skeleton in my own code instead?
Yes. The results list gives every landmark's coordinates, so a short loop over the same index pairs draws identical bones into any image you own. Do it when the overlay must match an HMI style, use dashed or colored lines, or when you archive annotated frames. Draw on a copy so the raw frame stays clean for training data later.

