Skip to main content

cumulus3d_features/
camera_init.rs

1/*
2 * camera_init.rs
3 *
4 * Copyright (c) 2026 ParkSangWoo
5 *
6 * Author(s):
7 *
8 *      ParkSangWoo <dev.parksangwoo@gmail.com>
9 *
10 *
11 * This file is part of cumulus3d.
12 *
13 * Licensed under the Apache License, Version 2.0 (the "License");
14 * you may not use this file except in compliance with the License.
15 * You may obtain a copy of the License at
16 *
17 *      http://www.apache.org/licenses/LICENSE-2.0
18 *
19 * Unless required by applicable law or agreed to in writing, software
20 * distributed under the License is distributed on an "AS IS" BASIS,
21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 * See the License for the specific language governing permissions and
23 * limitations under the License.
24 *
25 * Third-party notices: parts of the algorithms, default parameters and data
26 * formats in this file follow other open-source projects. Their copyright
27 * notices and licenses are reproduced in THIRD_PARTY_NOTICES.md.
28 *
29 * SPDX-License-Identifier: Apache-2.0
30 */
31
32//! Initial camera parameters.
33
34use crate::exif_info::ExifInfo;
35use cumulus3d_core::{Camera, CameraModelKind, Error, Result};
36
37/// Focal-length determination result.
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub struct FocalEstimate {
40    /// Focal length (pixels).
41    pub focal: f64,
42    /// True if determined from EXIF.
43    pub prior: bool,
44}
45
46/// Built-in sensor width table (manufacturer, model, sensor width mm).
47// Design decision: include only some drone cameras known from public product specifications.
48const SENSOR_WIDTHS: &[(&str, &str, f64)] = &[
49    ("dji", "fc6310", 13.2),
50    ("dji", "fc6310s", 13.2),
51    ("dji", "fc6510", 13.2),
52    ("dji", "fc6520", 17.3),
53    ("dji", "fc6540", 23.5),
54    ("dji", "fc330", 6.17),
55    ("dji", "fc300x", 6.17),
56    ("dji", "fc300s", 6.17),
57    ("dji", "fc220", 6.17),
58    ("dji", "fc2103", 6.17),
59    ("dji", "fc2204", 6.17),
60    ("dji", "fc3170", 6.4),
61    ("dji", "fc3411", 6.4),
62    ("dji", "fc3582", 9.6),
63    ("dji", "l1d-20c", 13.2),
64    ("dji", "l2d-20c", 17.3),
65    ("dji", "fc7303", 6.3),
66    ("dji", "zenmusep1", 35.9),
67    ("dji", "m3e", 17.3),
68];
69
70/// Looks up sensor width by manufacturer and model (ignores case and whitespace; allows a model prefixed with the manufacturer).
71pub fn lookup_sensor_width(make: &str, model: &str) -> Option<f64> {
72    let norm = |s: &str| s.chars().filter(|c| !c.is_whitespace() && *c != '_' && *c != '-').collect::<String>().to_lowercase();
73    let mk = norm(make);
74    let mut md = norm(model);
75    if md.starts_with(&mk) {
76        md = md[mk.len()..].to_string();
77    }
78    SENSOR_WIDTHS.iter().find(|(m, n, _)| mk.starts_with(&norm(m)) && norm(n) == md).map(|&(_, _, w)| w)
79}
80
81/// Focal-length rules. (W, H) is the input image size.
82pub fn infer_focal(exif: &ExifInfo, width: u64, height: u64, default_factor: f64) -> FocalEstimate {
83    let (w, h) = (width as f64, height as f64);
84    let max_size = w.max(h);
85    if let Some(f35) = exif.focal_35mm.filter(|&f| f > 0.0) {
86        return FocalEstimate { focal: f35 / 43.27 * (w * w + h * h).sqrt(), prior: true };
87    }
88    if let Some(fmm) = exif.focal_mm.filter(|&f| f > 0.0) {
89        if let (Some(r), Some(u)) = (exif.focal_plane_x_resolution, exif.focal_plane_resolution_unit) {
90            let px_per_mm = match u {
91                2 => Some(r / 25.4),
92                3 => Some(r / 10.0),
93                4 => Some(r),
94                5 => Some(r * 1000.0),
95                _ => None,
96            };
97            if let Some(p) = px_per_mm.filter(|p| *p > 0.0) {
98                return FocalEstimate { focal: fmm * p, prior: true };
99            }
100        }
101        if let (Some(mk), Some(md)) = (&exif.make, &exif.model) {
102            if let Some(sw) = lookup_sensor_width(mk, md) {
103                return FocalEstimate { focal: fmm / sw * max_size, prior: true };
104            }
105        }
106    }
107    FocalEstimate { focal: default_factor * max_size, prior: false }
108}
109
110/// Initializes a new camera: uses `params` as-is if given (flag set); otherwise EXIF/default focal length
111/// + principal point (W/2, H/2), zero distortion.
112pub fn init_camera(
113    model: CameraModelKind,
114    width: u64,
115    height: u64,
116    exif: &ExifInfo,
117    params: Option<&[f64]>,
118    default_focal_length_factor: f64,
119) -> Result<Camera> {
120    if let Some(p) = params {
121        let mut cam = Camera::new(cumulus3d_core::INVALID_CAMERA_ID, model, width, height, p.to_vec())?;
122        cam.focal_from_prior = true;
123        return Ok(cam);
124    }
125    if width == 0 || height == 0 {
126        return Err(Error::InvalidArgument("영상 크기 0".into()));
127    }
128    let fe = infer_focal(exif, width, height, default_focal_length_factor);
129    let mut cam = Camera::from_focal(model, fe.focal, width, height);
130    cam.focal_from_prior = fe.prior;
131    Ok(cam)
132}