1use crate::error::{Error, Result};
38use crate::geometry::{Mat3, Vec2, Vec3};
39use crate::ids::{CameraId, INVALID_CAMERA_ID};
40use nalgebra::{Matrix2, Matrix2x3};
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub enum CameraModelKind {
45 SingleFocalPinhole = 0,
47 Pinhole = 1,
49 SingleFocalRadial = 2,
51 Radial = 3,
53 OpenCv = 4,
55}
56
57pub const UNDISTORT_MAX_ITERS: usize = 100;
59pub const UNDISTORT_STEP_SQ_TOL: f64 = 1e-10;
61
62const MODEL_NAMES: [&str; 5] = ["SIMPLE_PINHOLE", "PINHOLE", "SIMPLE_RADIAL", "RADIAL", "OPENCV"];
64
65impl CameraModelKind {
66 pub fn from_id(id: i32) -> Result<Self> {
68 match id {
69 0 => Ok(Self::SingleFocalPinhole),
70 1 => Ok(Self::Pinhole),
71 2 => Ok(Self::SingleFocalRadial),
72 3 => Ok(Self::Radial),
73 4 => Ok(Self::OpenCv),
74 _ => Err(Error::Unsupported(format!("카메라 모델 id {id}"))),
75 }
76 }
77 pub fn id(self) -> i32 {
79 self as i32
80 }
81 pub fn name(self) -> &'static str {
83 MODEL_NAMES[self as usize]
84 }
85 pub fn from_name(name: &str) -> Result<Self> {
87 match MODEL_NAMES.iter().position(|n| *n == name) {
88 Some(i) => Self::from_id(i as i32),
89 None => Err(Error::Format(format!("알 수 없는 카메라 모델 이름 {name}"))),
90 }
91 }
92 pub fn num_params(self) -> usize {
94 match self {
95 Self::SingleFocalPinhole => 3,
96 Self::Pinhole => 4,
97 Self::SingleFocalRadial => 4,
98 Self::Radial => 5,
99 Self::OpenCv => 8,
100 }
101 }
102 pub fn params_info(self) -> &'static str {
104 match self {
105 Self::SingleFocalPinhole => "f, cx, cy",
106 Self::Pinhole => "fx, fy, cx, cy",
107 Self::SingleFocalRadial => "f, cx, cy, k",
108 Self::Radial => "f, cx, cy, k1, k2",
109 Self::OpenCv => "fx, fy, cx, cy, k1, k2, p1, p2",
110 }
111 }
112 pub fn focal_slots(self) -> &'static [usize] {
114 match self {
115 Self::SingleFocalPinhole | Self::SingleFocalRadial | Self::Radial => &[0],
116 Self::Pinhole | Self::OpenCv => &[0, 1],
117 }
118 }
119 pub fn pp_slots(self) -> &'static [usize] {
121 match self {
122 Self::SingleFocalPinhole | Self::SingleFocalRadial | Self::Radial => &[1, 2],
123 Self::Pinhole | Self::OpenCv => &[2, 3],
124 }
125 }
126 pub fn extra_param_slots(self) -> &'static [usize] {
128 match self {
129 Self::SingleFocalPinhole | Self::Pinhole => &[],
130 Self::SingleFocalRadial => &[3],
131 Self::Radial => &[3, 4],
132 Self::OpenCv => &[4, 5, 6, 7],
133 }
134 }
135}
136
137#[derive(Clone, Debug, PartialEq)]
139pub struct Camera {
140 pub camera_id: CameraId,
142 pub model: CameraModelKind,
144 pub width: u64,
146 pub height: u64,
148 pub params: Vec<f64>,
150 pub focal_from_prior: bool,
152}
153
154impl Camera {
155 pub fn new(camera_id: CameraId, model: CameraModelKind, width: u64, height: u64, params: Vec<f64>) -> Result<Self> {
157 if params.len() != model.num_params() {
158 return Err(Error::Format(format!(
159 "카메라 {camera_id}: 모델 {} 의 파라미터 개수 {} != {}",
160 model.name(),
161 params.len(),
162 model.num_params()
163 )));
164 }
165 Ok(Self { camera_id, model, width, height, params, focal_from_prior: false })
166 }
167
168 pub fn from_focal(model: CameraModelKind, focal: f64, width: u64, height: u64) -> Self {
170 let mut params = vec![0.0; model.num_params()];
171 for &i in model.focal_slots() {
172 params[i] = focal;
173 }
174 let pp = model.pp_slots();
175 params[pp[0]] = width as f64 / 2.0;
176 params[pp[1]] = height as f64 / 2.0;
177 Self { camera_id: INVALID_CAMERA_ID, model, width, height, params, focal_from_prior: false }
178 }
179
180 pub fn verify_params(&self) -> bool {
182 self.params.len() == self.model.num_params()
183 }
184
185 pub fn mean_focal(&self) -> f64 {
187 let idx = self.model.focal_slots();
188 idx.iter().map(|&i| self.params[i]).sum::<f64>() / idx.len() as f64
189 }
190 pub fn focal_length_x(&self) -> f64 {
192 self.params[self.model.focal_slots()[0]]
193 }
194 pub fn focal_length_y(&self) -> f64 {
196 let idx = self.model.focal_slots();
197 self.params[idx[idx.len() - 1]]
198 }
199 pub fn set_focal_length(&mut self, f: f64) {
201 for &i in self.model.focal_slots() {
202 self.params[i] = f;
203 }
204 }
205 pub fn principal_point_x(&self) -> f64 {
207 self.params[self.model.pp_slots()[0]]
208 }
209 pub fn principal_point_y(&self) -> f64 {
211 self.params[self.model.pp_slots()[1]]
212 }
213 pub fn set_principal_point(&mut self, cx: f64, cy: f64) {
215 let pp = self.model.pp_slots();
216 self.params[pp[0]] = cx;
217 self.params[pp[1]] = cy;
218 }
219 pub fn extra_params(&self) -> Vec<f64> {
221 self.model.extra_param_slots().iter().map(|&i| self.params[i]).collect()
222 }
223 pub fn is_undistorted(&self) -> bool {
225 self.model.extra_param_slots().iter().all(|&i| self.params[i] == 0.0)
226 }
227 pub fn calibration_matrix(&self) -> Mat3 {
229 Mat3::new(self.focal_length_x(), 0.0, self.principal_point_x(), 0.0, self.focal_length_y(), self.principal_point_y(), 0.0, 0.0, 1.0)
230 }
231 pub fn has_implausible_params(&self, min_focal_ratio: f64, max_focal_ratio: f64, max_extra_param: f64) -> bool {
233 let (w, h) = (self.width as f64, self.height as f64);
234 let (cx, cy) = (self.principal_point_x(), self.principal_point_y());
235 if cx < 0.0 || cx > w || cy < 0.0 || cy > h {
236 return true;
237 }
238 let m = w.max(h);
239 for &i in self.model.focal_slots() {
240 let r = self.params[i] / m;
241 if r < min_focal_ratio || r > max_focal_ratio {
242 return true;
243 }
244 }
245 self.model.extra_param_slots().iter().any(|&i| self.params[i].abs() > max_extra_param)
246 }
247
248 pub fn rescale(&mut self, scale: f64) {
250 let nw = (self.width as f64 * scale).round().max(1.0) as u64;
251 let nh = (self.height as f64 * scale).round().max(1.0) as u64;
252 self.scale_params(scale, scale);
253 self.width = nw;
254 self.height = nh;
255 }
256
257 pub fn rescale_to(&mut self, new_width: u64, new_height: u64) {
259 let sx = new_width as f64 / self.width as f64;
260 let sy = new_height as f64 / self.height as f64;
261 self.scale_params(sx, sy);
262 self.width = new_width;
263 self.height = new_height;
264 }
265
266 fn scale_params(&mut self, sx: f64, sy: f64) {
267 let f = self.model.focal_slots();
268 if f.len() == 1 {
269 self.params[f[0]] *= 0.5 * (sx + sy);
271 } else {
272 self.params[f[0]] *= sx;
273 self.params[f[1]] *= sy;
274 }
275 let pp = self.model.pp_slots();
276 self.params[pp[0]] *= sx;
277 self.params[pp[1]] *= sy;
278 }
279
280 pub fn img_to_cam_threshold(&self, threshold_px: f64) -> f64 {
282 threshold_px / self.mean_focal()
283 }
284
285 fn distortion(&self, u: f64, v: f64) -> (f64, f64, Matrix2<f64>) {
287 let p = &self.params;
288 match self.model {
289 CameraModelKind::SingleFocalPinhole | CameraModelKind::Pinhole => (0.0, 0.0, Matrix2::zeros()),
290 CameraModelKind::SingleFocalRadial | CameraModelKind::Radial => {
291 let (k1, k2) = if self.model == CameraModelKind::SingleFocalRadial { (p[3], 0.0) } else { (p[3], p[4]) };
292 let r2 = u * u + v * v;
293 let radial = k1 * r2 + k2 * r2 * r2;
294 let g = 2.0 * k1 + 4.0 * k2 * r2; let du = u * radial;
296 let dv = v * radial;
297 let j = Matrix2::new(radial + u * g * u, u * g * v, v * g * u, radial + v * g * v);
298 (du, dv, j)
299 }
300 CameraModelKind::OpenCv => {
301 let (k1, k2, p1, p2) = (p[4], p[5], p[6], p[7]);
302 let r2 = u * u + v * v;
303 let radial = k1 * r2 + k2 * r2 * r2;
304 let du = u * radial + 2.0 * p1 * u * v + p2 * (r2 + 2.0 * u * u);
305 let dv = v * radial + 2.0 * p2 * u * v + p1 * (r2 + 2.0 * v * v);
306 let du_du = radial + u * (2.0 * k1 * u + 4.0 * k2 * r2 * u) + 2.0 * p1 * v + 6.0 * p2 * u;
307 let du_dv = u * (2.0 * k1 * v + 4.0 * k2 * r2 * v) + 2.0 * p1 * u + 2.0 * p2 * v;
308 let dv_du = v * (2.0 * k1 * u + 4.0 * k2 * r2 * u) + 2.0 * p2 * v + 2.0 * p1 * u;
309 let dv_dv = radial + v * (2.0 * k1 * v + 4.0 * k2 * r2 * v) + 2.0 * p2 * u + 6.0 * p1 * v;
310 (du, dv, Matrix2::new(du_du, du_dv, dv_du, dv_dv))
311 }
312 }
313 }
314
315 fn fxfycxcy(&self) -> (f64, f64, f64, f64) {
316 (self.focal_length_x(), self.focal_length_y(), self.principal_point_x(), self.principal_point_y())
317 }
318
319 pub fn normalized_to_img(&self, uv: &Vec2) -> Vec2 {
321 let (fx, fy, cx, cy) = self.fxfycxcy();
322 let (du, dv, _) = self.distortion(uv.x, uv.y);
323 Vec2::new(fx * (uv.x + du) + cx, fy * (uv.y + dv) + cy)
324 }
325
326 pub fn cam_to_img(&self, xc: &Vec3) -> Option<Vec2> {
328 if xc.z < f64::EPSILON {
329 return None;
330 }
331 Some(self.normalized_to_img(&Vec2::new(xc.x / xc.z, xc.y / xc.z)))
332 }
333
334 pub fn img_to_normalized(&self, xy: &Vec2) -> Option<Vec2> {
336 let (fx, fy, cx, cy) = self.fxfycxcy();
337 let p0 = Vec2::new((xy.x - cx) / fx, (xy.y - cy) / fy);
338 if self.model.extra_param_slots().is_empty() || self.is_undistorted() {
339 return if p0.x.is_finite() && p0.y.is_finite() { Some(p0) } else { None };
340 }
341 let mut p = p0;
342 for _ in 0..UNDISTORT_MAX_ITERS {
343 let (du, dv, jd) = self.distortion(p.x, p.y);
344 let j = Matrix2::identity() + jd;
345 let res = Vec2::new(p.x + du - p0.x, p.y + dv - p0.y);
346 let mut step = j.lu().solve(&res)?;
347 if !step.x.is_finite() || !step.y.is_finite() {
348 return None;
349 }
350 let rho2 = (p.norm_squared() * 0.01).max(0.01);
351 let s2 = step.norm_squared();
352 if s2 > rho2 {
353 step *= (rho2 / s2).sqrt();
354 }
355 p -= step;
356 if step.norm_squared() < UNDISTORT_STEP_SQ_TOL {
357 return Some(p);
358 }
359 }
360 None
361 }
362
363 pub fn img_to_ray(&self, xy: &Vec2) -> Option<Vec3> {
365 self.img_to_normalized(xy).map(|uv| Vec3::new(uv.x, uv.y, 1.0).normalize())
366 }
367
368 pub fn cam_to_img_with_jacobian(&self, xc: &Vec3, d_params: Option<&mut [f64]>) -> Option<(Vec2, Matrix2x3<f64>)> {
374 if xc.z < f64::EPSILON {
375 return None;
376 }
377 let iz = 1.0 / xc.z;
378 let u = xc.x * iz;
379 let v = xc.y * iz;
380 let (fx, fy, cx, cy) = self.fxfycxcy();
381 let (du, dv, jd) = self.distortion(u, v);
382 let xd = u + du;
383 let yd = v + dv;
384 let pix = Vec2::new(fx * xd + cx, fy * yd + cy);
385 let d = Matrix2::identity() + jd;
386 let nm = Matrix2x3::new(iz, 0.0, -xc.x * iz * iz, 0.0, iz, -xc.y * iz * iz);
387 let fd = Matrix2::new(fx, 0.0, 0.0, fy);
388 let j_point = fd * d * nm;
389 if let Some(out) = d_params {
390 let n = self.model.num_params();
391 assert!(out.len() >= 2 * n, "d_params 길이 부족");
392 out[..2 * n].iter_mut().for_each(|x| *x = 0.0);
393 let r2 = u * u + v * v;
394 let (rx, ry) = out.split_at_mut(n);
395 match self.model {
396 CameraModelKind::SingleFocalPinhole => {
397 rx[0] = u;
398 rx[1] = 1.0;
399 ry[0] = v;
400 ry[2] = 1.0;
401 }
402 CameraModelKind::Pinhole => {
403 rx[0] = u;
404 rx[2] = 1.0;
405 ry[1] = v;
406 ry[3] = 1.0;
407 }
408 CameraModelKind::SingleFocalRadial | CameraModelKind::Radial => {
409 let f = fx;
410 rx[0] = xd;
411 rx[1] = 1.0;
412 rx[3] = f * u * r2;
413 ry[0] = yd;
414 ry[2] = 1.0;
415 ry[3] = f * v * r2;
416 if self.model == CameraModelKind::Radial {
417 rx[4] = f * u * r2 * r2;
418 ry[4] = f * v * r2 * r2;
419 }
420 }
421 CameraModelKind::OpenCv => {
422 rx[0] = xd;
423 rx[2] = 1.0;
424 rx[4] = fx * u * r2;
425 rx[5] = fx * u * r2 * r2;
426 rx[6] = fx * 2.0 * u * v;
427 rx[7] = fx * (r2 + 2.0 * u * u);
428 ry[1] = yd;
429 ry[3] = 1.0;
430 ry[4] = fy * v * r2;
431 ry[5] = fy * v * r2 * r2;
432 ry[6] = fy * (r2 + 2.0 * v * v);
433 ry[7] = fy * 2.0 * u * v;
434 }
435 }
436 }
437 Some((pix, j_point))
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use rand::{RngExt, SeedableRng};
445 use rand_pcg::Pcg64;
446
447 fn opencv(params: [f64; 8]) -> Camera {
448 Camera::new(1, CameraModelKind::OpenCv, 1000, 800, params.to_vec()).unwrap()
449 }
450
451 #[test]
452 fn opencv_hand_computed() {
453 let cam = opencv([1000.0, 1000.0, 500.0, 400.0, 0.1, 0.01, 0.001, -0.002]);
454 let p = cam.cam_to_img(&Vec3::new(0.2, -0.1, 1.0)).unwrap();
455 assert!((p.x - 700.705).abs() < 1e-9, "{}", p.x);
456 assert!((p.y - 299.6475).abs() < 1e-9, "{}", p.y);
457 assert!(cam.cam_to_img(&Vec3::new(0.0, 0.0, 0.0)).is_none());
458 assert!(cam.cam_to_img(&Vec3::new(0.0, 0.0, -1.0)).is_none());
459 }
460
461 #[test]
462 fn roundtrip_all_models() {
463 let mut rng = Pcg64::seed_from_u64(42);
464 for model in [
465 CameraModelKind::SingleFocalPinhole,
466 CameraModelKind::Pinhole,
467 CameraModelKind::SingleFocalRadial,
468 CameraModelKind::Radial,
469 CameraModelKind::OpenCv,
470 ] {
471 for _ in 0..300 {
472 let mut cam = Camera::from_focal(model, 900.0 + rng.random_range(0.0..200.0), 1000, 800);
473 let extra = model.extra_param_slots();
474 let ranges = [0.3, 0.1, 0.01, 0.01];
475 for (k, &i) in extra.iter().enumerate() {
476 cam.params[i] = rng.random_range(-ranges[k]..ranges[k]);
477 }
478 if model == CameraModelKind::OpenCv || model == CameraModelKind::Pinhole {
479 cam.params[1] = cam.params[0] * 1.01;
480 }
481 let uv = Vec2::new(rng.random_range(-0.8..0.8), rng.random_range(-0.8..0.8));
482 if (Matrix2::identity() + cam.distortion(uv.x, uv.y).2).determinant() <= 0.2 {
485 continue;
486 }
487 let px = cam.normalized_to_img(&uv);
488 let uv2 = cam.img_to_normalized(&px).expect("수렴해야 함");
489 assert!((uv2 - uv).norm() <= 1e-8, "{model:?} {uv:?} {uv2:?}");
490 let px2 = cam.normalized_to_img(&uv2);
491 assert!((px2 - px).norm() <= 1e-6);
492 }
493 }
494 }
495
496 #[test]
497 fn undistort_failure_no_nan() {
498 let cam = opencv([1000.0, 1000.0, 500.0, 400.0, -5.0, 0.0, 0.0, 0.0]);
499 let r = cam.img_to_normalized(&Vec2::new(500.0 + 900.0, 400.0 + 900.0));
501 assert!(r.is_none());
502 }
503
504 #[test]
505 fn distortion_jacobian_numeric() {
506 let mut rng = Pcg64::seed_from_u64(7);
507 for _ in 0..100 {
508 let cam = opencv([
509 1000.0,
510 1000.0,
511 500.0,
512 400.0,
513 rng.random_range(-0.3..0.3),
514 rng.random_range(-0.1..0.1),
515 rng.random_range(-0.01..0.01),
516 rng.random_range(-0.01..0.01),
517 ]);
518 let (u, v) = (rng.random_range(-0.8..0.8), rng.random_range(-0.8..0.8));
519 let (_, _, j) = cam.distortion(u, v);
520 let h = 1e-7;
521 let (a1, b1, _) = cam.distortion(u + h, v);
522 let (a0, b0, _) = cam.distortion(u - h, v);
523 let (c1, d1, _) = cam.distortion(u, v + h);
524 let (c0, d0, _) = cam.distortion(u, v - h);
525 let num = Matrix2::new((a1 - a0) / (2.0 * h), (c1 - c0) / (2.0 * h), (b1 - b0) / (2.0 * h), (d1 - d0) / (2.0 * h));
526 assert!((num - j).abs().max() <= 1e-6);
527 }
528 }
529
530 #[test]
531 fn projection_jacobian_numeric() {
532 let mut rng = Pcg64::seed_from_u64(9);
533 for model in [
534 CameraModelKind::SingleFocalPinhole,
535 CameraModelKind::Pinhole,
536 CameraModelKind::SingleFocalRadial,
537 CameraModelKind::Radial,
538 CameraModelKind::OpenCv,
539 ] {
540 for _ in 0..50 {
541 let mut cam = Camera::from_focal(model, 1200.0, 2000, 1500);
542 for &i in model.extra_param_slots() {
543 cam.params[i] = rng.random_range(-0.2..0.2);
544 }
545 if model == CameraModelKind::OpenCv {
546 cam.params[6] = rng.random_range(-0.01..0.01);
547 cam.params[7] = rng.random_range(-0.01..0.01);
548 }
549 let xc = Vec3::new(rng.random_range(-1.0..1.0), rng.random_range(-1.0..1.0), rng.random_range(2.0..5.0));
550 let n = model.num_params();
551 let mut dp = vec![0.0; 2 * n];
552 let (pix, jp) = cam.cam_to_img_with_jacobian(&xc, Some(&mut dp)).unwrap();
553 assert!((pix - cam.cam_to_img(&xc).unwrap()).norm() < 1e-12);
554 for k in 0..3 {
556 let h = 1e-6 * xc[k].abs().max(1.0);
557 let mut a = xc;
558 a[k] += h;
559 let mut b = xc;
560 b[k] -= h;
561 let num = (cam.cam_to_img(&a).unwrap() - cam.cam_to_img(&b).unwrap()) / (2.0 * h);
562 for r in 0..2 {
563 let an = jp[(r, k)];
564 assert!((num[r] - an).abs() <= 1e-6 * an.abs().max(1.0), "{model:?} pt {r},{k}: {} vs {}", num[r], an);
565 }
566 }
567 for k in 0..n {
569 let h = 1e-6 * cam.params[k].abs().max(1.0);
570 let mut a = cam.clone();
571 a.params[k] += h;
572 let mut b = cam.clone();
573 b.params[k] -= h;
574 let num = (a.cam_to_img(&xc).unwrap() - b.cam_to_img(&xc).unwrap()) / (2.0 * h);
575 for r in 0..2 {
576 let an = dp[r * n + k];
577 assert!((num[r] - an).abs() <= 1e-6 * an.abs().max(1.0), "{model:?} param {r},{k}: {} vs {}", num[r], an);
578 }
579 }
580 }
581 }
582 let cam = Camera::from_focal(CameraModelKind::OpenCv, 1000.0, 100, 100);
583 assert!(cam.cam_to_img_with_jacobian(&Vec3::new(0.0, 0.0, -1.0), None).is_none());
584 }
585
586 #[test]
587 fn model_tables_and_rescale() {
588 let cam = Camera::from_focal(CameraModelKind::OpenCv, 4800.0, 4000, 3000);
589 assert_eq!(cam.params, vec![4800.0, 4800.0, 2000.0, 1500.0, 0.0, 0.0, 0.0, 0.0]);
590 assert_eq!(CameraModelKind::from_name("OPENCV").unwrap(), CameraModelKind::OpenCv);
591 assert!(CameraModelKind::from_id(6).is_err());
592 assert!(Camera::new(1, CameraModelKind::OpenCv, 1, 1, vec![0.0; 7]).is_err());
593 let mut c = cam.clone();
594 c.rescale_to(960, 720);
595 assert!((c.params[0] - 4800.0 * 0.24).abs() < 1e-9);
596 assert!((c.params[3] - 1500.0 * 0.24).abs() < 1e-9);
597 assert!((cam.img_to_cam_threshold(4.0) - 4.0 / 4800.0).abs() < 1e-15);
598 assert!(!cam.has_implausible_params(0.1, 10.0, 1.0));
599 let mut b = cam.clone();
600 b.params[4] = 1.5;
601 assert!(b.has_implausible_params(0.1, 10.0, 1.0));
602 }
603}