The AY-Robots dataset recording tutorial, where the number and placement of cameras for a LeRobot dataset is decided
OcclusionWrist cameraDataset recordingVLA policiesSO-100LeRobot

Occlusion: What a Robot Policy Does When It Cannot See

AY-Robots ResearchAugust 23, 202620 min read

A policy under occlusion does not stop, it keeps acting from proprioception. What the published ablations measured, why the wrist camera matters, and how to record for it.

What you need to know

  • A policy that cannot see the object does not stop and does not error. It keeps acting on whatever signal is left, usually the joint angles, and that looks like a confident wrong move.
  • RoboMimic measured a real Can task at 73.3 percent with wrist observations and 43.3 percent without, and reports a 10 to 45 percent range across settings.
  • LIBERO-Occ (June 2026) ran five VLAs under scene-induced occlusion: OpenVLA fell from 92.65 to 40.65 average success, Pi0.5 from 90.00 to 40.55.
  • Two views is the working default on an SO-100: one third-person, one wrist. NVIDIA's own SO-100 example for GR00T declares exactly those two, named front and wrist.
  • Action chunking makes it worse in a specific way. ACT here defaults to chunkSize 100 and nActionSteps 100, so the arm runs 100 predicted actions before looking again.
  • The observation space is frozen at record time. You cannot add a camera at training time and you cannot drop one at inference time.

Occlusion is not one problem

Every demo of a vision-language-action model picking up a cube hits the same moment: the gripper closes over the object and the object stops being visible. On a tabletop arm that is not an edge case, it is the geometry of grasping. The end effector has to get between the camera and the target in order to touch it. With one webcam on a tripod, the last few centimetres of every approach happen behind the arm.

The interesting question is not whether occlusion happens but what a trained policy does during the seconds it cannot see. It keeps going. It does not slow down or report low confidence. It produces a smooth trajectory out of the parts of the observation that are still informative, and on an SO-100 that is almost always the joint angles it already knows.

TypeWhat is hiddenCause on an SO-100What it breaks
Self-occlusionThe target, behind the armThe forearm crosses the scene camera during approachLocalisation late in the approach
Gripper occlusionThe target, behind the fingersThe end effector sits between camera and object at contactGrasp timing, when the gripper closes
Scene occlusionThe target, behind another objectClutter, a bin wall, a taller object in frontInitial localisation, so the whole episode
Receptacle occlusionThe destination, not the objectThe box or bin is behind something elsePlacement, the second half of the task
Field-of-view lossNothing hidden, the object left the frameCamera nudged between sessions, arm moves past the frame edgeEverything downstream, silently

LIBERO-Occ, published in June 2026, formalises three of these. It extends the LIBERO suite with what the authors call scene-induced occlusion, placing physical occluders in the 3D scene rather than masking pixels, and sorts 2000 occluded task instances into manipulated-object occlusion (900), receptacle occlusion (750) and dual occlusion (350). Severity is the fraction of the target rendered invisible, (A_full minus A_visible) over A_full, split by per-suite quartiles into light, medium and heavy. Worth keeping in mind when you review your own recordings, because receptacle occlusion is the one people forget: the bin disappears more often than the cube.

What the policy does when the object disappears

Behaviour cloning fits a map from observation to action, and nothing in that procedure knows which part of the observation caused the human's action. De Haan, Jayaraman and Levine called the result causal confusion in 2019 and stated the counter-intuitive part plainly: access to more information can yield worse performance. The model latches onto whichever input predicts the label best on the training set, and in a teleoperated LeRobot dataset that is rarely the image. It is the previous action, which is almost perfectly encoded in the current joint positions.

The failure does not look like a failure

A policy that has learned to ignore vision still produces smooth, well-scaled motion. It reaches for where the object usually is. On your training table that is the right place, so evaluation looks fine. Move the object 8 cm and the arm goes to the old spot with complete confidence. The symptom pages to read first are policy only works in one setup and loss falls, policy does nothing.

Wen and colleagues named the mechanism in 2020: the copycat problem. When expert actions are strongly correlated over time, and at 30 Hz on a leader-follower rig they are nearly identical frame to frame, the imitator learns to predict the expert's previous action instead of the next one. That shortcut exists in every imitation learning dataset recorded by teleoperation, and it is exactly the shortcut that survives when the view goes dark.

  • Proprioceptive extrapolation. The trajectory implied by the current joint configuration continues. A smooth reach to the wrong place.
  • Collapse to the training prior. It goes to the average object position in the dataset. On a dataset recorded with the cube in one spot, indistinguishable from working.
  • Gripper timing failure. Open and close are the action dimensions most tightly coupled to what the wrist sees, so they go early or late. See gripper does not close.
  • Stall, the rarer case. Some checkpoints emit near-zero deltas far out of distribution, which reads as policy freezes mid-motion.

