-
-
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 |
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
My configuration: [Colmap Nocuda Version: 4.0.2] [FFmpeg Essentials Version: 8.1]
After the latest update, my computer which does not have a supported GPU did not function as intended.
So after 2 hours of debugging, I have found a fix to the following errors:
[(Warn) No images with matches -> (Error) Failed to create any sparse model Γ£û mapper failed ΓÇô skipping "IMG"]
And also:
[qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
This application failed to start because no Qt platform plugin could be initialized.]
Fixing Qt platform plugin issues:
- Locate qwindows.dll inside your COLMAP install folder — it will be somewhere like \plugins\platforms\qwindows.dll
- Open System Properties → Environment Variables
- Under System Variables click New and add:
- Variable name: QT_QPA_PLATFORM_PLUGIN_PATH
- Variable value: the full path to the platforms folder containing qwindows.dll (e.g. C:\COLMAP\plugins\platforms)
- Click OK, then close and reopen any terminal windows for the variable to take effect
To fix issues revolving the non-cuda version (CPU Version):
-
:: -------- 2) Feature Extraction --------------------------------
Change "--SiftExtraction.use_gpu 1 ^" to "--FeatureExtraction.use_gpu 0 ^"
Change "--SiftExtraction.max_image_size 4096" to "--FeatureExtraction.max_image_size 4096" -
:: -------- 3) Sequential matching --------------------------------
add: "--FeatureMatching.use_gpu false ^"
(optional) add: "--FeatureMatching.max_num_matches 16384" - Not 100% needed but just incase it gives a "gpu vram size too low" error
(I hope this helps someone)
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