Skip to main content

cumulus3d_cli/
densewrap.rs

1//! 조밀화 공용 경로: 구역 밖 영상 제외 → 왜곡 보정 → 장면 변환 → densify.
2//! 스트림과 `densify` 하위 명령이 함께 쓴다.
3
4use cumulus3d_core::{ImageId, Reconstruction};
5use cumulus3d_dense::{
6    undistort, DenseOutput, DenseScene, DensifyOptions, DepthMapCache, ImageBuffer, MvsProfile, PatchMatchBackend, SceneOptions, UndistortCache,
7    UndistortOptions,
8};
9use std::collections::BTreeMap;
10use std::path::{Path, PathBuf};
11use std::sync::{Arc, Mutex};
12use std::time::{Duration, Instant};
13
14/// PatchMatch 백엔드 선택. 지금은 `cuda` 만 있다. 장치가 없으면 오류 문자열.
15pub fn make_pm_backend(name: &str) -> Result<Arc<dyn PatchMatchBackend>, String> {
16    match name {
17        "cuda" => cumulus3d_cuda::CudaPatchMatch::try_default()
18            .map(|b| Arc::new(b) as Arc<dyn PatchMatchBackend>)
19            .map_err(|e| format!("--pm-backend cuda: {e} (조밀화에는 CUDA 장치가 필요)")),
20        other => Err(format!("알 수 없는 --pm-backend {other} (cuda)")),
21    }
22}
23
24/// `--mvs-profile` 해석.
25pub fn parse_profile(s: &str) -> Result<MvsProfile, String> {
26    MvsProfile::parse(s).ok_or_else(|| format!("알 수 없는 --mvs-profile {s} (fast|quality)"))
27}
28
29/// SIFT 백엔드 선택(cpu|cuda).
30pub fn make_sift_backend(name: &str) -> Result<Arc<dyn cumulus3d_features::SiftEngine>, String> {
31    match name {
32        "cpu" => Ok(Arc::new(cumulus3d_features::CpuSift::default())),
33        "cuda" => cumulus3d_cuda::CudaSift::try_default()
34            .map(|b| Arc::new(b) as Arc<dyn cumulus3d_features::SiftEngine>)
35            .map_err(|e| format!("--sift-backend cuda: {e}")),
36        other => Err(format!("알 수 없는 --sift-backend {other} (cpu|cuda)")),
37    }
38}
39
40/// 기술자 매칭 백엔드 선택(cpu|cuda).
41pub fn make_match_backend(name: &str) -> Result<Box<dyn cumulus3d_matching::MatcherBackend>, String> {
42    match name {
43        "cpu" => Ok(Box::new(cumulus3d_matching::CpuMatcher::default())),
44        "cuda" => cumulus3d_cuda::CudaMatcher::try_default()
45            .map(|b| Box::new(b) as Box<dyn cumulus3d_matching::MatcherBackend>)
46            .map_err(|e| format!("--match-backend cuda: {e}")),
47        other => Err(format!("알 수 없는 --match-backend {other} (cpu|cuda)")),
48    }
49}
50
51/// 조밀화 설정.
52#[derive(Clone)]
53pub struct DenseConfig {
54    /// 왜곡 보정 옵션.
55    pub undistort: UndistortOptions,
56    /// 조밀 장면 변환 옵션.
57    pub scene: SceneOptions,
58    /// 조밀화(PatchMatch·필터·융합) 옵션.
59    pub densify: DensifyOptions,
60    /// 점수 융합 설정(있으면 `densify.fusion.mode` 대신 점수 융합).
61    pub score: Option<cumulus3d_dense::fusion_score::ScoreFusionOptions>,
62    /// 백엔드(만들지 못했으면 그 오류; 조밀화 단계에서 보고).
63    pub backend: Result<Arc<dyn PatchMatchBackend>, String>,
64    /// `--serialize-dense`: 백엔드 호출을 한 번에 하나로.
65    pub lock: Option<Arc<Mutex<()>>>,
66    /// 깊이맵 캐시(`--depth-cache`).
67    pub depth_cache: Option<Arc<DepthMapCache>>,
68    /// 왜곡 보정 결과 캐시.
69    pub undistort_cache: Arc<UndistortCache>,
70}
71
72impl DenseConfig {
73    /// 왜곡 보정 max_image_size 960, 프로파일 기본값.
74    pub fn new(backend: Result<Arc<dyn PatchMatchBackend>, String>, profile: MvsProfile) -> Self {
75        Self {
76            undistort: UndistortOptions::pipeline(),
77            scene: SceneOptions::default(),
78            densify: DensifyOptions::with_profile(profile),
79            score: None,
80            backend,
81            lock: None,
82            depth_cache: None,
83            undistort_cache: Arc::new(UndistortCache::new()),
84        }
85    }
86}
87
88/// 조밀화 결과 요약.
89pub struct DenseRun {
90    /// 조밀화에 쓴 영상 수.
91    pub frames: usize,
92    /// 조밀화 입력 장면.
93    pub scene: DenseScene,
94    /// 조밀화 결과(점군 등).
95    pub output: DenseOutput,
96    /// 왜곡 보정 시간.
97    pub undistort_time: Duration,
98    /// 조밀화 시간.
99    pub densify_time: Duration,
100    /// 백엔드 직렬화 잠금 대기 시간.
101    pub lock_wait: Duration,
102}
103
104/// `keep` 이 거짓인 등록 영상을 빼고(image_deleter) 조밀화한다. 보정 전 입력 영상은 `image_root/이름`.
105pub fn dense_model(model: &Reconstruction, keep: impl Fn(&str) -> bool, image_root: &Path, cfg: &DenseConfig) -> Result<DenseRun, String> {
106    let backend = cfg.backend.clone()?;
107    let t0 = Instant::now();
108    let mut m = model.clone();
109    let del: Vec<String> = m
110        .registered_images()
111        .into_iter()
112        .filter_map(|id| m.image(id).map(|im| im.name.clone()))
113        .filter(|n| !keep(n))
114        .collect();
115    let del_ref: Vec<&str> = del.iter().map(|s| s.as_str()).collect();
116    m.deregister_images_by_name(&del_ref);
117    let root: PathBuf = image_root.to_path_buf();
118    let und = undistort(&m, &cfg.undistort, &cfg.undistort_cache, |im| ImageBuffer::load(root.join(&im.name)).ok().map(Arc::new))
119        .map_err(|e| format!("왜곡 보정 실패: {e}"))?;
120    let images: BTreeMap<ImageId, Arc<ImageBuffer>> = und.images;
121    let frames = images.len();
122    let scene = DenseScene::from_reconstruction(&und.reconstruction, &images, &cfg.scene).map_err(|e| format!("장면 변환 실패: {e}"))?;
123    drop(images);
124    let undistort_time = t0.elapsed();
125    let tw = Instant::now();
126    let guard = cfg.lock.as_ref().map(|l| l.lock().unwrap_or_else(|p| p.into_inner()));
127    let lock_wait = tw.elapsed();
128    let t1 = Instant::now();
129    let output = cumulus3d_dense::densify::densify_with(&scene, &cfg.densify, cfg.score.as_ref(), backend.as_ref(), cfg.depth_cache.as_deref()).map_err(|e| format!("조밀화 실패: {e}"));
130    drop(guard);
131    let output = output?;
132    Ok(DenseRun { frames, scene, output, undistort_time, densify_time: t1.elapsed(), lock_wait })
133}