The AY-Robots tutorial page for recording your first LeRobot dataset from a teleoperation session with the desktop client.
data augmentationimitation learningGR00T N1.7lerobotpolicy training

Data Augmentation for Robot Learning: What Helps, What Breaks

AY-Robots ResearchAugust 23, 202617 min read

Colour jitter and small random crops help manipulation policies. Flips, rotations and large shifts can break them, because the action label is tied to the image geometry.

In image classification, augmentation is nearly free. A cat rotated by fifteen degrees is still a cat, so you flip, rotate, crop and jitter, and the label survives. Manipulation policies are not built that way. The label is an action, usually joint targets or an end effector delta, and it lives in the robot's frame. The image lives in the camera's frame. Augmentation assumes those frames are independent. They are not.

What follows is what the shipped stacks actually apply, read from published config or source on 23 August 2026: the numbers inside the GR00T N1.7 processor config, the augmentation code printed in the Pi0.5 appendix, lerobot's default transform set, and the ablations that say which of these earn their place. Then the part nobody writes down: which augmentations corrupt the action label on a single-arm setup like the SO-100.

What you need to know

  • Photometric augmentation (brightness, contrast, saturation, hue) is safe. It changes pixels, not geometry, so the recorded action stays correct.
  • Small random crops are the one geometric augmentation everyone ships. GR00T N1.7 and Pi0.5 keep 95 percent of the frame, Diffusion Policy and robomimic keep 90 percent.
  • robomimic measured the cost of removing pixel shift randomisation: 47 percent relative drop on Square, 35 percent on Transport.
  • Rotation is off or tiny. GR00T N1.7 ships random_rotation_angle = 0; Pi0.5 and lerobot's default affine cap at 5 degrees.
  • Horizontal flips appear in none of the shipped manipulation recipes read here. BC-Z reflects only its human video branch.
  • Enabling lerobot image transforms also enables a RandomAffine entry that fires on half your frames, drawn independently per camera.
  • Augmentation buys robustness to lighting and colour, not a new camera angle, object or grasp.

Why a cat can be flipped and a grasp cannot

One test settles every candidate: after the transform, is the recorded action still correct for the transformed image? Colour jitter passes. A 20 percent brighter scene still needs the gripper to close in the same place at the same moment. Horizontal flip fails. In the flipped image the cube sits on the left, but the recorded action still reaches right.

Nothing crashes. You get label noise, and label noise in imitation learning has a signature on the real arm: training loss falls to a healthy-looking number, then the policy approaches the object and hesitates. A model trained on contradictory image-action pairs averages them, and the average of reaching left and reaching right is stopping in the middle. That is the symptom on loss falls, policy does nothing.

The invariance test

An operation is an augmentation only if the recorded action stays correct after it. Otherwise it is label noise with a useful-sounding name. Photometric changes almost always pass. Geometric changes need the action transformed too, and on a single arm with an off-centre camera there usually is no correct action transform to apply.

What the shipped stacks actually apply

Rather than argue from first principles, here is what five widely used stacks send to the model. Treat the version labels as load-bearing: this part of every repo moves.

Stack (version read)CropRotationFlipColour jitter
GR00T N1.7 (processor_config.json)crop_fraction 0.95, shortest edge 256random_rotation_angle = 0nonebrightness 0.3, contrast 0.4, saturation 0.5, hue 0.08
GR00T N1.5 (Isaac-GR00T, n1.5-release)VideoCrop scale 0.95VideoRandomRotation exists, no data config uses itVideoHorizontalFlip exists, no data config uses itsame four numbers, all seven data configs
Pi0.5 (arXiv 2504.16054, appendix)RandomCrop at 95 percent, then resize backRotate(-5, 5) degreesnonebrightness 0.3, contrast 0.4, saturation 0.5
lerobot defaults (v0.5.1 and main, off unless enabled)noneRandomAffine degrees (-5.0, 5.0), translate 0.05noneColorJitter on all four channels, plus SharpnessJitter
Diffusion Policy / robomimicrandom crop to 90 percent, centre crop at evalnonenonenone

