Skip to content

Instantly share code, notes, and snippets.

@celestial-33
Forked from polyfjord/batch_reconstruct.bat
Last active August 30, 2025 14:35
Show Gist options
  • Select an option

  • Save celestial-33/0778b1f9d9ffdd44c9223c5c3897f996 to your computer and use it in GitHub Desktop.

Select an option

Save celestial-33/0778b1f9d9ffdd44c9223c5c3897f996 to your computer and use it in GitHub Desktop.
Bash script for automated photogrammetry tracking workflow for Mac

Automated Video to 3D Scan Workflow for macOS

This repository contains a Bash script for automating a photogrammetry workflow on macOS. It takes video files as input and uses FFmpeg and COLMAP to generate a 3D sparse point cloud.

This script is a modified version of AeroGenesiX’s modified version of Polyfjord’s original Windows batch script, rewritten to run smoothly on macOS systems.


Features

  • One-Command Automation: Processes all videos in the input folder in a single run.
  • GPU Acceleration: Utilizes Apple Silicon GPU support in COLMAP for faster processing.
  • Frame Range Fix: Corrects the “2× frame range” issue that appears on some macOS setups.
  • Error Handling: Skips already processed videos and ensures the workflow executes step by step without breaking.
  • Organized Output: Keeps results clean with a structured project folder.
  • Apple Silicon Ready: Tested on MacBook Air M4 with Homebrew’s COLMAP build, and should work on other macOS systems too.

Requirements

Software

  • COLMAP – Structure-from-motion and multi-view stereo pipeline.

  • FFmpeg – For frame extraction.

    • Recommended installation via Homebrew:
      brew install ffmpeg colmap
    • Or via Conda:
      conda install -c conda-forge colmap

Folder Structure

    Project_Folder/
    ├── VIDEOS/      # Place input video files (.mp4, .mov) here.
    ├── SCENES/      # The script saves all outputs here.
    └── SCRIPTS/     # The run_colmap.sh script lives here.

How to Use

  1. Set Up Folders: Create the structure shown above.

  2. Add Videos: Copy your .mp4 or .mov files into the VIDEOS/ folder.

  3. Save the Script: Place the script (run_colmap.sh) in the SCRIPTS/ folder.

  4. Update Paths: Edit the script and update these variables to match your system installation: (If installed via Homebrew only, both are usually in `/opt/homebrew/bin/.) If you are unable to find paths, run these command to find where colmap and ffmpeg binaries are located

    which colmap ffmpeg
    
  5. Make it Executable:

    chmod +x run_colmap.sh
  6. Run the Script: From inside the SCRIPTS/ folder:

    ./run_colmap.sh

Troubleshooting

  • COLMAP not found: Run which colmap and update the script path.

  • FFmpeg not found: Run which ffmpeg and update the script path.

  • Slow processing: If it feels too slow, downscale your input video or reduce --SiftExtraction.max_image_size in the script (e.g., from 4096 → 2048).

  • GPU out of memory: Try lowering the frame size as above, or process shorter clips.


Conclusion

I’ve also successfully adapted Polyfjord’s batch script to run with GLOMAP on macOS. I’ll provide updates and documentation for that workflow when I get more time.

#!/bin/bash
# ================================================================
# BASH SCRIPT FOR AUTOMATED PHOTOGRAMMETRY TRACKING WORKFLOW (macOS Apple Silicon)
# Simplified: No CUDA checks, uses fixed paths.
# Please read the readme-mac.md for detailed steps.
# ================================================================
set -e # Stop on error
# --- Resolve top-level folder (one up from this script) ---
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
TOP="$(dirname "$SCRIPT_DIR")"
# --- Key paths ---
VIDEOS_DIR="$TOP/VIDEOS"
SCENES_DIR="$TOP/SCENES"
# --- Fixed executables (your setup) ---
FFMPEG="/opt/homebrew/bin/ffmpeg"
COLMAP="/usr/local/bin/colmap"
# --- CPU thread count (macOS) ---
NUM_THREADS=$(sysctl -n hw.ncpu)
# --- Ensure executables exist ---
if [ ! -x "$FFMPEG" ]; then
echo "[ERROR] ffmpeg not found at $FFMPEG" >&2
exit 1
fi
if [ ! -x "$COLMAP" ]; then
echo "[ERROR] colmap not found at $COLMAP" >&2
exit 1
fi
# --- Ensure required folders exist ---
if [ ! -d "$VIDEOS_DIR" ]; then
echo "[ERROR] Input folder '$VIDEOS_DIR' missing." >&2
exit 1
fi
mkdir -p "$SCENES_DIR"
# --- Count videos ---
TOTAL=$(find "$VIDEOS_DIR" -maxdepth 1 -type f | wc -l | tr -d ' ')
if [ "$TOTAL" -eq 0 ]; then
echo "[INFO] No video files found in '$VIDEOS_DIR'."
exit 0
fi
echo "=============================================================="
echo " Starting COLMAP on $TOTAL video(s) …"
echo "=============================================================="
IDX=0
for VIDEO_FILE in "$VIDEOS_DIR"/*; do
[ -f "$VIDEO_FILE" ] || continue
IDX=$((IDX + 1))
FILENAME=$(basename -- "$VIDEO_FILE")
BASE="${FILENAME%.*}"
echo
echo "[$IDX/$TOTAL] === Processing \"$FILENAME\" ==="
SCENE_DIR="$SCENES_DIR/$BASE"
IMG_DIR="$SCENE_DIR/images"
SPARSE_DIR="$SCENE_DIR/sparse"
if [ -d "$SCENE_DIR" ]; then
echo " ↻ Skipping \"$BASE\" – already reconstructed."
continue
fi
mkdir -p "$IMG_DIR" "$SPARSE_DIR"
echo " [1/4] Extracting frames …"
"$FFMPEG" -loglevel error -stats -i "$VIDEO_FILE" -qscale:v 2 \
"$IMG_DIR/frame_%06d.jpg"
find "$IMG_DIR" -name '._*' -delete
if ! ls "$IMG_DIR"/*.jpg &> /dev/null; then
echo " ✖ No frames extracted – skipping \"$BASE\"."
rm -r "$SCENE_DIR"
continue
fi
echo " [2/4] COLMAP feature_extractor (CPU only)…"
"$COLMAP" feature_extractor \
--database_path "$SCENE_DIR/database.db" \
--image_path "$IMG_DIR" \
--ImageReader.single_camera 1 \
--SiftExtraction.max_image_size 2048
echo " [3/4] COLMAP sequential_matcher …"
"$COLMAP" sequential_matcher \
--database_path "$SCENE_DIR/database.db" \
--SequentialMatching.overlap 15
echo " [4/4] COLMAP mapper …"
"$COLMAP" mapper \
--database_path "$SCENE_DIR/database.db" \
--image_path "$IMG_DIR" \
--output_path "$SPARSE_DIR" \
--Mapper.num_threads "$NUM_THREADS"
if [ -d "$SPARSE_DIR/0" ]; then
"$COLMAP" model_converter \
--input_path "$SPARSE_DIR/0" \
--output_path "$SCENE_DIR" \
--output_type TXT &> /dev/null
fi
echo " ✔ Finished \"$BASE\" ($IDX/$TOTAL)"
done
echo "--------------------------------------------------------------"
echo " All jobs finished – results are in \"$SCENES_DIR\"."
echo "--------------------------------------------------------------"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment