From c9cd243c44b39eda735b89de09b3e4536ddeaea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <tmueller@nvidia.com> Date: Tue, 3 Jan 2023 13:12:09 +0100 Subject: [PATCH] `colmap2nerf.py`: download COLMAP and FFmpeg automatically on Windows --- .gitignore | 1 + docs/nerf_dataset_tips.md | 8 ++++++-- scripts/colmap2nerf.py | 41 +++++++++++++++++++++++++++++++------ scripts/download_colmap.bat | 14 +++++++++++++ scripts/download_ffmpeg.bat | 14 +++++++++++++ 5 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 scripts/download_colmap.bat create mode 100644 scripts/download_ffmpeg.bat diff --git a/.gitignore b/.gitignore index 0b222e8..ad9c8b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.vscode /.vs /build* +/external /figures /out /results diff --git a/docs/nerf_dataset_tips.md b/docs/nerf_dataset_tips.md index 7952082..f3d501a 100644 --- a/docs/nerf_dataset_tips.md +++ b/docs/nerf_dataset_tips.md @@ -3,6 +3,8 @@ Our NeRF implementation expects initial camera parameters to be provided in a `transforms.json` file in a format compatible with [the original NeRF codebase](https://www.matthewtancik.com/nerf). We provide a script as a convenience, [scripts/colmap2nerf.py](/scripts/colmap2nerf.py), that can be used to process a video file or sequence of images, using the open source [COLMAP](https://colmap.github.io/) structure from motion software to extract the necessary camera data. +If you are using Windows, you do not need to install COLMAP yourself; running [scripts/colmap2nerf.py](/scripts/colmap2nerf.py) will automatically download COLMAP for you. + The training process can be quite picky about the dataset. For example, it is important for the dataset to have good coverage, to not contain mislabelled camera data, and to not contain blurry frames (motion blur and defocus blur are both problematic). This document attempts to give a few tips. @@ -57,9 +59,11 @@ To train on self-captured data, one has to process the data into an existing for ### COLMAP -Make sure that you have installed [COLMAP](https://colmap.github.io/) and that it is available in your PATH. If you are using a video file as input, also be sure to install [FFmpeg](https://www.ffmpeg.org/) and make sure that it is available in your PATH. +If you use Linux, make sure that you have installed [COLMAP](https://colmap.github.io/) and that it is available in your PATH. If you are using a video file as input, also be sure to install [FFmpeg](https://www.ffmpeg.org/) and make sure that it is available in your PATH. To check that this is the case, from a terminal window, you should be able to run `colmap` and `ffmpeg -?` and see some help text from each. +If you use Windows, you do not need to install anything. COLMAP and FFmpeg will be downloaded automatically when running the following scripts. + If you are training from a video file, run the [scripts/colmap2nerf.py](/scripts/colmap2nerf.py) script from the folder containing the video, with the following recommended parameters: ```sh @@ -74,7 +78,7 @@ For training from images, place them in a subfolder called `images` and then use data-folder$ python [path-to-instant-ngp]/scripts/colmap2nerf.py --colmap_matcher exhaustive --run_colmap --aabb_scale 16 ``` -The script will run FFmpeg and/or COLMAP as needed, followed by a conversion step to the required `transforms.json` format, which will be written in the current directory. +The script will run (and install, if you use Windows) FFmpeg and COLMAP as needed, followed by a conversion step to the required `transforms.json` format, which will be written in the current directory. By default, the script invokes colmap with the "sequential matcher", which is suitable for images taken from a smoothly changing camera path, as in a video. The exhaustive matcher is more appropriate if the images are in no particular order, as shown in the image example above. For more options, you can run the script with `--help`. For more advanced uses of COLMAP or for challenging scenes, please see the [COLMAP documentation](https://colmap.github.io/cli.html); you may need to modify the [scripts/colmap2nerf.py](/scripts/colmap2nerf.py) script itself. diff --git a/scripts/colmap2nerf.py b/scripts/colmap2nerf.py index 943373c..4dd0b9c 100755 --- a/scripts/colmap2nerf.py +++ b/scripts/colmap2nerf.py @@ -9,6 +9,7 @@ # license agreement from NVIDIA CORPORATION is strictly prohibited. import argparse +from glob import glob import os from pathlib import Path, PurePosixPath @@ -20,6 +21,9 @@ import cv2 import os import shutil +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +SCRIPTS_FOLDER = os.path.join(ROOT_DIR, "scripts") + def parse_args(): parser = argparse.ArgumentParser(description="Convert a text colmap export to nerf format transforms.json; optionally convert video to images, and optionally run colmap in the first place.") @@ -49,8 +53,21 @@ def do_system(arg): sys.exit(err) def run_ffmpeg(args): + ffmpeg_binary = "ffmpeg" + if os.name == 'nt': + ffmpeg_glob = os.path.join(ROOT_DIR, "external", "ffmpeg", "*", "bin", "ffmpeg.exe") + candidates = glob(ffmpeg_glob) + if not candidates: + print("FFmpeg not found. Attempting to download FFmpeg from the internet.") + do_system(os.path.join(SCRIPTS_FOLDER, "download_ffmpeg.bat")) + candidates = glob(ffmpeg_glob) + + if candidates: + ffmpeg_binary = candidates[0] + if not os.path.isabs(args.images): args.images = os.path.join(os.path.dirname(args.video_in), args.images) + images = "\"" + args.images + "\"" video = "\"" + args.video_in + "\"" fps = float(args.video_fps) or 1.0 @@ -69,9 +86,21 @@ def run_ffmpeg(args): if time_slice: start, end = time_slice.split(",") time_slice_value = f",select='between(t\,{start}\,{end})'" - do_system(f"ffmpeg -i {video} -qscale:v 1 -qmin 1 -vf \"fps={fps}{time_slice_value}\" {images}/%04d.jpg") + do_system(f"{ffmpeg_binary} -i {video} -qscale:v 1 -qmin 1 -vf \"fps={fps}{time_slice_value}\" {images}/%04d.jpg") def run_colmap(args): + colmap_binary = "colmap" + if os.name == 'nt': + colmap_glob = os.path.join(ROOT_DIR, "external", "colmap", "*", "COLMAP.bat") + candidates = glob(colmap_glob) + if not candidates: + print("COLMAP not found. Attempting to download COLMAP from the internet.") + do_system(os.path.join(SCRIPTS_FOLDER, "download_colmap.bat")) + candidates = glob(colmap_glob) + + if candidates: + colmap_binary = candidates[0] + db = args.colmap_db images = "\"" + args.images + "\"" db_noext=str(Path(db).with_suffix("")) @@ -85,8 +114,8 @@ def run_colmap(args): sys.exit(1) if os.path.exists(db): os.remove(db) - do_system(f"colmap feature_extractor --ImageReader.camera_model {args.colmap_camera_model} --ImageReader.camera_params \"{args.colmap_camera_params}\" --SiftExtraction.estimate_affine_shape=true --SiftExtraction.domain_size_pooling=true --ImageReader.single_camera 1 --database_path {db} --image_path {images}") - match_cmd = f"colmap {args.colmap_matcher}_matcher --SiftMatching.guided_matching=true --database_path {db}" + do_system(f"{colmap_binary} feature_extractor --ImageReader.camera_model {args.colmap_camera_model} --ImageReader.camera_params \"{args.colmap_camera_params}\" --SiftExtraction.estimate_affine_shape=true --SiftExtraction.domain_size_pooling=true --ImageReader.single_camera 1 --database_path {db} --image_path {images}") + match_cmd = f"{colmap_binary} {args.colmap_matcher}_matcher --SiftMatching.guided_matching=true --database_path {db}" if args.vocab_path: match_cmd += f" --VocabTreeMatching.vocab_tree_path {args.vocab_path}" do_system(match_cmd) @@ -95,14 +124,14 @@ def run_colmap(args): except: pass do_system(f"mkdir {sparse}") - do_system(f"colmap mapper --database_path {db} --image_path {images} --output_path {sparse}") - do_system(f"colmap bundle_adjuster --input_path {sparse}/0 --output_path {sparse}/0 --BundleAdjustment.refine_principal_point 1") + do_system(f"{colmap_binary} mapper --database_path {db} --image_path {images} --output_path {sparse}") + do_system(f"{colmap_binary} bundle_adjuster --input_path {sparse}/0 --output_path {sparse}/0 --BundleAdjustment.refine_principal_point 1") try: shutil.rmtree(text) except: pass do_system(f"mkdir {text}") - do_system(f"colmap model_converter --input_path {sparse}/0 --output_path {text} --output_type TXT") + do_system(f"{colmap_binary} model_converter --input_path {sparse}/0 --output_path {text} --output_type TXT") def variance_of_laplacian(image): return cv2.Laplacian(image, cv2.CV_64F).var() diff --git a/scripts/download_colmap.bat b/scripts/download_colmap.bat new file mode 100644 index 0000000..cac83db --- /dev/null +++ b/scripts/download_colmap.bat @@ -0,0 +1,14 @@ +@echo off + +set cwd=%cd% +cd /D %~dp0 + +echo Downloading COLMAP... +powershell -Command "(New-Object Net.WebClient).DownloadFile('https://github.com/colmap/colmap/releases/download/3.7/COLMAP-3.7-windows-no-cuda.zip', 'colmap.zip')" + +echo Unzipping... +powershell Expand-Archive colmap.zip -DestinationPath ..\external\colmap -Force + +echo Cleaning up... +if exist colmap.zip del /f /q colmap.zip +exit /b diff --git a/scripts/download_ffmpeg.bat b/scripts/download_ffmpeg.bat new file mode 100644 index 0000000..62b4828 --- /dev/null +++ b/scripts/download_ffmpeg.bat @@ -0,0 +1,14 @@ +@echo off + +set cwd=%cd% +cd /D %~dp0 + +echo Downloading FFmpeg... +powershell -Command "(New-Object Net.WebClient).DownloadFile('https://github.com/GyanD/codexffmpeg/releases/download/5.1.2/ffmpeg-5.1.2-full_build.zip', 'ffmpeg.zip')" + +echo Unzipping... +powershell Expand-Archive ffmpeg.zip -DestinationPath ..\external\ffmpeg -Force + +echo Cleaning up... +if exist ffmpeg.zip del /f /q ffmpeg.zip +exit /b -- GitLab