Two things stand out. Brightness 0.3, contrast 0.4 and saturation 0.5 appear verbatim in both NVIDIA's and Physical Intelligence's stacks. That is an observation, not a causal claim, but when two labs that do not share code land on the same three numbers it is a place to start. The two models sit side by side on the GR00T N1.7 against Pi0.5 comparison.

python
# arXiv 2504.16054, pi-0.5 appendix, applied to all input images in this order
transforms = [
    augmax.RandomCrop(int(width * 0.95), int(height * 0.95)),
    augmax.Resize(width, height),
    augmax.Rotate((-5, 5)),
    augmax.ColorJitter(brightness=0.3, contrast=0.4, saturation=0.5),
]
Physical Intelligence prints its entire image augmentation stack. Four lines, one of them geometric, capped at 5 degrees.

GR00T publishes the same information in a less quotable place: the processor config inside the base checkpoint. Fine-tune GR00T N1.7 without overriding anything and these are the values that run.

json
// nvidia/GR00T-N1.7-3B, processor_config.json -> processor_kwargs
"crop_fraction": 0.95,
"shortest_image_edge": 256,
"image_target_size": [256, 256],   // legacy fallback, unused when crop_fraction is set
"image_crop_size": [230, 230],     // legacy fallback, unused when crop_fraction is set
"random_rotation_angle": 0,
"color_jitter_params": {
  "brightness": 0.3, "contrast": 0.4, "saturation": 0.5, "hue": 0.08
},
"use_albumentations": true,
"state_dropout_prob": 0.2
Read from the base checkpoint on 23 August 2026. The two legacy size fields are consulted only when crop_fraction and shortest_image_edge are unset.
The AY-Robots policy comparison table showing the five trainable policies with parameter counts, GPU tier, inference latency and minimum episode count.
Each of the five trainable policies brings its own augmentation defaults. GR00T and Pi0.5 crop and jitter by default, ACT and SmolVLA apply nothing unless you switch it on.

Colour jitter: the cheap win

Photometric augmentation is the one nobody argues about. It passes the invariance test trivially and targets the failure mode that bites on a desk robot: you record in the afternoon, run the checkpoint in the evening, the light has changed, and the policy behaves differently. A vision-language-action model with a pretrained backbone is fairly robust to that, but fine-tuning is where you teach it that your lighting is part of the task.

There is a conversion trap. GR00T and Pi0.5 quote jitter as torchvision-style magnitudes, where brightness 0.3 means a factor sampled uniformly from 0.7 to 1.3. lerobot's transform config wants explicit ranges. lerobot's own GR00T documentation translates between the two, and the translation is exact.

ParameterGR00T magnitudeRange it implieslerobot tfs kwargs in the GR00T doc
brightness0.30.7 to 1.3[0.7, 1.3]
contrast0.40.6 to 1.4[0.6, 1.4]
saturation0.50.5 to 1.5[0.5, 1.5]
hue0.08-0.08 to 0.08[-0.08, 0.08]

That is not a coincidence either. When lerobot added a GR00T training path, its documented recipe rebuilds NVIDIA's jitter by hand and throws away the rest of the default set while doing it.

bash
# from the lerobot GR00T policy docs, the LIBERO training recipe
IMAGE_TRANSFORMS='{
  "brightness": {"weight": 1.0, "type": "ColorJitter", "kwargs": {"brightness": [0.7, 1.3]}},
  "contrast":   {"weight": 1.0, "type": "ColorJitter", "kwargs": {"contrast":   [0.6, 1.4]}},
  "saturation": {"weight": 1.0, "type": "ColorJitter", "kwargs": {"saturation": [0.5, 1.5]}},
  "hue":        {"weight": 1.0, "type": "ColorJitter", "kwargs": {"hue":        [-0.08, 0.08]}}
}'

lerobot-train \
  --dataset.repo_id=IPEC-COMMUNITY/libero_spatial_no_noops_1.0.0_lerobot \
  --dataset.image_transforms.enable=true \
  --dataset.image_transforms.max_num_transforms=4 \
  --dataset.image_transforms.tfs="$IMAGE_TRANSFORMS" \
  --policy.type=groot \
  --policy.base_model_path=nvidia/GR00T-N1.7-3B
Four colour transforms, max_num_transforms raised to 4, no geometric entry anywhere. The results lerobot reports for this recipe, labelled preliminary, are 95 percent on LIBERO Spatial and 96.5 percent averaged over the four suites.
Why max_num_transforms matters

lerobot samples a subset of the configured transforms per call and the default subset size is 3. Supply four transforms and leave the default, and one is dropped at random every time. The GR00T recipe raises it to 4. Read from lerobot main on 23 August 2026.

Random crop: keep it small, and centre it at evaluation

Random crop is the exception in the geometric family and it has the strongest published evidence behind it. In the robomimic study, removing pixel shift randomisation caused a 47 percent relative drop on Square and 35 percent on Transport. The implementation is deliberately conservative: 76 from 84 pixels, 108 from 120, 216 from 240.

Diffusion Policy adopted the same approach and adds the detail that matters more than the crop size: at inference it takes a static centre crop of the same size. Across four independent stacks the crop lands in a narrow band.

  • robomimic: 76 of 84, 108 of 120, 216 of 240 pixels, a consistent 90 percent.
  • Diffusion Policy: 2x84x84 becomes 2x76x76 in simulation, 2x320x240 becomes 2x288x216 on the real robot. Also 90 percent.
  • GR00T N1.7: shortest edge resized to 256, then crop_fraction 0.95, so a 256 by 256 view keeps a 243 by 243 window before being resized back.
  • Pi0.5: 95 percent, then resized back to the original width and height.
Random at training, centred at evaluation

Every stack that crops randomly during training switches to a deterministic centre crop for evaluation. GR00T builds two pipelines for exactly this reason, one with FractionalRandomCrop and one with FractionalCenterCrop. Crop randomly at inference and you inject viewpoint jitter at the moment the gripper needs precision. Skip the crop entirely and you change the field of view relative to training, which is worse.

Geometric augmentation and the action label

Now the part that costs people a weekend. Crop works because it is a small, zero-mean perturbation that keeps the object roughly where it was and is undone by a centre crop at test time. The rest of the geometric family lacks that property.

Flips

A horizontal flip looks like it doubles the dataset for free. It does not. The action in a LeRobot episode is a command that was actually sent to the arm, so mirroring the image without mirroring the action produces a frame whose correct answer is the mirror of its label. Mirroring the action too requires a symmetry a single arm does not have.

BC-Z is the interesting data point, because it uses reflections and is careful about where. Its policy input gets random crops, downsampling and standard photometric augmentation. Random reflections along the x and y axes go only to the human demonstration videos feeding the task embedding, sampled once per video, applied identically to all 20 frames, and none at inference. Longer version in the BC-Z write-up.

Rotation

Rotation about the optical axis means physically rolling the camera. For a fixed third-person camera that is a pose the robot will never see. For a wrist camera it is worse: the image really does rotate when the wrist rotates, and that rotation correlates with the joint state the policy is predicting, so fake rotation puts noise on top of a genuine cue. GR00T N1.7 ships random_rotation_angle at 0 while keeping the code path.

Translation and the wrist camera

Translation is random crop's less careful cousin. A few percent is the same regulariser: it stops the encoder keying on absolute pixel coordinates. Beyond that it breaks the mapping from image position to workspace position, which is what the policy uses in the final centimetres of a grasp. robomimic measured the wrist view too: removing the wrist camera caused a 43 percent relative drop on Transport.

Geometric augmentation on a manipulation dataset
What it buys you
  • Small crops and shifts stop the encoder memorising absolute pixel positions, the best-evidenced augmentation result in manipulation.
  • No extra recording time and no extra GPU memory.
  • The cheapest defence against a camera nudged between recording and deployment.
  • Every major stack ships some version of it.
