System designExplainer

Pose keypoint connections in JSON: draw the skeleton

The Connections block in a pose model's JSON joins keypoint indices into bones: where it sits in POST_PROCESS, the COCO mapping, and the model-not-found fix.

LumaSpec Vision10 min read Share on LinkedIn

On this page 10 sections
  1. 01The 17 indices come from the dataset
  2. 02The Connections map: index pairs, not coordinates
  3. 03Where the block belongs in the JSON
  4. 04Worked example: turning 17 COCO keypoints into 12 bones
  5. 05Reading the landmarks back from the results
  6. 06Not found in the model zoo after an edit: three checks
  7. 07Choosing the controller that runs the model
  8. 08When the COCO map is the wrong answer
  9. 09Checks before you ship
  10. 10Questions

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.

bones = (sum of all connection-list lengths) / 2 = 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.

  1. 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.
  2. 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.
  3. 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.

Vision controllersHikrobot MV-VC4712-256G10-2TH Ultra 7 255H, 16G×2+256GSSD+2THDD, PCIe expansion, Without VM license keyMV-VC4712-256G10-2TH
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
PoE
PoE camera ports
Light outputs
6 built-in channels
$6,049.00 · quotedView and quoteBuild a system with it

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.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

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:

Build the whole station

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.

Start with the light controller

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.