What the published ablations measured

RoboMimic, 2021: remove the wrist camera and watch

Mandlekar and colleagues ran six offline learning algorithms across five simulated and three real tasks. Their observation-space ablation is still the cleanest number on this: not including wrist camera observations can reduce performance by 10 to 45 percent. On the real Can task the effect is easy to state.

Real-robot Can taskSuccess rate
Full observation space (scene + wrist + pixel shift randomisation)73.3 percent
Same, without wrist camera observations43.3 percent
Same, without pixel shift randomisation26.7 percent

ICLR 2022: the wrist view generalises, the room view memorises

Hsu, Kim, Rafailov, Wu and Finn asked the sharper question: which view survives a change in the world. Their real setup was a Franka Emika Panda grasping a sponge among distractors, trained by behaviour cloning on 360 teleoperated demonstrations. Both the hand-centric and the third-person policy reached 85 percent on the training distribution. Then they changed the table height, the distractors and the table texture.

Observation spaceTraining distributionMean success under distribution shift
Hand-centric camera + proprioception85 percent52.0 percent
Third-person camera + proprioception85 percent20.0 percent

The caveat is theirs and it matters: this holds only when hand-centric observability is sufficient. If the wrist camera cannot see the target at the start of the episode, the third-person view is not optional. Their fix was to keep both and regularise the third-person stream with a variational information bottleneck, cutting aggregate out-of-distribution failure by 64 percent relative to using both views naively. You will not implement that on a Tuesday evening, but the framing is the useful part: the room camera is where overfitting enters, the wrist camera is where robustness comes from.

LIBERO-Occ, 2026: what current VLAs do under occlusion

The most direct measurement is the newest. LIBERO-Occ evaluated five models on the original suites and on the occluded variants, with the standard third-person view as the primary observation. Simulation numbers, so treat them as a direction and a magnitude.

