9

It's all in the title, but in the now-deprecated Android Camera API, there were two methods: Camera.Parameters.getHorizontalViewAngle() and Camera.Parameters.getVerticalViewAngle().

Now, with the current Camera2 API, it seems there is no equivalent to these in the docs. I'm assuming that this is because FOV angles are more complicated and nuanced than a simple horizontal and vertical value, but I can't find any information online about how to calculate the total field of view for an Android device using the newer Camera2 API.

TonyTheJet
  • 517
  • 1
  • 6
  • 15

3 Answers3

15

The basic formula is

FOV.x = 2 * atan(SENSOR_INFO_PHYSICAL_SIZE.x / (2 * LENS_FOCAL_LENGTH))
FOV.y = 2 * atan(SENSOR_INFO_PHYSICAL_SIZE.y / (2 * LENS_FOCAL_LENGTH))

This is an approximation assuming an ideal lens, etc, but generally good enough.

This calculates the FOV for the entire sensor pixel array.

However, the actual field of view of a given output will be smaller; first, the readout area of the sensor is often smaller than the full pixel array, so instead of using PHYSICAL_SIZE directly, you need to first scale it by the ratio of the pixel array pixel count to the active array pixel count (SENSOR_INFO_ACTIVE_ARRAY_SIZE / SENSOR_INFO_PIXEL_ARRAY_SIZE).

Then, the field of view depends on the aspect ratio of the output(s) you've configured (a 16:9 FOV will be different than a 4:3 FOV), relative to the aspect ratio of the active array, and the aspect ratio of the crop region (digital zoom) if it's smaller than than the full active array.

Each output buffer will be the result of minimally further cropping the cropRegion for the corresponding capture request to reach the correct output aspect ratio. (http://source.android.com/devices/camera/camera3_crop_reprocess.html has diagrams).

So let's say we have a sensor that has a pixel array of (120,120), and we have an active array rectangle of (10,10)-(110,110), so width/height of 100,100.

We configure two outputs, output A is (40,30), output B is (50, 50). Let's leave the crop region at the maximum (0,0)-(100,100).

The horizontal FOV for output A and B will be the same, because the maximum-area crop will result in both outputs using the full active array width:

output_physical_width = SENSOR_INFO_PHYSICAL_SIZE.x * ACTIVE_ARRAY.w / PIXEL_ARRAY.w
FOV_x = 2 * atan(output_physical_width / (2 * LENS_FOCAL_LENGTH))

However, the vertical FOVs will differ - output A will only use 3/4 of the vertical space due to the aspect ratio mismatch:

active_array_aspect = ACTIVE_ARRAY.w / ACTIVE_ARRAY.h
output_a_aspect = output_a.w / output_a.h
output_b_aspect = output_b.w / output_b.h
output_a_physical_height = SENSOR_INFO_PHYSICAL_SIZE.y * ACTIVE_ARRAY.h / PIXEL_ARRAY.h * output_a_aspect / active_array_aspect
output_b_physical_height = SENSOR_INFO_PHYSICAL_SIZE.y * ACTIVE_ARRAY.h / PIXEL_ARRAY.h * output_b_aspect / active_array_aspect
FOV_a_y = 2 * atan(output_a_physical_height / (2 * LENS_FOCAL_LENGTH))
FOV_b_y = 2 * atan(output_b_physical_height / (2 * LENS_FOCAL_LENGTH))

The above works when the output aspect ratio is <= active array aspect ratio (letterboxing); if that's not true, then the output horizontal dimension is reduced and the vertical dimension covers the whole active array (pillarboxing). The scale factor for the horizontal direction is then active_array_aspect/output_aspect.

If you want to calculate the FOV for a zoomed-in view, then substitute the crop region dimensions/aspect ratio for the active array dimensions/aspect ratio.

Eddy Talvala
  • 17,243
  • 2
  • 42
  • 47
  • Eddy, this is a brilliant response. Thank you so much for taking the time to carefully and thoroughly cover such a nuanced (but correct) topic. It matches what I intuitively thought was the case, which is that the previous Camera API methods left much to be desired, and that it was ultimately wise for Android to exclude such oversimplifications in Camera2. Cheers! – TonyTheJet Oct 15 '16 at 02:36
  • Thanks for detailed explanation. When I read http://source.android.com/devices/camera/camera3_crop_reprocess.html it says: If the stream's aspect ratio is wider than the crop region, the stream should be further cropped vertically, and if the stream's aspect ratio is narrower than the crop region, the stream should be further cropped horizontally. This contradicts to what you said in your remarks in 2nd last paragraph. As per reference, if output aspect ratio <= active array aspect ratio then further cropping will happen horizontally. So, your code will work if output > active. – Tanmay Aug 31 '17 at 22:21
  • Yes, the example is specifically for the numbers provided; it doesn't consider the case where output A is (30,40) for example, which would result in different horizontal FOVs and the same vertical FOV. – Eddy Talvala Mar 05 '18 at 22:04
6
private float getHFOV(CameraCharacteristics info) {
    SizeF sensorSize = info.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
    float[] focalLengths = info.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);

    if (focalLengths != null && focalLengths.length > 0) {
        return (float) (2.0f * atan(sensorSize.getWidth() / (2.0f * focalLengths[0])));
    }

    return 1.1f;
}
Joel Teply
  • 3,260
  • 1
  • 31
  • 21
0

While Eddy Talvala's answer gives you a solution, that is only viable for objects at a distance. However, as https://en.wikipedia.org/wiki/Angle_of_view#Macro_photography notes, for close subjects, you cannot assume the distance between lense and sensor to be equal to the focus distance. This distance cannot be queried but calculated S_2 = S_1 * focal length / (S_1 - focal length)

In this case S_1 is the minimal focus distance which can be computed from LENS_INFO_MINIMUM_FOCUS_DISTANCE when LENS_INFO_FOCUS_DISTANCE_CALIBRATION is not uncalibrated, in which case LENS_INFO_MINIMUM_FOCUS_DISTANCE has a unit of diopter (1/m).

So the final formula becomes:

// focal length/physical size is in mm -> convert from diopter (1/m) to mm
S_1 = 1000 / LENS_INFO_MINIMUM_FOCUS_DISTANCE
S_2 = LENS_FOCAL_LENGTH * S_1 / (S_1 - LENS_FOCAL_LENGTH)
FOV.x = 2 * atan(SENSOR_INFO_PHYSICAL_SIZE.x / (2 * S_2))

I measured this and compared with the computation for a Mi 9T Pro with following results:

name value
Focal Length 4.77 mm
Physical Sensor Size 6,4x4,8 mm
minimum focus distance 10 diopter
hyperfocal distance 0.123 diopter
calculated FOV.y hyperfocal 53.4°
calculated FOV.y macro 51.2°
Framesize (y) 9.6 cm
Distance 10.2 cm
measured FOV 50.4°