Lab 6 · Seeing the Path
What you do in this lab
AprilTags gave the car a waypoint where you stuck a tag. This lab gives it one with no tag at all: the depth camera looks at the floor ahead, and your code turns that picture into one number, where the open path is. That number is what steers the car in the next lab.
avoid frame, a duct on
the left: open floor in green, its centroid in yellow, offset +0.53. Steer right,
around the duct.Do this: read the five cards below. Then in Play step through the six frames of one lap of the car on the duct track, and switch the view: depth, mask, path, rgb. One tuning that gets all six frames right finishes the lab.
The depth frame
open frame twice. Left: the raw
numbers, brightness = distance. Right: the same numbers through the color map.The car's camera makes a second picture next to the color one. It is not a photo. Every pixel is one number: the distance to whatever is there, in millimeters. The floor two meters out is the number 2000. A pixel that reads 0 means "no measurement", never "touching".
Raw, the numbers are a gray ramp your eye cannot read. The color map spends thousands of colors on the same range so near pops from far. The rainbow is for you. Your code reads the numbers.
Where depth goes missing
wall frame, nose in the duct. Left:
raw, black is dropout, 39% of the frame. Right: the same frame with hole_fill 2.
The holes frame is worse: 74%.Black pixels are holes: places the camera could not measure. Shiny surfaces, dark materials, the edges of objects, and anything too close. A hole reads 0 mm. Left alone, it is never open floor.
Do this in Play: pick the holes frame in the depth view. Move
hole_fill from 0 to 2 to 3. With 2 a hole becomes far, so an obstacle's
missing edge reads as open. With 3 a hole becomes near. That is the cautious pick when
the question is "can I drive there".
The mask
avoid frame. Left: the raw mask
inside the box. Right: after open + close with kernel 5. 62% of the box is duct,
so with ratio 0.5 this frame is BLOCKED.The pipeline never looks at the whole frame. It looks at a box on the floor just ahead of the bumper, the ROI (region of interest). Inside the box, every pixel farther than the threshold is open floor, white. Everything else is obstacle, black. That is the mask.
A raw mask is full of speckle and gaps. Morphology cleans it: open eats
specks, close fills gaps. Then one rule: BLOCKED when obstacle covers more
than ratio of the box.
Do this in Play: switch to the mask view. Set kernel to 0 and
watch the speckle come back. Set it to 5 again. Then drag the box up to the horizon
on the avoid frame: 13% obstacle, OPEN. Same duct, same threshold. The
box is the only part of the picture the next second of driving depends on.
The path: a blob and its centroid
avoid frame's cleaned mask,
magnified. Blob 1 (13506 px) touches the top edge and wins. Blob 2 (111 px) is a
pocket at the bottom left: it does not reach the top, so it cannot be the path.A mask is pixels. A blob is pixels that touch. One OpenCV call labels every white
region and hands back its area and its centroid. Two rules pick the path: the blob
must touch the top of the box (open floor you can see through a gap under the duct is
not floor you can drive onto), and it must beat min_area (speckle that
survived cleanup must not win the wheel).
The centroid is the mean of the blob's pixels. In the box above that is x = 276 of 360. The offset scales it to the box: (276 - 180) / 180 = +0.53. Zero is the middle, -1 the left edge, +1 the right edge.
Do this in Play: switch to the path view on the avoid frame and
read the offset on the bar. Raise min_area and watch the small blob go
red in the mask panel below. Then pick wall: no blob reaches the top, so
the honest answer is "boxed in". The car stops.
The pull
avoid frame again: offset +0.53 is
a pull to the right, around the duct. The offset is an error, how far the open
path sits from the middle of the box, and an error is what a controller eats.Depth frame, mask, blob, centroid, offset. That number is the whole result of this lab, and it is the same shape as the heading error the policy saw in The Twin: zero means straight on, the sign says which way.
The next lab, Obstacle Avoidance, feeds it to the wheel: you drive, and your code pulls the steering toward the open path while you do. The one file you tuned here is the one that pulls.
Follow the White Rabbit
The depth camera is an Intel RealSense D436. Two infrared cameras a known distance apart look at the same scene, and a projector sprays an infrared dot pattern so even a blank wall has texture to match. The same point lands on different pixels in the two images; that shift is the disparity, and depth is Z = f * B / d. The spec is about 2% at 2 m. Point it at a blank wall with the laser off and the depth goes away where the scene has no texture of its own. A visual SLAM rig sees those dots too, and a dot pattern that moves with your car poisons its features, so SLAM setups often run the laser off and pay with sparser depth.
How do you know the depth image is right? Measure the same thing two ways. AprilTags did: a tag's pose from geometry against the depth at the tag, within a few percent. These frames once shipped with the wrong intrinsics and every pose read 58% too far. The depth image caught it.
- librealsense: depth from stereo - how the camera makes the frame, and why a pixel with no match reads 0 mm.
- OpenCV: morphological operations - erosion, dilation, open, close, with pictures.
- OpenCV: connectedComponentsWithStats - every blob's area, bounding box and centroid in one pass.
- Connected-component labeling - the algorithm underneath, and why 4- vs 8-connectivity changes which speckle joins the path.
Play
draw a box on the image to set the watched region
what do these knobs do?
- roi box
- the watched region - drag a box on the image to set it (roi_x, roi_y = top-left corner, roi_w, roi_h = size, in px)
- depth_min_thres
- the threshold (mm): farther is open floor, closer is obstacle
- kernel
- square size used by open/close - bigger = stronger cleanup, 0 = off
- ratio
- BLOCKED when obstacle covers more than this fraction of the box
- min_area
- smallest open blob (px) worth steering at - speckle cannot win the centroid
- hole_fill
- fills depth dropout (black): 0 off, 1 from-left, 2 farthest (bg), 3 nearest (fg)
Code
- A depth pixel is a distance in millimeters. 0 means "no measurement", never
"touching".
hole_filldecides what a hole becomes: 2 far, 3 near. - The box is the only part of the frame that matters. Inside it, farther than the threshold is open floor; the cleaned mask and one ratio give BLOCKED or OPEN.
- The path is the biggest open blob that touches the top of the box and beats
min_area. Its centroid is a steering offset in [-1, 1]. - That offset is the error Obstacle Avoidance feeds to the wheel.
// what you should be able to do now. Each one traces to a knob or a view in this lab.
- Point at a hole in the depth view and say what the pipeline does with it at hole_fill 0, 2 and 3.
- Set kernel to 0 and say what open and close each fixed when you set it back.
- Move the box to the horizon on the avoid frame and say why the verdict flips to OPEN.
- Read the offset on a frame and say which way the car should steer.
- Say why the winning blob must touch the top of the box, and what min_area keeps from winning.
- RoboRacer course: Lecture 12 - Classical Perception - cameras, intrinsics and the pinhole model
- RoboRacer course: Lecture 5 - Follow the Gap - obstacle avoidance from a lidar scan: find the gap, steer to it
- RoboRacer course: Lab 4 - Follow the Gap - the gap follower as a lab
// roboracer.ai (formerly F1TENTH), not goatracer.ai. Each link opens in a new tab.