ModelOriginal LIBERO (avg)LIBERO-Occ (avg)Drop
OpenVLA92.6540.6552.00 points
Pi0.590.0040.5549.45 points
OpenVLA-OFT95.7547.9547.80 points
Pi089.2549.3039.95 points
UniVLA88.2557.1031.15 points
VIM (the paper's method)90.7565.0525.70 points
What these numbers are and are not

Three setups, three eras of hardware, one consistent shape: hiding the target costs roughly a third to a half of the success rate, and nothing on the list is immune. None of these are SO-100 numbers. For per-model benchmark values with each figure linked to its source, use the model arena.

The AY-Robots failure-mode index, a list of named robot policy and hardware failures each linking to a fix page
Most of the symptoms above have their own page. The gripper closing at the wrong moment and the policy that only works in one setup are usually a camera problem wearing a different hat.

Why the wrist view helps, and where it does not

A wrist camera does not remove occlusion. The fingers still block the object at contact. What it changes is framing: the target appears in a coordinate frame that moves with the gripper, so the mapping from pixels to the correction the arm should make is close to invariant, and the background, the part of the image that differs between your kitchen table and your desk, mostly falls out of frame.

Adding a wrist camera to an SO-100 recording setup
What you get
  • The target stays visible through the part of the approach where the scene camera is blocked by the arm.
  • Gripper timing gets a view actually correlated with contact, the thing one-camera policies most often get wrong.
  • Measured robustness: 52.0 against 20.0 percent under table, distractor and texture shifts in the ICLR 2022 real-robot test.
  • It is what the reference stacks do. NVIDIA's SO-100 GR00T example, the SmolVLA SO-100 experiments and ALOHA all ship a wrist view.
  • Cheap. A second USB webcam and a printed bracket against a 110 to 150 EUR arm.
What it costs
  • It cannot see the target before the approach begins. If the object starts outside the wrist frame, that view alone cannot learn the task.
  • A second stream is more encode time in the record loop, more disk, and more vision tokens per inference step.
  • The published GR00T N1.7 timing table is measured with one camera, so it understates your deployment.
  • Cable strain. A USB cable on a moving forearm fails as camera not detected halfway through a session.
  • It changes the observation space, so it invalidates every dataset you recorded without it.

Two views is the setup everyone converged on

Nobody arrived here by theory. The stacks that work all landed on roughly the same shape, one view of the scene and one from the hand, and the differences are mostly budget.

SetupScene viewsWrist viewsDetail
ALOHA / ACT (2023)2 (front and top)2 (one per follower arm)Four Logitech C922x webcams at 480x640, teleoperation and recording at 50 Hz
DROID (2024)2 adjustable Zed 2 stereo1 Zed Mini stereoFranka Panda, 76k trajectories, 350 hours, 564 scenes, 84 tasks
SmolVLA on SO-1001 top1 wristThe paper's real-world SO-100 benchmark tasks
SmolVLA on SO-1011 top and 1 side0Same paper, different embodiment, no wrist view
NVIDIA GR00T SO-100 example1, key named front1, key named wristDeclared in modality.json and so100_config.py in Isaac-GR00T
LeRobot dataset guidancepreferably two views totalPlus: leader arm should not appear in frame, at least 480x640
json
{
    "video": {
        "front": {
            "original_key": "observation.images.front"
        },
        "wrist": {
            "original_key": "observation.images.wrist"
        }
    }
}
The video section of examples/SO100/modality.json in NVIDIA's Isaac-GR00T repository, checked 2026-08-24. Two keys, no more.

The matching modality config says what each one is for, and the inline comment is the clearest one-line statement of the idea. Note the second detail: the action horizon is 16 steps, and the open-loop evaluation in the same example runs an execution horizon of 16. That is a very different posture from ACT here, which defaults to chunkSize 100 and nActionSteps 100.

python
so100_config = {
    # Video: current frame only; keys must match "video" entries in meta/modality.json
    "video": ModalityConfig(
        delta_indices=[0],
        modality_keys=["front", "wrist"],  # front third-person view + wrist egocentric
    ),
    ...
    "action": ModalityConfig(
        delta_indices=list(range(0, 16)),  # predict 16 future steps
        ...
    ),
}
examples/SO100/so100_config.py, Isaac-GR00T, abridged. The comment is NVIDIA's, not ours.

Zhao and colleagues define chunking precisely in the ACT paper: every k steps the agent receives an observation, generates the next k actions, and executes them in sequence. Their ablation shows why you want k large, success climbing from 1 percent at k=1 to 44 percent at k=100, and why not too large, with a dip at k=200 and k=400 they attribute to the lack of reactive behaviour. Read that through the occlusion lens: action chunking means the arm is deliberately blind for the length of a chunk. At 30 frames per second, 100 actions is over three seconds of committed motion.

Recording an SO-100 dataset that survives occlusion

The observation space is decided at record time and frozen there. You cannot add a wrist camera at training time because the frames do not exist, and you cannot drop a camera at inference time because the policy was fitted to specific keys. Decide before episode one. Below is the manual path with the LeRobot CLI; the desktop client records the same format if you would rather not manage ports and indices by hand.

  1. 1
    Find your cameras and write the indices down

    LeRobot ships auto-discovery. Run it with both cameras plugged in, in the order you intend to keep.

    bash
    lerobot-find-cameras opencv
    # or: lerobot-find-cameras realsense
  2. 2
    Mount the wrist camera so it sees the fingers

    The useful framing includes both jaws and roughly 10 cm in front of them. If the fingertips are out of frame, the view cannot supply grasp timing, which is the main reason you added it. Aim the scene camera so the workspace and the receptacle stay visible at full arm extension.

  3. 3
    Run the human test

    The LeRobot recording guide states the rule that matters: you should be able to do the task yourself by only looking at the camera images. Open both streams, cover your view of the real table, and talk through the grasp. If you cannot tell when to close the gripper, neither can the policy.

  4. 4
    Record with both cameras declared

    Camera names become dataset keys, so pick them once. front and wrist match NVIDIA's SO-100 example, which saves a conversion later.

    bash
    lerobot-record \
      --robot.type=so100_follower \
      --robot.port=/dev/ttyACM0 \
      --robot.id=my_follower \
      --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}, wrist: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}}" \
      --teleop.type=so100_leader \
      --teleop.port=/dev/ttyACM1 \
      --teleop.id=my_leader \
      --display_data=true \
      --dataset.repo_id=${HF_USER}/occlusion-pick \
      --dataset.num_episodes=50 \
      --dataset.single_task="Pick the red cube out of the bin"
  5. 5
    Vary what gets occluded, not just where the object sits

    The LeRobot guide suggests at least 50 episodes with 10 per location for a basic pick and place. For an occlusion-heavy task, spend that budget on approach angles that put the arm between camera and target in different ways, and on starts where the object begins partly behind something.

  6. 6
    Read the cadence report before you train

    lerobot-record prints what the loop achieved. Two streams usually push the observe step to the largest share of the loop, and a session that ran at 25 Hz still produces a dataset that claims 30, because timestamps come from the frame index. That mislabelled speed is a training problem you inherit for free.

