Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions opensfm/exif.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import datetime
import logging
from codecs import decode, encode
import math
from typing import Any, BinaryIO, Callable, Dict, List, Optional, Tuple, Union

import exifread
Expand Down Expand Up @@ -60,22 +61,52 @@ def get_tag_as_float(tags: Dict[str, Any], key: str, index: int = 0) -> Optional
return None


def focal35_to_focal_ratio(
focal35_or_ratio: float, width: int, height: int, inverse=False
) -> float:
"""
Convert focal length in 35mm film equivalent to focal ratio (and vice versa).
We follow https://en.wikipedia.org/wiki/35_mm_equivalent_focal_length
"""
image_ratio = float(max(width, height)) / min(width, height)
is_32 = math.fabs(image_ratio - 3.0 / 2.0)
is_43 = math.fabs(image_ratio - 4.0 / 3.0)
if is_32 < is_43:
Comment on lines +71 to +74
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wikipedia article mentions a definition that works for any aspect ratio in the "Calculation" section

"Converted focal length into 35 mm camera" = (Diagonal distance of image area in the 35 mm camera (43.27 mm) / Diagonal distance of image area on the image sensor of the DSC) × focal length of the lens of the DSC.

Should we use that instead so that we support any aspect ratio rather than picking between these two?
I guess the question is which definition do camera manufacturers use to fill the EXIF tag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but in the end, everywhere else in the code, we use a ratio of focal / width (and not focal / diagonal), so we'll have to specify this width anyway.

# 3:2 aspect ratio : use 36mm for 35mm film
film_width = 36.0
if inverse:
return focal35_or_ratio * film_width
else:
return focal35_or_ratio / film_width
else:
# 4:3 aspect ratio : use 34mm for 35mm film
film_width = 34
if inverse:
return focal35_or_ratio * film_width
else:
return focal35_or_ratio / film_width


def compute_focal(
pixel_width: int,
pixel_height: int,
focal_35: Optional[float],
focal: Optional[float],
sensor_width: Optional[float],
sensor_string: Optional[str],
) -> Tuple[float, float]:
if focal_35 is not None and focal_35 > 0:
focal_ratio = focal_35 / 36.0 # 35mm film produces 36x24mm pictures.
focal_ratio = focal35_to_focal_ratio(focal_35, pixel_width, pixel_height)
else:
if not sensor_width:
sensor_width = (
sensor_data().get(sensor_string, None) if sensor_string else None
)
if sensor_width and focal:
focal_ratio = focal / sensor_width
focal_35 = 36.0 * focal_ratio
focal_35 = focal35_to_focal_ratio(
focal, pixel_width, pixel_height, inverse=True
)
else:
focal_35 = 0.0
focal_ratio = 0.0
Expand Down Expand Up @@ -248,7 +279,10 @@ def extract_projection_type(self) -> str:

def extract_focal(self) -> Tuple[float, float]:
make, model = self.extract_make(), self.extract_model()
width, height = self.extract_image_size()
focal_35, focal_ratio = compute_focal(
width,
height,
get_tag_as_float(self.tags, "EXIF FocalLengthIn35mmFilm"),
get_tag_as_float(self.tags, "EXIF FocalLength"),
self.extract_sensor_width(),
Expand Down Expand Up @@ -636,7 +670,13 @@ def extract_exif(self) -> Dict[str, Any]:

def hard_coded_calibration(exif: Dict[str, Any]) -> Optional[Dict[str, Any]]:
focal = exif["focal_ratio"]
fmm35 = int(round(focal * 36.0))
fmm35 = int(
round(
focal35_to_focal_ratio(
focal, int(exif["width"]), int(exif["height"]), inverse=True
)
)
)
make = exif["make"].strip().lower()
model = exif["model"].strip().lower()
raw_calibrations = camera_calibration()[0]
Expand Down
Loading