What it costs you
  • Past roughly 10 percent of the frame it disagrees with the action label instead of regularising.
  • Flips and large rotations create pairs that contradict the dataset, and the model averages the contradiction into hesitation.
  • It cannot invent the parallax of a new camera angle.
  • On wrist cameras it perturbs the signal that controls grasp alignment.
The trap that eats a day

Passing --dataset.image_transforms.enable=true to lerobot-train does not just turn on colour jitter. The default set has six entries and one is RandomAffine with degrees=(-5.0, 5.0) and translate=(0.05, 0.05). Because max_num_transforms defaults to 3 and all six weights are 1.0, three of six are drawn without replacement per call, so the affine fires on half your frames. The draw happens once per camera key, so front and wrist get different rotations of the same instant. Verified against v0.5.1 and main.

One draw per sample, or one per camera?

This detail is invisible in the config and shows up in behaviour. GR00T applies augmentation through an albumentations ReplayCompose: the first image creates the random parameters and every later image replays them. The handle is created once and carried across the loop over camera views, so front and wrist views of the same moment get the same crop offset and the same colour shift.

python
# huggingface/lerobot, src/lerobot/datasets/dataset_reader.py, main, 23 Aug 2026
if self._image_transforms is not None:
    for cam in self._meta.camera_keys:
        if cam in self._meta.depth_keys:
            continue
        item[cam] = self._image_transforms(item[cam])

# NVIDIA/Isaac-GR00T, gr00t/model/gr00t_n1d7/processing_gr00t_n1d7.py, main, 23 Aug 2026
replay = None
for view in image_keys:
    transformed_images, replay = apply_with_replay(
        image_transform, view_images, view_masks, replay
    )
Two defensible designs. lerobot calls the transform once per camera key, so each call draws fresh parameters. GR00T draws once and replays across every view.

Which you want depends on what you are making the model robust to. Independent draws simulate cameras with different white balance and mounting. Shared draws keep multi-view geometry self-consistent, which matters when the policy fuses views to localise one object. Neither is wrong. What is wrong is not knowing which your stack does, then wondering why colour augmentation on a two-camera LeRobot dataset behaves differently from the single-camera run you tested.

Look at the frames before you spend a GPU hour

Both stacks ship a preview tool and both take under a minute. Augmentation bugs are obvious in a contact sheet and invisible in a loss curve.

  1. 1
    Render lerobot's transforms on your own dataset

    Writes one PNG per example under outputs/image_transforms, once for the combined pipeline and once for each transform alone. Note the extra: the viewer scripts sit behind dataset_viz, not training.

    bash
    pip install "lerobot[dataset_viz]"
    
    lerobot-imgtransform-viz \
      --repo_id=lerobot/pusht \
      --episodes='[0]' \
      --image_transforms.enable=True
  2. 2
    Preview GR00T's mask-guided augmentation

    Writes side by side comparisons of original, augmented and mask under output_dir/<view_name>/. It needs a dataset with masks, and the repo ships one.

    bash
    cd examples/mask-guided-background-suppression
    
    uv run python test_extra_augmentation.py \
      --dataset_path ../../demo_data/cube_to_bowl_5_with_mask \
      --embodiment_tag NEW_EMBODIMENT \
      --modality_config_path so101_config.py \
      --extra_augmentation_config '{"background_noise_transforms": [{"target_mask_values": [0], "p": 1.0}]}' \
      --output_dir /tmp/augmentation_vis --num_frames 5
  3. 3
    Change the jitter on a real GR00T fine-tune

    examples/finetune.sh forwards to launch_finetune.py, a tyro CLI over FinetuneConfig. The colour jitter default is written out in the shell script, so you see what you override. crop_fraction and shortest_image_edge are documented as a pair.

    bash
    # examples/finetune.sh default, verbatim:
    # COLOR_JITTER_PARAMS="brightness 0.3 contrast 0.4 saturation 0.5 hue 0.08"
    
    CUDA_VISIBLE_DEVICES=0 NUM_GPUS=1 uv run bash examples/finetune.sh \
      --base-model-path nvidia/GR00T-N1.7-3B \
      --dataset-path examples/SO100/finish_sandwich_lerobot/izuluaga/finish_sandwich \
      --modality-config-path examples/SO100/so100_config.py \
      --embodiment-tag NEW_EMBODIMENT \
      --shortest-image-edge 256 \
      --crop-fraction 0.9 \
      --color-jitter-params "brightness 0.4 contrast 0.4 saturation 0.5 hue 0.08" \
      --output-dir /tmp/so100_finetune
