-
-
Save polyfjord/4ed7e8988bdb9674145f1c270440200d to your computer and use it in GitHub Desktop.
| :: ================================================================ | |
| :: BATCH SCRIPT FOR AUTOMATED PHOTOGRAMMETRY TRACKING WORKFLOW | |
| :: By polyfjord - https://youtube.com/polyfjord | |
| :: ================================================================ | |
| :: USAGE | |
| :: • Double-click this .bat or run it from a command prompt. | |
| :: • Frames are extracted, features matched, and a sparse | |
| :: reconstruction is produced automatically. | |
| :: • Videos that have already been processed are skipped on | |
| :: subsequent runs. | |
| :: | |
| :: PURPOSE | |
| :: This is a fully automated photogrammetry tracker for turning | |
| :: videos into COLMAP sparse models with robust error handling, | |
| :: clean directory setup, and clear ✖ / ✔ logging. | |
| :: | |
| :: FOLDER LAYOUT (all folders sit side-by-side): | |
| :: 01 COLMAP – Download the latest release from | |
| :: https://github.com/colmap/colmap | |
| :: and place colmap.bat (plus its dlls) here. | |
| :: | |
| :: 02 VIDEOS – Put your input video files (.mp4, .mov, …) here. | |
| :: All framerates and aspect ratios are supported. | |
| :: | |
| :: 03 FFMPEG – Drop a **static build** of FFmpeg | |
| :: (either ffmpeg.exe or bin\ffmpeg.exe) here. | |
| :: | |
| :: 04 SCENES – The script creates one sub-folder per video | |
| :: containing extracted frames, the COLMAP | |
| :: database, sparse model, and TXT export. | |
| :: | |
| :: 05 SCRIPTS – This batch file lives here. | |
| :: | |
| :: ================================================================ | |
| @echo off | |
| :: ---------- Resolve top-level folder (one up from this .bat) ----- | |
| pushd "%~dp0\.." >nul | |
| set "TOP=%cd%" | |
| popd >nul | |
| :: ---------- Key paths ------------------------------------------- | |
| set "COLMAP_DIR=%TOP%\01 COLMAP" | |
| set "VIDEOS_DIR=%TOP%\02 VIDEOS" | |
| set "FFMPEG_DIR=%TOP%\03 FFMPEG" | |
| set "SCENES_DIR=%TOP%\04 SCENES" | |
| :: ---------- Locate ffmpeg.exe ----------------------------------- | |
| if exist "%FFMPEG_DIR%\ffmpeg.exe" ( | |
| set "FFMPEG=%FFMPEG_DIR%\ffmpeg.exe" | |
| ) else if exist "%FFMPEG_DIR%\bin\ffmpeg.exe" ( | |
| set "FFMPEG=%FFMPEG_DIR%\bin\ffmpeg.exe" | |
| ) else ( | |
| echo [ERROR] ffmpeg.exe not found inside "%FFMPEG_DIR%". | |
| pause & goto :eof | |
| ) | |
| :: ---------- Locate colmap.exe (skip the .bat) -------------------- | |
| if exist "%COLMAP_DIR%\colmap.exe" ( | |
| set "COLMAP=%COLMAP_DIR%\colmap.exe" | |
| ) else if exist "%COLMAP_DIR%\bin\colmap.exe" ( | |
| set "COLMAP=%COLMAP_DIR%\bin\colmap.exe" | |
| ) else ( | |
| echo [ERROR] colmap.exe not found inside "%COLMAP_DIR%". | |
| pause & goto :eof | |
| ) | |
| :: ---------- Put COLMAP’s dll folder(s) on PATH ------------------- | |
| set "PATH=%COLMAP_DIR%;%COLMAP_DIR%\bin;%PATH%" | |
| :: ---------- Ensure required folders exist ------------------------ | |
| if not exist "%VIDEOS_DIR%" ( | |
| echo [ERROR] Input folder "%VIDEOS_DIR%" missing. | |
| pause & goto :eof | |
| ) | |
| if not exist "%SCENES_DIR%" mkdir "%SCENES_DIR%" | |
| :: ---------- Count videos for progress bar ------------------------ | |
| for /f %%C in ('dir /b /a-d "%VIDEOS_DIR%\*" ^| find /c /v ""') do set "TOTAL=%%C" | |
| if "%TOTAL%"=="0" ( | |
| echo [INFO] No video files found in "%VIDEOS_DIR%". | |
| pause & goto :eof | |
| ) | |
| echo ============================================================== | |
| echo Starting COLMAP on %TOTAL% video(s) … | |
| echo ============================================================== | |
| setlocal EnableDelayedExpansion | |
| set /a IDX=0 | |
| for %%V in ("%VIDEOS_DIR%\*.*") do ( | |
| if exist "%%~fV" ( | |
| set /a IDX+=1 | |
| call :PROCESS_VIDEO "%%~fV" "!IDX!" "%TOTAL%" | |
| ) | |
| ) | |
| echo -------------------------------------------------------------- | |
| echo All jobs finished – results are in "%SCENES_DIR%". | |
| echo -------------------------------------------------------------- | |
| pause | |
| goto :eof | |
| :PROCESS_VIDEO | |
| :: ---------------------------------------------------------------- | |
| :: %1 = full path to video %2 = current index %3 = total | |
| :: ---------------------------------------------------------------- | |
| setlocal | |
| set "VIDEO=%~1" | |
| set "NUM=%~2" | |
| set "TOT=%~3" | |
| for %%I in ("%VIDEO%") do ( | |
| set "BASE=%%~nI" | |
| set "EXT=%%~xI" | |
| ) | |
| echo. | |
| echo [!NUM!/!TOT!] === Processing "!BASE!!EXT!" === | |
| :: -------- Directory layout for this scene ----------------------- | |
| set "SCENE=%SCENES_DIR%\!BASE!" | |
| set "IMG_DIR=!SCENE!\images" | |
| set "SPARSE_DIR=!SCENE!\sparse" | |
| :: -------- Skip if already reconstructed ------------------------- | |
| if exist "!SCENE!" ( | |
| echo ↻ Skipping "!BASE!" – already reconstructed. | |
| goto :END | |
| ) | |
| :: Clean slate ---------------------------------------------------- | |
| mkdir "!IMG_DIR!" >nul | |
| mkdir "!SPARSE_DIR!" >nul | |
| :: -------- 1) Extract every frame -------------------------------- | |
| echo [1/4] Extracting frames … | |
| "%FFMPEG%" -loglevel error -stats -i "!VIDEO!" -qscale:v 2 ^ | |
| "!IMG_DIR!\frame_%%06d.jpg" | |
| if errorlevel 1 ( | |
| echo ✖ FFmpeg failed – skipping "!BASE!". | |
| goto :END | |
| ) | |
| :: Check at least one frame exists | |
| dir /b "!IMG_DIR!\*.jpg" >nul 2>&1 || ( | |
| echo ✖ No frames extracted – skipping "!BASE!". | |
| goto :END | |
| ) | |
| :: -------- 2) Feature extraction --------------------------------- | |
| echo [2/4] COLMAP feature_extractor … | |
| "%COLMAP%" feature_extractor ^ | |
| --database_path "!SCENE!\database.db" ^ | |
| --image_path "!IMG_DIR!" ^ | |
| --ImageReader.single_camera 1 ^ | |
| --SiftExtraction.use_gpu 1 ^ | |
| --SiftExtraction.max_image_size 4096 | |
| if errorlevel 1 ( | |
| echo ✖ feature_extractor failed – skipping "!BASE!". | |
| goto :END | |
| ) | |
| :: -------- 3) Sequential matching -------------------------------- | |
| echo [3/4] COLMAP sequential_matcher … | |
| "%COLMAP%" sequential_matcher ^ | |
| --database_path "!SCENE!\database.db" ^ | |
| --SequentialMatching.overlap 15 | |
| if errorlevel 1 ( | |
| echo ✖ sequential_matcher failed – skipping "!BASE!". | |
| goto :END | |
| ) | |
| :: -------- 4) Sparse reconstruction ------------------------------ | |
| echo [4/4] COLMAP mapper … | |
| "%COLMAP%" mapper ^ | |
| --database_path "!SCENE!\database.db" ^ | |
| --image_path "!IMG_DIR!" ^ | |
| --output_path "!SPARSE_DIR!" ^ | |
| --Mapper.num_threads %NUMBER_OF_PROCESSORS% | |
| if errorlevel 1 ( | |
| echo ✖ mapper failed – skipping "!BASE!". | |
| goto :END | |
| ) | |
| :: -------- Export best model to TXT ------------------------------ | |
| if exist "!SPARSE_DIR!\0" ( | |
| "%COLMAP%" model_converter ^ | |
| --input_path "!SPARSE_DIR!\0" ^ | |
| --output_path "!SPARSE_DIR!" ^ | |
| --output_type TXT >nul | |
| ) | |
| echo ✔ Finished "!BASE!" (!NUM!/!TOT!) | |
| :END | |
| endlocal & goto :eof |
I think I've fixed it, apparently related to a colmap update. If anyone else has this open batch_reconstruct.bat in any editing program, and change
:: -------- 2) Feature extraction ---------------------------------
echo [2/4] COLMAP feature_extractor …
"%COLMAP%" feature_extractor ^
--database_path "!SCENE!\database.db" ^
--image_path "!IMG_DIR!" ^
--ImageReader.single_camera 1 ^
--SiftExtraction.use_gpu 1 ^
--SiftExtraction.max_image_size 4096
if errorlevel 1 (
echo ✖ feature_extractor failed – skipping "!BASE!".
goto :END
)
to
:: -------- 2) Feature extraction ---------------------------------
echo [2/4] COLMAP feature_extractor …
"%COLMAP%" feature_extractor ^
--database_path "!SCENE!\database.db" ^
--image_path "!IMG_DIR!" ^
--ImageReader.single_camera 1 ^
--FeatureExtraction.use_gpu 1 ^
--SiftExtraction.max_image_size 4096
if errorlevel 1 (
echo ✖ feature_extractor failed – skipping "!BASE!".
goto :END
)
SiftExtraction still exists for the image size i presume as if i switch that to FeatureExtraction aswell it breaks again. I'll update if that was the solution.
EDIT: this was the solution
For some reason ffmpeg was incorrectly detecting the video framerate for me.
It was extrating the images at 120 fps but my video was recorded at 30 (which means each frame was repeated 4 times). So I had to manually set the -fpsmax:v 30 flag (which was my framerate). This reduced the processing time by 75% already.
But if the video moves slow enough (which was my case) the software is capable of extrating good enough information with a much less framerate.
I successfully processed a (4k, 65 second) video in 5 minutes by setting the FPS down to 3.
I think I've fixed it, apparently related to a colmap update. If anyone else has this open batch_reconstruct.bat in any editing program, and change
:: -------- 2) Feature extraction --------------------------------- echo [2/4] COLMAP feature_extractor … "%COLMAP%" feature_extractor ^ --database_path "!SCENE!\database.db" ^ --image_path "!IMG_DIR!" ^ --ImageReader.single_camera 1 ^ --SiftExtraction.use_gpu 1 ^ --SiftExtraction.max_image_size 4096 if errorlevel 1 ( echo ✖ feature_extractor failed – skipping "!BASE!". goto :END )to
:: -------- 2) Feature extraction --------------------------------- echo [2/4] COLMAP feature_extractor … "%COLMAP%" feature_extractor ^ --database_path "!SCENE!\database.db" ^ --image_path "!IMG_DIR!" ^ --ImageReader.single_camera 1 ^ --FeatureExtraction.use_gpu 1 ^ --SiftExtraction.max_image_size 4096 if errorlevel 1 ( echo ✖ feature_extractor failed – skipping "!BASE!". goto :END )SiftExtraction still exists for the image size i presume as if i switch that to FeatureExtraction aswell it breaks again. I'll update if that was the solution.
EDIT: this was the solution
did ur fix , still the same problem , did u do anything else ?
EDIT : the video title was in arabic , changed to english and it worked for me
For macOS using either the native Terminal app or Ghostty, a different approach, a script that can receive one or several videos and outputs the result in a folder with the same name as the input:
#!/usr/bin/env bash
# ================================================================
# AUTOMATED PHOTOGRAMMETRY TRACKING WORKFLOW
# ================================================================
# USAGE
# chmod +x colmap_photogrammetry.sh
# ./colmap_photogrammetry.sh video1.mp4 video2.mov …
#
# OUTPUT
# Each input file produces a sibling directory named after the
# file (without extension), containing:
# images/ – extracted frames
# database.db – COLMAP feature database
# sparse/ – sparse reconstruction + TXT export
#
# DEPENDENCIES (must be on $PATH)
# ffmpeg – brew install ffmpeg
# colmap – brew install colmap
# ================================================================
set -euo pipefail
# ----------------------------------------------------------------
# Helpers
# ----------------------------------------------------------------
log() { echo " $*"; }
die() { echo "[ERROR] $*" >&2; exit 1; }
# ----------------------------------------------------------------
# Dependency check
# ----------------------------------------------------------------
for cmd in ffmpeg colmap; do
command -v "$cmd" &>/dev/null || die "'$cmd' not found on \$PATH. Install via: brew install $cmd"
done
# ----------------------------------------------------------------
# Usage check
# ----------------------------------------------------------------
if [[ $# -eq 0 ]]; then
echo "Usage: $(basename "$0") <video1> [video2 …]"
exit 1
fi
NUM_THREADS="$(sysctl -n hw.logicalcpu 2>/dev/null || echo 4)"
TOTAL=$#
IDX=0
# ----------------------------------------------------------------
# Process one video
# ----------------------------------------------------------------
process_video() {
local VIDEO="$1"
[[ -f "$VIDEO" ]] || { log "✖ Not a file: \"$VIDEO\" – skipping."; return 0; }
local BASE="${VIDEO%.*}" # path minus extension
local NAME="$(basename "$BASE")" # filename only, for labels
IDX=$(( IDX + 1 ))
echo ""
echo "[$IDX/$TOTAL] === Processing \"$(basename "$VIDEO")\" ==="
# -------- Output directories ---------------------------------
local SCENE="$BASE"
local IMG_DIR="$SCENE/images"
local SPARSE_DIR="$SCENE/sparse"
# Overwrite: wipe and recreate
rm -rf "$SCENE"
mkdir -p "$IMG_DIR" "$SPARSE_DIR"
# -------- 1) Extract every frame -----------------------------
log "[1/4] Extracting frames …"
if ! ffmpeg -loglevel error -stats -i "$VIDEO" -qscale:v 2 \
"$IMG_DIR/frame_%06d.jpg"; then
log "✖ ffmpeg failed – skipping \"$NAME\"."
rm -rf "$SCENE"; return 0
fi
ls "$IMG_DIR"/*.jpg &>/dev/null || {
log "✖ No frames extracted – skipping \"$NAME\"."
rm -rf "$SCENE"; return 0
}
# -------- 2) Feature extraction ------------------------------
log "[2/4] COLMAP feature_extractor …"
if ! colmap feature_extractor \
--database_path "$SCENE/database.db" \
--image_path "$IMG_DIR" \
--ImageReader.single_camera 1 \
--SiftExtraction.use_gpu 0 \
--SiftExtraction.max_image_size 4096; then
log "✖ feature_extractor failed – skipping \"$NAME\"."
rm -rf "$SCENE"; return 0
fi
# -------- 3) Sequential matching -----------------------------
log "[3/4] COLMAP sequential_matcher …"
if ! colmap sequential_matcher \
--database_path "$SCENE/database.db" \
--SequentialMatching.overlap 15; then
log "✖ sequential_matcher failed – skipping \"$NAME\"."
rm -rf "$SCENE"; return 0
fi
# -------- 4) Sparse reconstruction ---------------------------
log "[4/4] COLMAP mapper …"
if ! colmap mapper \
--database_path "$SCENE/database.db" \
--image_path "$IMG_DIR" \
--output_path "$SPARSE_DIR" \
--Mapper.num_threads "$NUM_THREADS"; then
log "✖ mapper failed – skipping \"$NAME\"."
rm -rf "$SCENE"; return 0
fi
# -------- Export best model to TXT ---------------------------
if [[ -d "$SPARSE_DIR/0" ]]; then
colmap model_converter \
--input_path "$SPARSE_DIR/0" \
--output_path "$SPARSE_DIR" \
--output_type TXT &>/dev/null
fi
log "✔ Finished \"$NAME\" ($IDX/$TOTAL)"
}
# ----------------------------------------------------------------
# Main loop
# ----------------------------------------------------------------
echo "=============================================================="
echo " Starting COLMAP on $TOTAL video(s) …"
echo "=============================================================="
for VIDEO in "$@"; do
process_video "$VIDEO"
done
echo ""
echo "=============================================================="
echo " All jobs finished."
echo "=============================================================="Similar scripts:
https://github.com/rodrigopolo/clis/tree/main/GaussianSplatting
When I run the script, it gives me the following output
this puts 2 folders in 04 SCENES, images, and sparse. the images folder has the frames extracted correctly, but the sparse folder is empty.