3D visionExplainer

Real HFOV at different resolutions: crop, scale and getFov

Compute the real HFOV of a depth camera at different resolutions: the crop-then-scale ISP pipeline, the atan formula, a worked example and the useSpec flag.

LumaSpec Vision8 min read Share on LinkedIn

On this page 9 sections
  1. 01Why getFov() ignores your resolution setting
  2. 02Crop first, scale second: follow the pipeline
  3. 03The FOV formula for a cropped frame
  4. 04Worked example: 108° sensor from 12 MP to 720p
  5. 05Calibrated or specified: which FOV value to use
  6. 06From angle to coverage: mm per pixel decides the buy
  7. 07The same steps on an area scan camera
  8. 08Checks and decisions
  9. 09Questions

You switch the camera to 720p for bandwidth, call getFov() on the calibration and it returns the same angle it did at full resolution. The live image clearly shows less of the scene, so the number feels wrong. It is not wrong — it answers a different question. getFov() reads the sensor-level calibration, while the crop happens later, inside the ISP. This page gives you the one-line trigonometry for the real horizontal and vertical FOV of any output mode, shows which pipeline stage moves the angle and which does not, and explains how to read the calibrated value instead of the specified one. The same three steps work for any depth or area scan camera whose modes crop or scale the full frame.

Why getFov() ignores your resolution setting

The calibration lives in the device's EEPROM and describes the sensor in its full frame. Intrinsics and FOV are computed for the maximum resolution, because that is what the sensor actually images; the device in the original report returned a specified 108° and a calibrated 79.475° no matter which mode was running. A 720p stream is produced after calibration, when the ISP trims and resizes the frame, so the call has nothing to say about your mode and no built-in call reports its angle. readCalibration() hands you the stored calibration; the pipeline below hands you the rest.

Crop first, scale second: follow the pipeline

Small modes are built by cropping the full frame and then scaling it. The chain for a 720p stream from a 12 MP sensor is documented: crop to 4K, 3840 × 2160, then scale to 1280 × 720. Both frames are 16:9, so the scale is uniform and trims nothing further.

That order decides everything:

The 720p stream's FOV therefore equals the 4K crop's FOV, and you only ever compute the crop step. Size settings that look ignored on stereo depth output tell the same story — Stereo depth output resolution: why size settings seem ignored works through it.

The FOV formula for a cropped frame

How sensor width, working distance and field of view set the focal length Rays from the edges of the sensor pass through the lens and spread to the edges of the field of view. The ratio of sensor width to field of view is the magnification m.CameraField of view (FOV)Working distance (WD)≈ fsensor width slensm = s / FOVf = WD × m / (1 + m)
How sensor width, working distance and field of view set the focal length. Rays from the edges of the sensor pass through the lens and spread to the edges of the field of view. The ratio of sensor width to field of view is the magnification m.

For a centred crop that keeps W_used of the full frame's W_max pixels on one axis:

HFOV_used = 2 · atan( (W_used / W_max) · tan(HFOV_max / 2) ) VFOV_used = 2 · atan( (H_used / H_max) · tan(VFOV_max / 2) )
  • HFOV_max comes from the calibration, read with useSpec set to False.
  • W_max, H_max are the full-frame pixel dimensions the calibration corresponds to. Read them from the calibration data, not from the mode name.
  • W_used, H_used are the pixels the ISP keeps before any scaling.

Two limits worth knowing. W_used = W_max returns the full angle, as it must. Halving W_used does not halve the angle — it halves the tangent of the half-angle, which narrows the view less than you would guess. The formula assumes a rectilinear lens; a fisheye needs its full distortion model, and the atan shortcut underestimates its edge angles.

Worked example: 108° sensor from 12 MP to 720p