The trap that eats a day: camera indices are not stable

The LeRobot camera documentation says it outright: the identifiers may change after rebooting your computer or re-plugging the camera, depending on your operating system. So index_or_path: 0 is your front camera on Monday and your wrist camera on Tuesday. Nothing errors. The key observation.images.front now holds wrist frames for half your episodes, and the trained policy behaves exactly like one that cannot see: confident, smooth, wrong. On Linux, pin the devices by stable path (/dev/v4l/by-id/...) instead of an integer, and eyeball both streams with --display_data=true for the first ten seconds of every session. Mechanics on camera not detected.

The AY-Robots record-your-first-dataset tutorial page, showing the LeRobot dataset recording workflow step by step
The recording tutorial. The decision that matters for occlusion happens before the first episode: how many cameras, where, and what you call them.

Two ways to get there

Everything above runs on your own machine with the LeRobot CLI and, for GR00T, a clone of NVIDIA's Isaac-GR00T repository. You own the ports, the camera indices, the dataset version and the GPU.

  1. Install LeRobot, calibrate leader and follower, find the camera indices.
  2. Record with two named camera keys. Push to the Hub or keep it local.
  3. For GR00T: convert the dataset down to LeRobot v2.1, copy the SO-100 modality.json into meta/, and launch the finetune script with --modality-config-path pointing at so100_config.py.
  4. Rent or own an 80 GB card for GR00T and Pi0.5, or a 24 GB card for SmolVLA and ACT.
  5. Serve the checkpoint next to the robot and run lerobot-rollout, or the SO-100 eval client from the Isaac-GR00T repo.
bash
# NVIDIA's own SO-100 closed-loop eval client, two cameras
WRIST_CAM_IDX=2
FRONT_CAM_IDX=6

uv run --no-sync python eval_so100.py \
  --robot.type=so101_follower \
  --robot.port=/dev/ttyACM2 \
  --robot.id=orange_follower \
  --robot.cameras="{ front: {type: opencv, index_or_path: $FRONT_CAM_IDX, width: 640, height: 480, fps: 30}, wrist: {type: opencv, index_or_path: $WRIST_CAM_IDX, width: 640, height: 480, fps: 30}}"
From examples/SO100/README.md in Isaac-GR00T. The camera keys here must match the dataset keys exactly.
The version trap

A LeRobot v3.0 dataset crashes the GR00T loader. It has to be converted down to v2.1 first. If your job dies on load, start at dataset rejected: v3.

The blackout test: which view is actually driving the policy

Before concluding anything about occlusion, find out whether your checkpoint uses the images at all. This is offline. Nothing moves, and it takes about a minute per episode. Replay a recorded episode through the policy twice: once intact, once with one camera stream replaced by zeros. Same frames, same state, one difference.

python
import numpy as np
import torch

# policy is a LeRobot PreTrainedPolicy; episode yields frames from a
# recorded LeRobotDataset. Nothing here touches the robot.
deltas = {"front": [], "wrist": []}

for step in episode:
    batch = {
        "observation.images.front": step["front"],
        "observation.images.wrist": step["wrist"],
        "observation.state": step["state"],
    }
    a_full = policy.select_action(dict(batch))

    for view in ("front", "wrist"):
        key = f"observation.images.{view}"
        blinded = dict(batch)
        blinded[key] = torch.zeros_like(batch[key])
        a_blind = policy.select_action(blinded)
        deltas[view].append((a_full - a_blind).abs().mean().item())

for view, d in deltas.items():
    print(view, "mean |delta action| =", np.mean(d))
select_action is the real LeRobot policy API (lerobot/policies/pretrained.py). Reset the action queue between calls for policies that keep one.

Read it like this. A mean absolute action delta near zero for a view means that view is not driving the policy, and better camera placement will not help until the training data gives it a reason to. A large delta on wrist concentrated in the gripper dimension is the healthy pattern for a pick task. A large delta on front and near-zero on wrist usually means the wrist camera was mounted too close or too far and never showed the fingers. Rerun after every fine-tuning run; it is the cheapest regression check in the stack.

What the second camera costs at inference time

More views means more vision tokens, and vision tokens are most of the forward pass in a VLA. The published numbers are not always measured the way you will run the model: the GR00T N1.7 model card's timing table is explicitly labelled as 4 denoising steps and 1 camera. Your two-camera deployment is not that configuration. Here is what this platform states per action step, which is the number to plan the control loop around.

PolicyInference per action stepGPU tierMinimum episodes
ACT20 msRTX 4090 or any 24 GB card50
GR00T N1.7152 msA100 80 GB or H100 80 GB50
GR00T N1.5165 msA100 80 GB or H100 80 GB50
SmolVLA245 msRTX 4090 or any 24 GB card30
Pi0.5485 msA100 80 GB or H100 80 GB50
Occlusion and latency compound

The control loop is 20 to 485 ms per action step depending on the model. Adding public-internet round trips turns a working policy into a hesitant one, and a hesitant policy under occlusion is exactly the case where the arm commits to a stale plan. Remote inference is viable for slow pick-and-place, not for fast reactive motion. If the task needs the policy to react the moment the object reappears, inference has to sit next to the servos. Numbers per model on the policies page, head to head on GR00T N1.7 against Pi0.5.

The AY-Robots SO-100 hub page with links to setup, data collection, imitation learning and LeRobot guides
The SO-100 hub. Camera mounting, dataset recording and the LeRobot toolchain start here, and the occlusion decision is upstream of all three.

What none of this fixes

A wrist camera and a well-recorded dataset move the failure rate. They do not remove the problem, and four things stay out of reach.

  • Total occlusion is still total. Fully inside a closed gripper or fully behind a wall, no camera arrangement recovers it. The best you get is a correct prior about where it was, which is a data problem.
  • Nothing here gives the policy memory. The GR00T SO-100 example uses delta_indices=[0] for video, the current frame only. A single-frame policy cannot remember that the cube was there two seconds ago.
  • The platform cannot repair an observation space you already froze. Recording without a wrist camera and then wanting one means recording again. There is no upgrade path in the dataset format.
  • Simulation results do not transfer as numbers. The LIBERO-Occ drops are a shape, not a forecast for your table and your lighting.

For the wider data argument, how to collect high-quality VLA training data covers what else goes wrong in recording, and the DROID walkthrough is the closest thing to a reference rig at scale. For hardware, the complete SO-100 setup guide covers mounting and calibration, the SO-100 data collection page is the short version, and every named failure mode lives on the fix index.

The arm reaches for the wrong spot and nothing errors

Occlusion symptoms have names: the gripper closes early, the policy only works in one setup, the arm freezes mid-motion. Each has a page with the actual cause and the actual fix.

Open the failure-mode index

Frequently asked questions

I recorded 60 episodes with one camera. Can I add a wrist camera and keep the data?

Not for the same policy. The wrist frames do not exist in those episodes, so no training run can use them. Keep the old set as a one-camera baseline and record a new two-camera set alongside it. The minimum here is 50 episodes for GR00T N1.7, GR00T N1.5, Pi0.5 and ACT, and 30 for SmolVLA.

Can I train with two cameras and run with one if the wrist camera fails?

No. The policy expects the keys it was fitted to. Feeding a black frame for the missing view is exactly the blackout test above, and the result is a policy acting on an observation it never saw in training. Use it as a diagnostic, not a deployment mode.

If I can only mount one camera, which one?

The third-person one, and it is not close. The ICLR 2022 result favours the hand-centric view only when hand-centric observability is sufficient, meaning the wrist camera can see the target at the start of the episode. On most tabletop pick tasks it cannot: the object is somewhere on the table and the gripper starts at home. Get the scene view working first, then add the wrist view as the second camera rather than as a replacement.

Does GR00T N1.7 accept two camera views on an SO-100?

Yes. NVIDIA's SO-100 example in the Isaac-GR00T repository declares exactly two video keys, front and wrist, in both meta/modality.json and the modality config, described in the code comment as the third-person view and the wrist egocentric view. The model card lists vision input as a variable number of uint8 image frames from robot cameras. GR00T needs LeRobot v2.0 or v2.1 data, so a v3.0 dataset has to be converted down first.

Would a third camera help more?

Sometimes, and never for free. ALOHA runs four and DROID runs three, but both are far more expensive rigs. The SmolVLA authors mapped community datasets to a standardised order of top, wrist and side views and dropped the extra views during training, which says something about the marginal value of view three at this scale. Against that you pay encode time, disk, and vision tokens on a loop that is already 20 to 485 ms.

Ready for high-quality robotics data?

AY-Robots connects your robots to skilled operators worldwide.

Get Started