You cannot A/B a GR00T run cleanly

The Isaac-GR00T fine-tuning entry point exposes no seed, so two runs with identical settings are not bit-for-bit identical and a small difference between an augmented and an unaugmented run proves nothing. lerobot has a seed and defaults it to 1000, which makes ACT and SmolVLA the better platform for a controlled augmentation experiment.

Two ways to run the comparison

Whether augmentation helps on your task has a cheap answer: train the same dataset twice and drive the arm. What differs is how much plumbing sits between you and that answer.

You clone the trainer, mount the dataset, rent or own the GPU, and run the pair yourself. This is the only path if you want augmentation parameters the trainer form does not expose, which today is all of them.

  1. 1
    Convert the dataset if you are training GR00T

    The GR00T loader wants LeRobot v2.x. The converter carries its own pyproject, so run it from its own directory.

    bash
    cd scripts/lerobot_conversion
    uv venv
    source .venv/bin/activate
    uv pip install -e .
    
    python convert_v3_to_v2.py --repo-id <your-user>/<your-dataset>
  2. 2
    Run the baseline with augmentation off

    For ACT or SmolVLA this is the default, since image transforms are off unless enabled. Fix the seed so the comparison means something.

    bash
    lerobot-train \
      --dataset.repo_id=<your-user>/<your-dataset> \
      --policy.type=act \
      --seed=1000 \
      --output_dir=outputs/train/act_noaug
  3. 3
    Run the same thing with colour only

    Supply your own tfs dict rather than only flipping enable to true, so the default RandomAffine does not join uninvited.

    bash
    lerobot-train \
      --dataset.repo_id=<your-user>/<your-dataset> \
      --dataset.image_transforms.enable=true \
      --dataset.image_transforms.max_num_transforms=4 \
      --dataset.image_transforms.tfs="$IMAGE_TRANSFORMS" \
      --policy.type=act \
      --seed=1000 \
      --output_dir=outputs/train/act_colour
  4. 4
    Evaluate on the arm, not on the loss

    Augmentation raises training loss and is supposed to. Judge it by success rate under changed light or a moved lamp.

Budget it before you start

Two runs, not one. If the difference matters it is worth the second GPU hour; if it does not, spend the time recording more episodes.

What augmentation will not fix

Augmentation operates on pixels that already exist. It cannot create parallax, it cannot create a grasp you never demonstrated, and it cannot resolve a contradiction inside your dataset. The training documentation lists what each trainer runs.

  • A new camera angle. Cropping a fixed view does not produce the occlusion pattern of a different viewpoint. Move the camera and record again.
  • A new object or background. GreenAug is the sharpest data point: record in front of a green screen, then chroma-key real backgrounds in. Its best variant improved on no augmentation by about 65 percent, on standard computer vision augmentation by about 29 percent and on generative augmentation by about 21 percent, all relative, across over 850 demonstrations and 8.2k evaluation episodes.
  • Inconsistent camera assignment. Swapped front and wrist streams are a device identification problem, the same family as a camera that is not detected.
  • Too few episodes. GR00T and Pi0.5 want at least 50 episodes here, SmolVLA at least 30.
  • A policy that only works in one setup. Start at that failure mode page.

Latency deserves plain speech, because it is the limit of remote inference generally. The per-step inference latency of these models ranges from 20 ms for ACT to 485 ms for Pi0.5, and public internet round trips on top turn a working policy into a hesitant one. Remote inference is viable for slow pick and place, not fast reactive motion.

