3D visionExplainer

Visibility fraction is occlusion only: filter truncated poses too

The visibility fraction measures occlusion only, so edge-truncated objects pass any threshold; add a frame-bounds test on the projected bounding box.

LumaSpec Vision10 min read Share on LinkedIn

On this page 10 sections
  1. 01What does the visibility fraction in a 6D pose annotation measure?
  2. 02Why does an object cut off by the image edge still pass the filter?
  3. 03How wrong can a visib_fract of 0.9 be?
  4. 04Why doesn't raising the visib_fract threshold fix it?
  5. 05Which filter actually removes image-edge truncation?
  6. 06How much margin should the frame-bounds test leave?
  7. 07When is a truncated annotation fine to keep?
  8. 08How do you stop truncation before it reaches the dataset?
  9. 09Which checks should you run before you retrain?
  10. 10Questions

You filtered a pose dataset with visib_fract and slivers of objects still sit at the frame edges, some with values above 0.9. The fraction is not broken; it answers a narrower question than the one you asked it.

This guide shows what the number is computed from, why image-border truncation never appears in it, and the second test that removes truncated samples: every corner of the projected bounding box inside the frame, with a small margin. We give the worked pixel arithmetic, the margin to start with, the cases where truncated samples should stay, and the checks to run before retraining. Anyone curating 6D pose annotations, or building detection data from rendered masks, can apply the filter in an afternoon.

What does the visibility fraction in a 6D pose annotation measure?

The visibility fraction is the number of pixels in the visible-part mask divided by the number of pixels in the full object mask at the annotated pose, so by construction it measures occlusion only and says nothing about the image border.

Each annotated object carries two masks. mask is the object's full silhouette rendered at the annotated pose; mask_visib is the part of that silhouette left uncovered. The BOP dataset format documentation lists mask_visib as the masks of the visible parts of the object silhouettes, and visib_fract in bbox_info records the ratio of their pixel counts. Rendering the model in the annotated pose produces both masks, so the denominator only ever contains pixels the numerator can also see. The value drops when a real occluder covers silhouette pixels inside the frame, and for no other reason.

visib_fract = N(mask_visib) / N(mask) keep = (visib_fract > threshold) AND (all 8 projected corners inside the frame)

Nothing in the first line reads the image dimensions, so the border cannot reach the value. That is exactly why the second line exists.

Why does an object cut off by the image edge still pass the filter?

Truncation removes the same pixels from both masks: the renderer only draws onto the sensor canvas, so a part hanging over the border loses nothing that the ratio is able to see.

Both masks are stored as canvas-sized images. When the annotated pose puts half a bracket beyond the right border, those pixels belong to neither mask, the division still lands near 1.0, and the sample looks pristine. In the scene, half the object was never recorded. Nothing is corrupted; the number answers occlusion, and you asked it about framing.

Border slivers cluster wherever parts arrive on a moving stage or conveyor, because framing drift is mechanical, not random. The sibling failure, annotations whose objects sit entirely outside the view, is covered in 6D pose dataset annotation mismatch: objects outside the view; occlusion itself deserves honest labeling, as in occluded object bounding box labeling for training.

How sensor width, working distance and field of view set the focal length Rays from the edges of the sensor pass through the lens and spread to the edges of the field of view. The ratio of sensor width to field of view is the magnification m.CameraField of view (FOV)Working distance (WD)≈ fsensor width slensm = s / FOVf = WD × m / (1 + m)
How sensor width, working distance and field of view set the focal length. Rays from the edges of the sensor pass through the lens and spread to the edges of the field of view. The ratio of sensor width to field of view is the magnification m.

How wrong can a visib_fract of 0.9 be?

Arbitrarily wrong: a part with three quarters of its silhouette outside the frame still stores visib_fract of 1.00, because both masks clip at the border before the division happens.

Take a 640 × 480 frame and a bracket whose full silhouette measures 40,000 pixels at the annotated pose. The stage pushed it right, so only 10,000 silhouette pixels lie inside the frame, and nothing occludes it:

  • mask = 10,000 pixels, mask_visib = 10,000 pixels
  • visib_fract = 10,000 / 10,000 = 1.00, so a 0.9 cutoff passes the sample
  • the share of the bracket actually imaged is 10,000 / 40,000 = 0.25

