ITRI Internship - Quadruped Vision Locomotion
A reinforcement-learning locomotion policy for the Unitree Go2 quadruped. From one small forward depth camera (16 × 16 pixels) and its own joint sensors, the robot learns to climb stairs, jump onto and off a 40 cm box, leap gaps up to 0.9 m and cross narrow beams. The goal was to improve the Go2’s real-world performance on tasks like stairs and obstacles, so every skill was trained in Isaac Gym, checked sim-to-sim in Gazebo through the robot’s C++ controller, and then run on the real robot.
Research internship at ITRI (Industrial Technology Research Institute), Taiwan, May to October 2026.
My role: I built the training environment (procedural terrains, reward functions, curriculum and domain randomization for every skill) and the GRU memory module that carries terrain across frames. I also reworked the deployment pipeline: the existing sim-to-sim and real-robot code was written for another model, so I adapted the export, depth preprocessing and controller to this policy.
Tools: PyTorch, Isaac Gym, PPO, NVIDIA Warp ray casting, TorchScript, ROS 2 Humble, Gazebo, rl_sar, Docker, TensorBoard
| Real stairs | Box jump | Approach yaw | Open-riser stairs | Gap leap | Fall recovery |
|---|---|---|---|---|---|
| 18 cm | 40 cm | ±20° | 17 cm | 80% | 100% |
| ascent and descent on the real Go2 | onto and off, in Gazebo and on the real Go2 | box mount with no steering correction | suspended 4 cm treads, in Gazebo | from 0.3%, on 0.1–0.9 m gaps (Isaac) | self-righting in 1.4 s (Isaac) |
Videos
Approach
I started from MGDP, a research framework that compresses depth into a compact terrain code, and adapted it to the Go2. The policy combines a small visual token with its own body state. Signals that only exist in simulation shape training and are dropped at deployment.
Figure 1. Training in Isaac Gym (top) and the deployment path (bottom). The exported policy and depth encoder run in the rl_sar C++ controller, first in Gazebo and then on the real Go2.
- Vision: two 16 × 16 depth frames go through a CNN encoder that denoises and compresses them, then a GRU memory that keeps track of the terrain under the body, producing a 64-D visual token.
- Body: 45-D proprioception (joints, IMU, velocity command) goes through a state estimator over the last 4 observations, producing a 7-D body token.
- Action: an actor MLP (116 → 512 → 256 → 128) outputs 12 PD joint targets.
- Training only: a height-map encoder aligned with 32 of the visual latent dimensions through a contrastive (InfoNCE) loss, clean-depth and future-depth reconstruction, and a critic that also sees true velocity, foot heights, terrain heights and the randomized physics. Training runs in three phases: depth encoder, then GRU memory, then policy. The auxiliary visual-memory losses were designed by Shih-Ting Yang.
Training runs 4,096–6,144 robots in parallel, and the final policy comes from one checkpoint lineage of about 566k PPO iterations.
Process: one policy, skill by skill
Every stage adds one family of skills to the same policy without retraining from scratch. The hard part is keeping old skills while learning new ones.
| When | Stage | Measured | What I learned |
|---|---|---|---|
| Jun | Flat ground. Omnidirectional gait: up to ±0.6 m/s forward, ±1 m/s sideways, ±1 rad/s turning. | 4 failure modes (hopping, wide-stance crawl, nose-dive, rear-foot drag), each removed in turn | Over-constraining one joint creates the next failure. The rear-foot drag came from my own thigh penalty. |
| Jun | Stairs, forward only. A height-map encoder trained from scratch; posture penalties relaxed on stairs only. | 1 hip: a steady left veer traced to one rear hip spreading outward | Look at per-joint asymmetry before adding a reward term. Most “drift” problems were one joint. |
| Jun – Aug | Stairs in any direction. Seven sub-stages adding sideways, turning and backward commands, descent and taller steps. | ≥ 15 cm steps: tall-step climbing regressed while I tuned flat-ground behavior | Tuning one skill can quietly break another. Protect each skill’s rewards and curriculum while working on a different one. |
| Jul – Sep | Box jump. Front paws placed on top, a synchronized rear-leg push and a controlled dismount, then widened to ±20° approach angles. | 79% vs 13–19%: front-paw mount rate approaching square-on vs at ±0.2 rad | Reward the successful end state, not the motions that lead to it. Process rewards were farmed by ramming. |
| Sep | Pits and inverse pits. Climbing out of a sunken pit and stepping down into one, sharing one policy with stairs. | 96–99% success, counted only once the robot holds the crossing upright and keeps moving | A success that ends the episode the instant it happens teaches nothing about what comes after. A stricter test is worth a lower number. |
| Oct | Gaps, air beams, open risers. A real leap over gaps up to 0.9 m, first-try footholds on narrow beams, stairs without risers. | 0.3% → 80% gap crossing before and after the leap redesign | The curriculum level is not a success rate. Probe fixed conditions before trusting a dashboard. |
| Oct | Fall recovery. A separate blind policy that rights the robot, with an automatic handover when the body tilts past 60° for 0.3 s. | 100% in 1.4 s in Isaac; 48% once joint-velocity limits were lifted, so I retrained | Smoothness penalties outweighed the roll reward and the robot settled on its side. Gate them on being upright. |
| Nov | Course terrains. Stepping stones, bridges, cross and rotating beams, a crawl slab and a corridor, each with an exact foothold test. | 418 automated logic checks passing before launch; a code review found 6 issues, fixed test-first | Test reward and success logic offline before it costs a week of GPU time. |
The recovery policy is kept separate on purpose: locomotion training ends an episode on body contact, so the walking policy never learns to get up, and a separate policy keeps flips out of its depth and GRU memory. Control hands back to walking once the robot is upright and still for 0.5 s, and the visual memory is reset.
Sim-to-sim: what a skill depends on
The gap leap scored about 80% in Isaac Gym but fell short in Gazebo. To find out why, I re-ran the trained policy in Isaac with one condition changed at a time. Each panel compares the training condition against Gazebo-like ones.
Physics step
Trained at 5 ms. Gazebo runs at 0.5 ms.
- 5 ms (training)
- 2.5 ms
- 1 ms
Actuation delay
Trained with a random 0–20 ms delay.
- Random 0–20 ms (training)
- Fixed 0 ms
- Fixed 20 ms
Foot friction
Training saw ≥ 0.7. Gazebo's feet gave 0.6.
- ≥ 0.7 (training)
- 0.6 (Gazebo)
Figure 2. Gap-crossing success by gap width, from fixed-condition probes of gap-leap checkpoints (485k–502k iterations). The physics-step panel uses a later checkpoint than the other two. Hover a column for exact values.
Show the data as a table
| Condition | 0.3 m | 0.5 m | 0.7 m | 0.9 m |
|---|---|---|---|---|
| Physics step: 5 ms (training) | 100 | 100 | 93 | 77 |
| Physics step: 2.5 ms | 83 | 80 | 47 | 17 |
| Physics step: 1 ms | 33 | 10 | 0 | 0 |
| Actuation delay: Random 0–20 ms (training) | 95 | 93 | 90 | 80 |
| Actuation delay: Fixed 0 ms | 100 | 90 | 23 | 3 |
| Actuation delay: Fixed 20 ms | 60 | 43 | 30 | 40 |
| Foot friction: ≥ 0.7 (training) | 100 | 97 | 93 | 73 |
| Foot friction: 0.6 (Gazebo) | 80 | 73 | 67 | 40 |
What I changed. The leap leaned on PhysX’s 5 ms contact step for extra push-off, so I moved training to a 2 ms step at 2.5× the physics cost. I also randomized actuation delay per robot from 0 to 40 ms and widened foot friction to 0.1–1.3. Checkpoints are gated on a 1 ms-physics probe before they leave Isaac.
Not everything I suspected was real. Capping the calf torque at Gazebo’s limit cut the leap to 33–57% in Isaac, but Gazebo’s controller never actually enforced that cap. I checked before shipping the fix.
Air beams: first-try footholds
Share of steps that land on the beam without a correction.
- Front paws
- Rear paws
Figure 3. Air-beam first-try foothold rate across training. Rewards changed after 459k iterations to stop paying for corrected steps.
Lessons: six bugs, with before and after numbers
Most of the work was finding out why a policy that looked good on paper didn’t do what I expected.
The box-jump level rose while nobody cleared it
0% clean clears while the curriculum level went from 7 to 10.2.
- Cause: four “process” rewards (pre-jump lift, forward leap and so on) were cheaper to farm than the skill, speed tracking still paid while ramming, and the curriculum promoted on distance alone.
- Fix: removed the process rewards, kept a clean-crossing reward, ended the episode on ramming, and required a clean crossing with no body contact for promotion.
- Lesson: reward the precise successful state. Anything easier gets farmed.
It was the approach angle, not the speed
13–19% → 79% front-paw mount rate, at ±0.2 rad yaw vs square-on.
- Cause: I had been tuning speed. Broken down by condition, speed helped (68% at 0.2 m/s → 83% at 0.8 m/s) and angle was the limiter. An apparent regression was a change in the yaw mix.
- Fix: a closed-loop alignment command that charges lateral offset and heading error on the approach.
- Lesson: break a metric down by condition before changing anything.
The gap nobody crossed
0–13% → ~80% crossing rate at every width, before and after the redesign.
- Cause: the gap showed a high curriculum level (13.2), but robots walked in and nose-dived. Base contact ended the episode before the fall penalty fired, and distance promotion rewarded the walk-in.
- Fix: redefined the skill as a leap, with one reward per phase (takeoff, flight, far touchdown, stable landing) and promotion only on a confirmed crossing.
- Lesson: write the success test first, then the rewards that lead to it.
Two simulators, two depth images
0.22 → 0.009 worst-row depth error at a sharp edge (normalized depth).
- Cause: a full parity audit showed the camera geometry matched exactly. But up to 35% of a Gazebo frame had no return, read as “near” where training used “far”, which showed up as a phantom wall across the top of the image. Resampling also differed.
- Fix: matched the no-return convention and added training’s noise and dropout to deployment (fixing the convention alone made the gait worse). A two-stage resample removes most of the remaining edge error.
- Lesson: match the whole input distribution, noise included, not just the geometry.
The recovery that only worked in Isaac
100% → 48% recovery success in Isaac once joint-velocity limits were lifted, as in Gazebo.
- Cause: in Gazebo the robot rocked on its back with legs flailing at 10–30 rad/s. Isaac’s physics clamps joint speed; Gazebo’s effort control doesn’t.
- Fix: retrained without the clamp, with a motor torque-speed curve on half the robots and an overspeed penalty.
- Lesson: reproduce the deployment failure inside the training simulator before retraining.
Export the pair, not the policy
Frozen → walking in Gazebo, with a stale vs matched depth encoder.
- Cause: the policy and its depth encoder are trained together, and the encoder keeps changing. The “best” checkpoints of the two came from different iterations.
- Fix: the export always pairs both networks from the same iteration, and a check compares the deployed token against training (cosine similarity 0.9995).
- Lesson: a checkpoint is every network the policy reads from, at one iteration.
Two smaller reward lessons came up along the way. A still robot escapes reward and penalty alike: the rear legs dragged down stairs because speed-scaled terms vanish when the body stops, so leg-lift credit had to be independent of speed. And a minimum can become the target: an anti-stall floor at 60% of the commanded speed became the cruising speed (0.80 → 0.58 of command) until I moved it to 85% on the approach only.
Takeaways
- Define success strictly and measure it directly, separate from curriculum level and mean reward.
- Probe fixed conditions (width, speed, angle, delay) before reading a dashboard average.
- Give each phase of a skill one reward owner, so nothing pays twice.
- Unit-test reward and termination logic before spending GPU time.
- Reproduce a deployment failure in training before changing the policy.
- Change one variable per run, and write down why in a decision log.
Slides
Credits
Built on MGDP (ArcLab, The University of Hong Kong) and legged_gym. The base perception method is theirs, and the visual-memory design apart from the GRU module is by Shih-Ting Yang. The curriculum, terrains, rewards, recovery policy and deployment work on this page are mine. Special thanks to Yueh Lin, Shih-Ting Yang and Isaac Lin.