The AY-Robots public dataset directory listing recorded LeRobot datasets.
Before tuning augmentation, look at the data. Most robustness problems are visible in the episodes themselves.

RT-1 is a useful sanity check. Its paper describes the input pipeline in detail, six images at 300 by 300 into an ImageNet-pretrained EfficientNet-B3, and never describes an augmentation stack at all. What it does ablate is the pretrained initialisation: removing ImageNet pre-training decreased unseen task performance by 33 percent. Backbone and data first, pixel transforms second. See how to collect high quality VLA training data and the broader VLA overview.

Defaults worth starting from

For a single arm with one fixed camera and one wrist camera, this is the position supported by everything above.

AugmentationSettingWhy
Brightness / contrast / saturation0.3 / 0.4 / 0.5 magnitudesTwo independent stacks converged on these.
Hue0.08GR00T ships it, Pi0.5 leaves it out.
Random crop0.90 to 0.95 of the frameBest-evidenced augmentation in manipulation.
Centre crop at evaluationSame size as the training cropTrain and eval must see the same field of view.
Rotation0, or at most 5 degreesGR00T ships 0. Nothing read here goes beyond 5.
Translation / affineOff, unless it is the cropRedundant with the crop and harder to bound.
Horizontal flipOffIt contradicts the action label on an asymmetric workspace.
Background replacementOnly with a green screen or masksLargest measured win in GreenAug, and the most setup work.
The cheapest experiment first

Before touching any of this, train once with the defaults your model already ships and drive the arm under two lighting conditions. If it holds up, augmentation is not your bottleneck and the next twenty episodes are worth more than the next twenty parameter tweaks. Start at train your first policy or compare options on the policies page.

Train the same dataset with the defaults that ship

Pick a model and an arm, and the guide shows the exact hyperparameters the trainer sends, including the augmentation each stack applies by default. GR00T N1.7, GR00T N1.5, Pi0.5, SmolVLA and ACT, on SO-100, SO-101, Koch v1.1 and LeKiwi.

Open the training guides
Does colour jitter ever hurt a manipulation policy?

In one case: when colour is the task signal. If the policy must pick the red block from the blue block, a saturation range of 0.5 to 1.5 plus a hue shift of plus or minus 0.08 can push a washed-out red towards something the model reads as orange. For colour-discriminative tasks, keep brightness and contrast and shrink hue and saturation. Otherwise the 0.3 / 0.4 / 0.5 / 0.08 that GR00T ships is a reasonable start.

Can I mirror my dataset to double the number of episodes?

Not without mirroring the actions, and on a single arm there is usually no correct mirror: the joint that swings left is not the joint that swings right, and the camera is not on the symmetry axis. BC-Z, which does use reflections, applies them only to the human video branch that produces the task embedding, never to the policy's image input.

How much does random crop help in practice?

The best measurement available is robomimic's: removing pixel shift randomisation caused a 47 percent relative drop on Square and 35 percent on Transport. That is a simulation benchmark with a specific architecture, so treat it as an order of magnitude. The consistent part across stacks is the size, 90 to 95 percent of the frame, with a deterministic centre crop at evaluation.

Does the AY-Robots training form let me change augmentation?

No. The extra knobs are saveSteps for the GR00T models, seed and logFreq for Pi0.5 and SmolVLA, and chunkSize, nActionSteps, seed and logFreq for ACT. Augmentation runs at each trainer's default. To sweep jitter ranges or crop fractions, run the trainer yourself with the commands above.

Why does GR00T apply the same crop to both cameras?

It uses an albumentations ReplayCompose and carries one replay handle across the loop over camera views, so parameters drawn for the first image are reused for every other view and timestep in that sample. Its mask-guided example states the intent: standard augmentations stay consistent across camera views within the same timestep, while mask-based transforms run per frame. lerobot calls its transform once per camera key, so each camera draws fresh parameters.

Sources

Ready for high-quality robotics data?

AY-Robots connects your robots to skilled operators worldwide.

Get Started