Now the same bracket fully in frame, with a fixture covering 16,000 silhouette pixels. The visible mask holds 24,000 pixels (40,000 - 16,000), so visib_fract = 24,000 / 40,000 = 0.60, and the 0.9 cutoff rejects it. The threshold rejects the bracket that is 100 percent in view after passing the one that is 25 percent in view. That inversion is the whole problem: the threshold ranks occlusion, while you are trying to rank framing.

Why doesn't raising the visib_fract threshold fix it?

Raising the cutoff from 0.5 to 0.9 discards moderately occluded samples, which are useful training data, while every border sliver passes untouched, because truncation never moves the numerator or the denominator.

Occlusion lives inside the frame; truncation lives at the border, and one scalar can only gate one axis. In a preparation script such as prepare_data.py, no value compared against bbox_info["visib_fract"] changes what that field measures, which the worked example above shows numerically. Add a second predicate next to the first instead of turning the first one up.

Which filter actually removes image-edge truncation?

Test the geometry, not the ratio: project the object's 3D bounding box through the annotated pose and camera, and require all 8 corners to land inside [0, width) × [0, height).

With poses available, corner projection is the exact test, because it uses the same model and pose the masks were rendered from and so sees past any mask quirk. For detection-only datasets shot with area scan cameras, where no pose or model exists, use the border-touch rule curators have relied on for years: reject any box whose edge comes within the margin of a border. It errs toward dropping, which is the safe direction for training data.

# Filter for one annotation. bbox_info and visib_fract are the annotation
# file's own names; every other name here is illustrative — rename to suit.

image_width_px: int = 640    # frame width in pixels
image_height_px: int = 480   # frame height in pixels
margin_px: int = 4           # keep-out band inside each border

# Corners of the projected 3D bounding box, xy in pixels:
corners_px = [
    (618.0,  96.0), (639.0,  94.0), (641.0, 152.0), (620.0, 154.0),
    (610.0,  90.0), (631.0,  88.0), (633.0, 146.0), (612.0, 148.0),
]

fully_inside: bool = all(
    margin_px <= x_px < image_width_px - margin_px
    and margin_px <= y_px < image_height_px - margin_px
    for x_px, y_px in corners_px
)

keep_sample: bool = fully_inside and bbox_info["visib_fract"] > 0.5

Check the arithmetic on those values. With margin_px = 4, the allowed ranges are x in [4, 636) and y in [4, 476). Corner (639.0, 94.0) sits inside the frame but inside the keep-out band, because 639 ≥ 636. Corner (641.0, 152.0) sits past the frame itself, because 641 > 640. Either failure makes fully_inside False, so keep_sample is False even with visib_fract at 0.93. Shift the same box 12 pixels left and the largest x becomes 629, which satisfies 629 < 636, so every corner passes and the sample survives. Same masks, same visibility value, different verdict: that is the second predicate working.

How much margin should the frame-bounds test leave?

Start at 3 to 5 pixels on a 640-pixel-wide frame, about 0.5 to 0.8 percent of the width, and widen only when a specific error source in your data asks for it.

The band absorbs measurement noise, not sloppiness. Rendered masks and projected corners carry on the order of 1 to 2 pixels of error from calibration residuals and rasterization, so 3 to 5 pixels absorbs that while discarding almost nothing well-framed. Three things move it:

  • wide-angle projection without undistortion: undistort the corners first; if no distortion model is available, widen toward 8 pixels at 640
  • detector boxes instead of projected corners: detection boxes erode near borders, so widen to about 1 percent of the width, 6 pixels at 640
  • frame size: hold the band near half a percent of the width — 3 pixels at 640, 6 at 1280, 12 at 2560

Start at 4 pixels for 640-wide frames, refilter, and inspect the rejects. If the bounds test drops boxes sitting well clear of every border, the margin is too large or the projection is wrong — fix that before blaming the data.

When is a truncated annotation fine to keep?

Keep truncated samples only when your runtime scene genuinely puts objects at the frame edge, and keep them because they are truncated on purpose, not because the visibility fraction looked acceptable.

