Vision · University · about 35 min
Focal length in pixels, the principal point, and measuring them from known geometry.
[1 mark]A 3.6 mm lens sits on a sensor 4.8 mm wide that produces images 640 pixels wide. What is the focal length in pixels?
[1 mark]The 320 pixel wide BugBot image, with fx = 92.4, is downsampled to 160 pixels wide. What is fx for the small image, in pixels?
[1 mark]What does a camera calibration actually compute?
[1 mark]Every calibration image shows the chessboard square on to the camera. What goes wrong?
[1 mark]Four tags give tan(bearing) and the observed u - 160. The model u - 160 = f tan(bearing) is a line through the origin, fitted by least squares. What does this print?
tans = [0.0, 30 / 70, -40 / 50, 40 / 30] offsets = [0.0, 40.0, -74.0, 123.0] f = sum(t * o for t, o in zip(tans, offsets)) / sum(t * t for t in tans) residuals = [round(o - f * t, 1) for t, o in zip(tans, offsets)] print(round(f, 1)) print(residuals)
92.4 [0.0, 0.4, -0.1, -0.2]
The least squares gradient through the origin is sum(t o) / sum(t t) = 92.4 px, and every residual is under a pixel, so the pinhole model describes this camera.
[1 mark]Why does a tag straight ahead of the robot carry no information about f?
[1 mark]A calibration reports a root mean square reprojection error of 1.4 pixels. What should you conclude?
Four tags are on the mat at (100, 130), (130, 110), (60, 90) and (140, 70). The robot stands at (100, 40) facing along +y. Print focal length:, fitted in pixels, and field of view:, the full horizontal field of view in degrees that your focal length implies.
from bugbot import *
import math
connect()
ROBOT = (100.0, 40.0)
TAGS = {1: (100.0, 130.0), 2: (130.0, 110.0), 3: (60.0, 90.0), 4: (140.0, 70.0)}
set_cv("apriltag")
wait(0.3)The hint students can ask for: Four tags are stuck on the mat at positions the task tells you, and the robot is at a position it tells you too, facing along +y. So you know the true bearing of every tag, and you can read the column each one lands in. Plot the pixel offset against the tangent of the bearing: it is a straight line through the origin, and the focal length is its gradient. Fit it, do not take one point. The field of view then follows from the half width of the sensor.
from bugbot import *
import math
connect()
# where the calibration target is, and where the robot is standing
ROBOT = (100.0, 40.0)
TAGS = {1: (100.0, 130.0), 2: (130.0, 110.0), 3: (60.0, 90.0), 4: (140.0, 70.0)}
set_cv("apriltag")
wait(0.3)
# one correspondence per tag: the true bearing, and the column it landed in
num = den = 0.0
for tag_id, cx, cy, dist in apriltags():
tx, ty = TAGS[tag_id]
truth = math.atan2(tx - ROBOT[0], ty - ROBOT[1]) # heading is 0, so this is the bearing
u = cx - 160.0 # offset from the principal point
num += u * math.tan(truth)
den += math.tan(truth) ** 2
f = num / den # least squares gradient through the origin
fov = 2 * math.degrees(math.atan(160.0 / f))
print("focal length:", round(f, 2))
print("field of view:", round(fov, 1))
Any program that meets the task's checks is marked correct in the simulator; this is one way, not the only way.