Paper · 2019-11-19 · cs.CV
Single-Stage 6D Object Pose Estimation
Replaces the RANSAC-PnP step of two-stage 6D pose pipelines with a small network. For each 3D keypoint it takes a group of 2D candidates, pools each group order-invariantly, and regresses a quaternion and translation. Added to SegDriven and PVNet, it raises Occluded-LINEMOD ADD-0.1d averages and is about twice as fast as PVNet with the same backbone (author-reported).
Perspective-n-point (camera pose from 2-D/3-D correspondences)Pose (rigid position and orientation of an object)Correspondence (2-D candidates for 3-D keypoints)pose-estimation6d-object-poseperspective-n-pointpoint-set-pooling
arXivPDFCodeMarkdown11 min read
AI-compiled by claude-haiku-5-5 on 2026-11-08, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
To find where an object is and how it is turned, many systems first find image spots that match fixed points on the object's 3D model, then run a classic geometric solver with random sampling to fit the pose to those spots. The sampling step is slow, and the network that finds the spots is trained on a proxy error, so the final pose can be off even when the spots look fine.
This work replaces the geometric solver with a small neural network. For each model point, the network receives a whole bunch of candidate spots. Inside a bunch the order of the candidates does not matter, so the network pools them. The order of the model points does matter, so each bunch keeps its own slot in the input. The network then outputs the pose directly and is trained on a pose error. Because it is only a replacement for the solver, it can be attached to existing spot-finding networks and trained with them.
TL;DR
- Replaces RANSAC-based PnP in two-stage 6D pose pipelines with a learned, order-aware network §1 §3.1.
- Input: for each of n fixed 3D keypoints, m candidate 2D correspondences; output: a quaternion and a translation §3.1.
- Used with SegDriven and PVNet correspondence networks, it raises the Occluded-LINEMOD ADD-0.1d average and is faster than PVNet's pipeline on the same backbone tab. 1 tab. 2 tab. 3.
- Results are author-reported; the released repository contains only the pose network, not the real-data pipeline README.md.
Problem
Two-stage pipelines learn 2D correspondences with a surrogate loss on 2D error, then solve PnP with RANSAC §1. The paper argues that equal average 2D error can produce different poses, and that RANSAC output depends on the order of correspondences fig. 1. The pipeline is not end-to-end trainable, and RANSAC is slow when there are many outliers §1.
Method
Problem formulation §3.1. Given the camera matrix K and, for each of n 3D keypoints p_i, m candidate 2D points u_ik, the pose (R, t) satisfies the projection relation eq. 1. The method learns a function g with parameters Θ that maps all (p_i, u_ik) pairs directly to (R, t) eq. 2. Classical PnP solvers instead use RANSAC over random subsets §3.1.
Properties the network must respect §3.1.1.
- Cluster ordering: the order of candidates inside one keypoint's cluster must not change the output, but the order of the clusters is fixed by the keypoints §3.1.1.
- Interaction: one cluster alone says nothing about the pose; the global structure across clusters must be captured §3.1.1.
- Absolute positions: unlike point-cloud networks, the features must depend on absolute image positions, not on rigid transformations §3.1.1.
Architecture §3.1.2 fig. 3.
- Local features: a three-layer MLP with weights shared across all candidates and clusters produces a D-dimensional feature f_ik for each candidate §3.1.2.
- Grouped aggregation: for each keypoint i, take the element-wise max over its m candidate features; concatenate the n results in keypoint order to get an nD vector eq. 3 §3.1.2.
- No instance or batch normalisation is used; the paper reports that neither helped §3.1.2.
- Global inference: three fully connected layers map the nD vector to a quaternion and a translation §3.1.2.
The paper contrasts this with a single global max-pool over all points, which would be permutation invariant but would ignore the fixed keypoint order §3.1.2.
Single-stage framework §3.2. A second network f(p_i, I; Φ) outputs the m candidate correspondences for keypoint i from the image I eq. 4. Concretely, f is the encoder-decoder of SegDriven [Hu19a] or PVNet [Peng19a] §3.2. The complete model composes f over all keypoints with g eq. 5.
- A single cube is used for all objects, so its eight corners are the keypoints. The cube is the largest one inside a sphere whose radius is the average of the object bounding spheres §3.2. This means the keypoint identity is implied by the cluster order, not supplied as input §3.2.
- Input per candidate is 4D: the grid-cell centre (x, y) and an offset (dx, dy). The image point is x + dx, y + dy. The paper reports that feeding the image point directly worked worse than this first-order form §3.2. With PVNet's direction-vector output, dx and dy are normalised to unit direction §3.2.
- m = 200 grid cells are sampled inside the predicted mask of the class §3.2.
Training loss §3.2. The total loss is L = L_s + L_k + L_p eq. 6. L_s is the focal loss for per-cell class labels [Lin17]. L_k is the keypoint regression term of the chosen backbone. L_p is the mean, over the n keypoints, of the distance between the 3D keypoints under the predicted and the ground-truth pose eq. 7. Rotations come from quaternions in a differentiable way §3.2. Translations are normalised so their regression targets have a comparable range §3.2.
# Pose from correspondence clusters [§3.1.2] [eq. 3]
# Input: correspondences (B, n, m, 4): per keypoint, m cells of (x, y, dx, dy)
# Output: quaternion (B, 4), translation (B, 3)
def pose_from_clusters(correspondences):
# Per-candidate features, one shared MLP for all keypoints [§3.1.2]
features = shared_mlp(correspondences) # (B, n, m, D)
# Element-wise max over the m candidates of each keypoint [eq. 3]
cluster_features = max_over_candidates(features) # (B, n, D)
# Keep keypoint order: concatenate in the fixed keypoint sequence [eq. 3]
global_vector = flatten(cluster_features) # (B, n * D)
# Three fully connected layers give the raw outputs [§3.1.2]
raw = global_head(global_vector) # (B, 7)
# First four values form the quaternion [§3.1.2] [code: train.py#L139-L140]
quaternion = raw[:, :4]
# Remaining three form a normalised translation [§3.2] [code: train.py#L143-L145]
translation = (raw[:, 4:] + 0.5) * (t_max - t_min) + t_min
return quaternion, translation# Pose loss [eq. 7]
# Input: keypoints (n, 3); predicted and ground-truth quaternion and translation
# Output: scalar
def pose_loss(keypoints, pred_q, pred_t, gt_q, gt_t):
# Normalise quaternions before converting them to rotation matrices
# [code: train.py#L22]
pred_r = rotation_from_quaternion(pred_q / norm(pred_q))
gt_r = rotation_from_quaternion(gt_q / norm(gt_q))
# Move the same keypoints under both poses [eq. 7]
pred_points = pred_r @ keypoints.T + pred_t # (3, n)
gt_points = gt_r @ keypoints.T + gt_t # (3, n)
# Average Euclidean distance over keypoints [eq. 7]
return mean(norm(pred_points - gt_points, axis=0))Hyperparameters
Values in the paper and the released code often differ. Both are listed; the Paper vs code section explains the gaps.
| Name | Value | Where | Notes |
|---|---|---|---|
| Keypoints n | 8 | §3.2 eq. 5 | corners of one shared cube, same for all objects |
| Candidates per keypoint m | 200 | §3.2 §4.1 | grid cells inside the mask |
| Local MLP | 3 layers, shared | §3.1.2 | code: three 1×1 convs, width 128 model.py |
| Feature width D | not stated | §3.1.2 | code: 128 model.py |
| Global head | 3 fully connected layers | §3.1.2 | code: 1024→512→256→7 model.py |
| Output | quaternion (4) + translation (3) | §3.1.2 | |
| Normalisation layers | none | §3.1.2 | code: none model.py |
| Activation | not stated | — | code: LeakyReLU, slope 0.1 model.py |
| Focal loss parameters | not stated | §3.2 [Lin17] | |
| Loss weights | 1 for L_s, L_k, L_p | eq. 6 | code uses alpha = 1 on L_p only train.py |
| Synthetic optimiser | Adam, lr 1e-3 | §4.1 | code: Adam, lr 1e-4 train.py |
| Synthetic epochs | 300 | §4.1 | code: 200 train.py |
| Synthetic batch size | 32 | §4.1 | code: 32 train.py |
| Synthetic training set | 20K images | §4.1 | code: 20,000 fresh samples per epoch train.py |
| Synthetic test set | 2K images | §4.1 | |
| Synthetic LR schedule | not stated | §4.1 | code: ×0.1 at epochs 100, 160, 180 train.py |
| 2D noise | σ drawn in [0, 15] | §4.1 | paper says "variance σ"; code uses it as standard deviation dataset.py |
| Outlier fraction | 0% to 30% | §4.1 | code: uniform per sample dataset.py |
| Synthetic camera | 640×480, f = 800, centred principal point | §4.1 | dataset.py |
| Object centre range | x, y in [-2, 2], z in [4, 8] | §4.1 | dataset.py |
| Input normalisation | not stated | — | code: x and y centred and divided by width/height, dx by width, dy by height train.py |
| Real-data input size | 640×480 | §4.2 | train and test |
| Real-data optimiser | Adam, lr 1e-3, ÷10 after 50%, 75%, 90% of samples | §4.2 | stated for both datasets |
| Real-data batch size | 8 | §4.2 | |
| Real-data samples | 5M, with online augmentation | §4.2 | augmentations: luminance, Gaussian noise, translation, scaling, occlusion |
| Occluded-LINEMOD data | 20K cut-and-paste images (4 to 10 instances) + 10K renders per object | §4.2 | 8 objects; 1,214 test images |
| YCB-Video data | 10K renders per object, 21 objects | §4.2 | no cut-and-paste; original multi-object frames used |
Results
Author-reported. Setup: synthetic data with a unit sphere §4.1; real data on Occluded-LINEMOD (8 objects, 1,214 test images) and YCB-Video (21 objects, about 130K frames) §4.2; baselines PoseCNN, SegDriven and PVNet §4.2. Hardware for timing is a GTX 1080 Ti tab. 3. Table and figure numbers follow the order of the LaTeX source; the compiled PDF was not checked.
Synthetic data §4.1 fig. 6 fig. 7 fig. 8. The paper reports results only as plots. In words: RANSAC-EPnP and RANSAC-P3P perform similarly and beat the learned method at very low noise. The learned method becomes clearly more accurate as noise grows fig. 6. Against PVNet's voting-based PnP, the voting method wins at low noise and the learned method is more robust at high noise fig. 8. A single max-pool without cluster order degrades accuracy markedly fig. 7.
Occluded-LINEMOD, ADD-0.1d and REP-5px tab. 2 (%, percentage of correct poses).
| Object | ADD PoseCNN | ADD SegDriven | ADD PVNet | ADD Ours | REP PoseCNN | REP SegDriven | REP PVNet | REP Ours |
|---|---|---|---|---|---|---|---|---|
| Ape | 9.6 | 12.1 | 15.8 | 19.2 | 34.6 | 59.1 | 69.1 | 70.3 |
| Can | 45.2 | 39.9 | 63.3 | 65.1 | 15.1 | 59.8 | 86.1 | 85.2 |
| Cat | 0.9 | 8.2 | 16.7 | 18.9 | 10.4 | 46.9 | 65.1 | 67.2 |
| Driller | 41.4 | 45.2 | 65.7 | 69.0 | 7.4 | 59.0 | 73.1 | 71.8 |
| Duck | 19.6 | 17.2 | 25.2 | 25.3 | 31.8 | 42.6 | 61.4 | 63.6 |
| Eggbox* | 22.0 | 22.1 | 50.2 | 52.0 | 1.9 | 11.9 | 8.4 | 12.7 |
| Glue* | 38.5 | 35.8 | 49.6 | 51.4 | 13.8 | 16.5 | 55.4 | 56.5 |
| Holepunch | 22.1 | 36.0 | 39.7 | 45.6 | 23.1 | 63.6 | 69.8 | 71.0 |
| Average | 24.9 | 27.0 | 40.8 | 43.3 | 17.2 | 44.9 | 61.1 | 62.3 |
* symmetric objects, scored with the symmetric metric §4.2.
Replacing the RANSAC step on Occluded-LINEMOD, ADD-0.1d tab. 1.
| Object | SegDriven | SegDriven + Ours | PVNet | PVNet + Ours |
|---|---|---|---|---|
| Ape | 12.1 | 14.8 | 15.8 | 19.2 |
| Can | 39.9 | 45.5 | 63.3 | 65.1 |
| Cat | 8.2 | 12.1 | 16.7 | 18.9 |
| Driller | 45.2 | 54.6 | 65.7 | 69.0 |
| Duck | 17.2 | 18.3 | 25.2 | 25.3 |
| Eggbox* | 22.1 | 30.2 | 50.2 | 52.0 |
| Glue* | 35.8 | 45.8 | 49.6 | 51.4 |
| Holepunch | 36.0 | 37.4 | 39.7 | 45.6 |
| Average | 27.0 | 32.3 | 40.8 | 43.3 |
Speed on a GTX 1080 Ti, milliseconds tab. 3. The paper says the PVNet backbone with this network runs about twice as fast as PVNet's own pipeline.
| Method | Correspondence extraction | Fusion | Total | FPS |
|---|---|---|---|---|
| PoseCNN | – | – | >250 | <4 |
| SegDriven | 30 | 20 | 50 | 20 |
| PVNet | 14 | 26 | 40 | 25 |
| Ours | 14 | 8 | 22 | 45 |
YCB-Video tab. 4 (%).
| Metric | PoseCNN | SegDriven | PVNet | Ours |
|---|---|---|---|---|
| ADD-0.1d | 21.3 | 39.0 | – | 53.9 |
| REP-5px | 3.7 | 30.8 | 47.4 | 48.7 |
The "–" marks a value missing from the original PVNet paper tab. 4.
Paper vs code
Official code at commit 8e524666e5d8d19f591461e907b2908048acff0a (GPL-3.0,
cited only). It contains the pose network, the synthetic data generator, and
training and test scripts. It does not contain the correspondence networks,
the real-data pipeline or the Occluded-LINEMOD or YCB experiments README.md.
- Training schedule. Paper: 300 epochs, Adam at lr 1e-3, batch 32 §4.1. Code: 200 epochs, Adam at lr 1e-4, batch 32, with the rate cut by 10 at epochs 100, 160 and 180 train.py.
- Synthetic grid sampling. Paper: 200 cells sampled within the mask §4.1. Code: 200 cells drawn uniformly from the whole image, with no mask dataset.py.
- Noise. Paper: "2D noise with variance σ" in [0, 15] §4.1. Code: a Gaussian with standard deviation σ, σ drawn uniformly in [0, 15] per sample dataset.py.
- Outliers. Paper: a percentage of dx, dy is replaced by uniform values §4.1. Code: the grid position x, y is replaced as well, and the fraction is drawn per sample in [0, 0.3] dataset.py.
- Test protocol. Paper: sweeps noise and outlier rate §4.1. Code: noise 0 to 30 with a fixed 10% outlier fraction, 2,000 samples per level test.py. Only the EPnP baseline is active; the P3P line is commented out test.py.
- Error metric. Paper: error as a ratio to the target object's diameter §4.1. Code: the mean keypoint distance in model units, with no division by diameter test.py.
- Architecture. Paper: a three-layer local MLP §3.1.2. Code: the third layer is linear, with no activation, before pooling model.py. The global head uses LeakyReLU between its hidden layers and a linear output model.py.
- Keypoint count. Code hard-codes eight keypoints in the reshape model.py; the paper uses eight corners §3.2.
- Translation decoding. Not stated in the paper beyond "normalised" §3.2. Code maps the raw output to the synthetic range [-2, 2] × [-2, 2] × [4, 8] train.py.
- Loss. Paper: L = L_s + L_k + L_p eq. 6. Code: only L_p is computed, with alpha = 1 train.py.
- Consistent points. Eight keypoints, the three-layer global head, and no normalisation layers all match the paper model.py.
No discrepancies were found for the reported real-data numbers, because the released code does not run those experiments.
Pitfalls
- Data layout is silent. The code pools by reshaping the flat candidate list into (batch, channels, candidates, 8). This requires keypoint index as the innermost axis and candidates grouped by grid cell. A different layout still runs and returns a number, but it pools across the wrong axis dataset.py model.py.
- Keypoint count and order are fixed. The paper says the network works only for a fixed set of 3D points §4.3. Changing keypoints requires retraining. Eight is hard-coded in the released model model.py.
- Quaternion scale. The network output is not normalised; the loss normalises the quaternion first train.py. Gradients pass through that normalisation. The quaternion sign is not fixed by the loss, since q and −q give the same rotation matrix (an inference from the code, not stated in the paper).
- Translation range. Predicted translations are confined to a box chosen from the synthetic setup train.py. Real-data ranges are not stated in the paper.
- Input normalisation is not in the paper. The released code centres coordinates by image size and scales offsets train.py. Without it, the scale of the inputs differs.
- Variance versus standard deviation. The paper's "variance σ" is a standard deviation in the code dataset.py. This changes the noise levels by a square.
- Epoch semantics. Each epoch draws fresh random poses and noise, so the "20K images" are resampled every epoch dataset.py train.py.
- Symmetric objects. Symmetric objects are scored with a symmetric metric, and training pose ranges are restricted to avoid symmetry confusion §4.2. The released code has no symmetry handling.
- Speed claims depend on the backbone. The timing comparison uses the PVNet backbone for the extraction step, which takes 14 ms in both rows. The gap comes from fusion, 8 ms against 26 ms tab. 3. The timing hardware is a single GPU.
- Limitation. The network is less accurate than geometric PnP when very precise correspondences are available §4.3 fig. 6.
- Not reproducible from the release. The real-data results need the correspondence networks, which are not in this repository README.md.
Lineage
- Builds on: SegDriven [Hu19a] and PVNet [Peng19a] as correspondence extractors, with the same encoder-decoder §3.2. Classical PnP solvers EPnP [Lepetit09] and P3P [Gao03] are the baselines on synthetic data §4.1. The focal loss [Lin17] is used for segmentation §3.2.
- Compared against: PoseCNN [Xiang18b], SegDriven [Hu19a], PVNet [Peng19a] §4.2.
- Superseded by: not known from content already in this wiki.
Source and license
Adapted from “Single-Stage 6D Object Pose Estimation” by Yinlin Hu, Pascal Fua, Wei Wang, Mathieu Salzmann (arXiv:1911.08324), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/cvlab-epfl/single-stage-pose at commit 8e524666e5d8 (GPL-3.0 (copyleft; cited, not quoted)). Any quoted code is shown under that license.