Three cases decide it:

  • Full-extent pose supervision: drop them. The model would regress geometry the frame never recorded, and every kept sliver teaches it to invent the missing part.
  • Detection on a fixed camera whose scene really crops parts: keep them, marked as truncated. A model trained only on fully visible objects learns that border slivers are background, and fails exactly where parts arrive.
  • Pipelines that crop a centered region of interest: run the corner test on the ROI bounds instead of the sensor frame; a part cut by the ROI border is the same failure one level down.

When the part itself can be larger than the view, no framing rescues the annotation and the task needs a different treatment, as in template matching a part larger than the field of view.

How do you stop truncation before it reaches the dataset?

Fix it at capture: frame the scene so every annotated part stays a few pixels clear of every border through the whole stroke, and the bounds test becomes a safety net instead of the only defense.

On stereo rigs the depth view is narrower than the color view, so a part that fits the RGB frame can still be truncated in depth; the geometry is covered in depth image field of view: why stereo coverage is smaller. For bin-picking data, keep the camera far enough back that the tote's near walls, not just its floor, stay in view; the trade-offs are compared in 3D bin picking for flat parts: choosing a vision approach.

For a bench rig that captures its own pose data, a projected structured-light 3D camera provides dense depth and clean silhouettes to run both filters on:

3D sensorsHikrobot MV-DPS200P-02 Projected structured-light 3D cameraMV-DPS200P-02
Type
Stereo / structured light
$8,829.00 · quotedView and quote

For an overhead rig that watches a zone and must hold every object fully in view before measuring, a ToF sensing camera covers the same framing discipline across the whole scene:

3D sensorsHikrobot MV-DT01WMU ToF sensing cameraMV-DT01WMU
Type
Depth / ToF camera
Price on requestView and quote

Both sit in the 3D sensors category, the camera finder filters them by type and interface, and the camera configurator walks a new rig from sensor to mount. Capture-side geometry questions live on the 3D vision topic page.

Which checks should you run before you retrain?

Re-run the filter over the whole dataset and expect the two tests to remove different samples; if the bounds test removes none, your margin, your projection or your loader is wrong.

  1. Count removals per test. Both counts should be non-zero, and neither should jump by tens of percent without something you can point at in the images.
  2. Render 20 rejects from each test. Occlusion rejects should show real overlap with other objects; bounds rejects should touch a border.
  3. Measure the closest approach. Across all kept samples, the minimum distance from a box edge to its nearest border must exceed the margin; if it equals the margin exactly, look for an off-by-one in the comparison.
  4. Break the removals out per scene. One scene with an outlier rate points at a moved camera or a shifted guide, not at the filter.
  5. Save the filter report with the dataset version, both thresholds and the margin, so next quarter's counts reconcile against this quarter's.

Run the two-predicate filter, eyeball 20 rejects, and the slivers leave your training set the same afternoon. If you are speccing a new capture rig around this filter, tell us the part sizes and travel at the contact page and we will help you match the view to the part.

Questions

Why do the stray boxes cluster at one edge of the image?

Because something mechanical pushes parts that way. A shifted conveyor guide, a worn stop or a moved camera shows up as a cluster of border-touching boxes on one side. Fix the framing and the bounds test's reject count in that scene drops to near zero, which is your confirmation.

Can a truncated annotation be repaired instead of dropped?

Rarely. The annotated pose places real geometry beyond the border, so the object's full extent is unknown in that frame. Unless you recapture with wider framing, dropping is the honest choice; the same part usually appears fully framed in nearby frames of the same scene, so little data is lost.

Does a higher-resolution camera shrink the truncated-annotation problem?

No. Resolution adds pixels, not view. The share of the object inside the frame is set by field of view and working distance, so a 12 MP frame records the same sliver with finer pixels and the same visib_fract. Widen the view or filter the samples; changing sensors does neither.

Does the same truncation problem affect laser profiler data?

Yes, along the scan direction. A profiler truncates a part whose profile leaves the measurement window or whose travel stops mid-part, and profile masks need the same in-window test, applied against the encoder range and the laser's own field instead of a rectangular frame.

Why does nothing in the annotation file flag truncated objects?

The format stores what the pose implies: a full silhouette mask and a visible-part mask, both rendered on the frame's canvas. Framing is a property of the capture, not of the annotation, so no field marks it. Your filter is the right place for the check, exactly where the occlusion threshold already lives.