Three files, no dependencies beyond numpy + matplotlib:
| File | Role |
|---|---|
s10_fov.py |
Core library + CLI. All the geometry lives here. |
s10_plot.py |
Draws the FOV wedge, tilt sweep, and design-space map. |
demo.py |
Sample survey script: compares camera variants × mounting options. |
pip install numpy matplotlib
python3 demo.py # run the full survey
python3 s10_fov.py --cx 0.42 --cz 0.02 # auto-find optimal tilt
python3 s10_fov.py --tilt 28 --plot out.png # lock the angle, export image
python3 s10_fov.py --cam s10-90 --rotate # switch variant / rotate 90°- Origin
Osits on the ground, directly below the robot body pivot. xpoints forward (m),zpoints up (m).cx,cz= camera position in the body frame (unchanged as the robot changes pose) — matching your bracket drawing.tilt_deg= camera tilt relative to the body. The effective tilt relative to the ground =tilt + pitch.pitch_deginPoseis the body angle relative to the ground plane, not relative to gravity's horizontal. Climbing a slope with the body parallel to the slope surface givespitch_deg = 0,ground_slope_deg = 20.
d_near = h / tan(θ + FOV_v/2) # nearest ground-hit point
s_near = h / sin(θ + FOV_v/2) # slant distance, must be ≥ r_min
H(d) = h + d·tan(FOV_v/2 − θ) # upper FOV bound at distance d
W(d) = 2·d·tan(FOV_h/2) # coverage width at distance d
For sloped ground g, ground_hit() solves the ray–inclined-plane
intersection instead of using z = 0.
| Metric | Meaning | Suggested threshold |
|---|---|---|
mu* (d_near_front) |
Remaining blind gap measured from the robot's front edge | ≤ 0.25 m, negative is good |
s_near |
Slant distance to the nearest ground-hit point | ≥ 0.30 m (S10 dead zone) |
n_px_v/h |
Pixel count covering the target object | ≥ 3×3 |
best_tilt() ranks by: (1) no pose loses the target object, (2) no dead-zone
violation, (3) smallest worst-case blind zone.
default_m20()— the threebody_hfigures (standing / sitting / raised stance) are assumptions. Measure them on the real robot with a tape measure, down to the hip pivot, not the top shell.Robot.front_x— x-coordinate of the front wheel's outer edge in standing pose.Pose.pitch_degfor hard braking / acceleration — take it from real IMU logs, don't guess.CameraSpec.fov_*— replace with the measured FOV from step 3.1 of the procedure, not the nominal datasheet figure (rated ±3°).r_max— defaults to 3.0 m for the worst-case low-reflectivity target. Change to 8.0 if you're only considering bright-colored objects.
python3 s10_interactive.pyNeeds an interactive backend:
sudo apt install python3-tk (Linux) or pip install PyQt5.
Panels: side view, top view, tilt sweep, live metrics with PASS/FAIL.
Sliders
| Slider | Meaning |
|---|---|
| Camera height | optical center relative to ground in the nominal standing pose |
| Forward offset | camera's x position in the body frame |
| Tilt down | camera angle relative to the body, not the ground |
| Pose Δheight | drag negative = robot sitting, positive = standing tall |
| Body pitch | positive = nose-down (hard braking) |
| Ground slope | positive = uphill ahead |
| Obstacle dist | measured from the robot's front edge |
| Min pixels | minimum pixel count to count as detectable |
Keys: a auto-optimise tilt · r print report · s save PNG · 0 reset
Reading the display
- Thick red bar on the ground = remaining blind zone in front of the robot's edge.
- Red arc =
r_min0.3 m (dead zone), dashed grey arc =r_max. - Faint lines = FOV wedge at other poses (sitting / standing tall / braking / accelerating).
- Green obstacle = detectable, red = missed.
- Vertical dashed line on the tilt-sweep chart = current tilt angle.
Design targets: blind ≤ 0.25 m, slant ≥ 0.30 m, pixel cluster ≥ 3×3 px.