Take a 108° camera whose full frame is 4032 × 3024 (12 MP, 4:3 — illustrative dimensions; substitute your calibration's real width). The pipeline crops to 3840 × 2160, then scales to 720p.

Horizontal: 3840 ÷ 4032 = 0.952 and tan(108° ÷ 2) = tan 54° = 1.376, so HFOV = 2 · atan(0.952 × 1.376) = 2 · atan(1.311) = 105.3°.

Vertical: the full-frame angle is 2 · atan((3024 ÷ 4032) × 1.376) = 2 · atan(1.032) = 91.8°. The crop keeps 2160 ÷ 3024 = 0.714 of the height, so VFOV = 2 · atan(0.714 × 1.032) = 2 · atan(0.737) = 72.8°.

Stage Active pixels Ratio kept HFOV VFOV
Full frame 4032 × 3024 1.000 108.0° 91.8°
Crop to 4K 3840 × 2160 0.952 w × 0.714 h 105.3° 72.8°
Scale to 720p 1280 × 720 angles unchanged 105.3° 72.8°

The instinct that 720p must crop a lot is wrong horizontally: you lose 2.7° of 108°. The loss is vertical — 19.0° of 91.8° — because 4:3 to 16:9 trims height, not width.

import math

def fov_after_crop(fov_full_deg: float, px_used: int, px_max: int) -> float:
    # FOV in degrees after a centred crop keeps px_used of px_max pixels on one axis
    return math.degrees(2.0 * math.atan((px_used / px_max) * math.tan(math.radians(fov_full_deg / 2.0))))

assert abs(fov_after_crop(108.0, 4032, 4032) - 108.0) < 1e-9      # no crop: identity
print(round(fov_after_crop(108.0, 3840, 4032), 1))                # 105.3
print(round(fov_after_crop(fov_after_crop(108.0, 3024, 4032), 2160, 3024), 1))  # 72.8

Calibrated or specified: which FOV value to use

The EEPROM stores two angles per socket. The specified value is written from sensor and lens data when the device is flashed; the calibrated value is computed from the measured intrinsics. The useSpec flag chooses between them: False returns the calibrated angle, True returns the specified baseline.

They can differ by far more than any mode ever will. On the device in the original report the specified value is 108° and the calibrated one 79.475°. Run the same 0.952 crop on the calibrated number: tan(79.475° ÷ 2) = 0.831, so HFOV = 2 · atan(0.952 × 0.831) = 2 · atan(0.791) = 76.7°, not 105.3°. For coverage geometry, feed your plan the 76.7°.

From angle to coverage: mm per pixel decides the buy

An object of width W_obj just fills the frame when W_obj = 2 · D · tan(HFOV / 2). At D = 1000 mm with the calibrated 76.7° example, the 720p stream sees 2 × 1000 × tan 38.35° = 2000 × 0.791 = 1582 mm across. Divided by 1280 pixels that is 1.2 mm per pixel; a 1 mm defect spans 0.8 px and disappears. The same scene on the 4K crop, 3840 pixels wide, gives 1582 ÷ 3840 = 0.41 mm per pixel, and the defect spans 2.4 px.

The FOV barely moved between the modes; the pixel count did the damage. Check which resolution your target detail actually needs:

Camera resolution for the smallest feature
Units
Pixels needed
–
Sensor size
– MP
Smallest sensor that fits
–
Pixel on the part
– mm
Pixels across the feature, that sensor
–

Pixels needed = field / feature × pixels per feature on each axis (field incl. margin). The lens must resolve the pixels too: Nyquist = 1000 / (2 × pixel µm) lp/mm.

Find cameras with enough pixels Open the full calculator

If you are sizing a ToF depth camera's coverage before ordering, the same atan check applies at its fixed FOV:

3D sensorsHikrobot MV-DT01WMU ToF sensing cameraMV-DT01WMU
Type
Depth / ToF camera
Price on requestView and quote

The same steps on an area scan camera

Video modes crop on 2D cameras too. The LS-CMR-0166 outputs 4000 × 3000 over USB3. Draw a centred 2000 × 1000 processing ROI and the horizontal scale factor is 2000 ÷ 4000 = 0.500: with a 60° lens, HFOV = 2 · atan(0.500 × 0.577) = 32.2°, a bit more than half of 60°. Where to place such an ROI, and what it costs on 3D cameras, is covered in 2D vs 3D camera ROI: where to set it and which one wins. The angle also shifts when the sensor behind a lens changes; Same lens on a different sensor: how HFOV changes covers that case.

Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it

Checks and decisions

Before you trust a number:

  • Read the FOV with useSpec=False, always.
  • Take W_max from the calibration's image size. Full frames in the 12 MP class are 4000, 4032 or 4056 pixels wide, and picking the wrong one shifts the result by a degree or two.
  • Compare the output aspect ratio with the full frame's. Equal means scale-only after one crop; different means another crop stage — apply the formula twice.
  • Assume the crop is centred; measure an off-centre ROI by its own span.
  • Bench-check once per device type: stand a ruler at a measured distance D, read the visible width W at the frame edges, compute 2 · atan((W ÷ 2) ÷ D), and expect agreement within about 2°.

Decisions by case:

  • Crop-then-scale mode (the 720p case): FOV equals the crop stage's angle — 105.3° horizontal and 72.8° vertical on the specified basis, 76.7° horizontal on the calibrated one. Commit those to the coverage math, not the getFov() output.
  • Scale-only or decimated output: FOV unchanged; only mm per pixel moves.
  • Aspect mismatch: expect the trim on the short axis — 19.0° vertical lost versus 2.7° horizontal on the example.
  • Buying: a part needing better than 1.2 mm per pixel at 1 m cannot ride the 720p stream at this FOV. Take the 4K path, mount closer, or choose a narrower-angle depth camera. For guidance work, a projected structured-light camera is the usual choice once its FOV covers the work area at the mounting height:
3D sensorsHikrobot MV-DPS200P-02 Projected structured-light 3D cameraMV-DPS200P-02
Type
Stereo / structured light
$8,829.00 · quotedView and quote

Size the rest from the camera finder, browse the 3D sensors and area scan cameras categories, or start the camera configurator. More geometry like this sits on the 3D vision topic page, and the team answers set-up questions through the contact page.

Questions

Does lowering the output resolution always reduce the field of view?

No. Only a crop reduces it. If the smaller mode is made by scaling or decimation alone — same scene, fewer pixels — the angle is identical and only the millimetres per pixel change. Trace how your pipeline builds the mode: crop to an intermediate size first, then scale, and only the crop step enters the atan formula. On the 12 MP to 4K to 720p chain the crop happens once, so the 720p angle equals the 4K angle.

Why does getFov() return the same value at every resolution?

Because it reads the device calibration from EEPROM, and the calibration describes the sensor at full resolution. The crop to a smaller mode is applied later by the ISP, after the calibration has done its job, so the call cannot see it. There is no built-in mode-aware FOV call; compute 2·atan((W_used/W_max)·tan(HFOV/2)) yourself, taking both the angle and the full-frame width from the calibration data.

Should useSpec be True or False when reading the FOV?

False for real geometry. useSpec=True returns the specified FOV stored at flashing from sensor and lens data — a factory baseline. useSpec=False returns the angle computed from the calibrated intrinsics, which tracks the actual unit. On the device in the original report the two values were 108° and 79.475°, a 28.5° gap, so the specified number alone would wreck a coverage plan.

How do I measure the FOV on the bench without any formula?

Stand a ruler or a printed target perpendicular to the optical axis at a measured distance D and capture one frame. Read the width W visible between the frame edges and compute 2·atan((W/2)/D). Repeat at a second distance; the two angles should agree within about 2°. If they do not, your pipeline crops somewhere the mode table does not admit, or the lens is fisheye and needs the full distortion model.