242 lines
17 KiB
Markdown
242 lines
17 KiB
Markdown
|
|
# Rectify
|
|||
|
|
|
|||
|
|
Perspective correction for paintings and rectangular objects. Takes a photo of a painting on a wall (taken at an angle) and produces a head-on, undistorted rectangular image.
|
|||
|
|
|
|||
|
|
## Quick start
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# From source
|
|||
|
|
python3 -m venv .venv
|
|||
|
|
source .venv/bin/activate
|
|||
|
|
pip install -r requirements.txt
|
|||
|
|
python -m rectify --gui photo.jpg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Or download a pre-built executable from the releases page — no Python required.
|
|||
|
|
|
|||
|
|
## Setup from source
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git clone https://git.andykopra.com/ack/rectify.git
|
|||
|
|
cd rectify
|
|||
|
|
python3 -m venv .venv
|
|||
|
|
source .venv/bin/activate
|
|||
|
|
pip install -r requirements.txt
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Platform notes:**
|
|||
|
|
- **Linux:** May need `sudo apt install libxcb-xinerama0 libxcb-cursor0` for Qt
|
|||
|
|
- **macOS:** Works with Homebrew or python.org Python
|
|||
|
|
- **Windows:** See [Run from source on Windows](#run-from-source-on-windows) below
|
|||
|
|
|
|||
|
|
### Run from source on Windows
|
|||
|
|
|
|||
|
|
Every dependency ships a Windows wheel — PySide6 bundles Qt and `pillow-heif`
|
|||
|
|
bundles libheif — so `pip install` is the whole build step. No compiler and no
|
|||
|
|
system libraries are required, and the **Open** dialog is the standard Explorer
|
|||
|
|
dialog, with image thumbnails.
|
|||
|
|
|
|||
|
|
**Prerequisites:** Python 3.10–3.13 from
|
|||
|
|
[python.org](https://www.python.org/downloads/windows/) — tick **"Add python.exe
|
|||
|
|
to PATH"** in the installer. `git` is optional; you can download the repository
|
|||
|
|
as a ZIP instead.
|
|||
|
|
|
|||
|
|
**PowerShell:**
|
|||
|
|
|
|||
|
|
```powershell
|
|||
|
|
git clone https://git.andykopra.com/ack/rectify.git
|
|||
|
|
cd rectify
|
|||
|
|
py -m venv .venv
|
|||
|
|
.\.venv\Scripts\Activate.ps1
|
|||
|
|
pip install -r requirements.txt
|
|||
|
|
python -m rectify --gui
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
If PowerShell refuses to run the activation script, allow it for that window
|
|||
|
|
only:
|
|||
|
|
|
|||
|
|
```powershell
|
|||
|
|
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Command Prompt** is identical except for the activation line:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
.venv\Scripts\activate.bat
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Usage is the same as on the other platforms — see [Usage](#usage) below, and
|
|||
|
|
`python -m rectify --help` for the CLI flags. Per-user settings are written to
|
|||
|
|
`%APPDATA%\<username>\Rectify\settings.json`.
|
|||
|
|
|
|||
|
|
**If something goes wrong:**
|
|||
|
|
|
|||
|
|
- *`ImportError: DLL load failed while importing QtCore`* — install the
|
|||
|
|
[Microsoft Visual C++ 2015–2022 Redistributable (x64)](https://aka.ms/vs/17/release/vc_redist.x64.exe),
|
|||
|
|
which Qt needs. It is already present on most Windows installations.
|
|||
|
|
- *`py` is not recognized* — the Python launcher was not installed; use
|
|||
|
|
`python -m venv .venv` instead, or re-run the installer with **"Add python.exe
|
|||
|
|
to PATH"** ticked.
|
|||
|
|
- *Blurry or oversized UI on a scaled display* — Rectify scales its own fonts;
|
|||
|
|
report the display scaling percentage along with a screenshot.
|
|||
|
|
|
|||
|
|
**Reporting back on a Windows trial.** Windows is not yet a regularly tested
|
|||
|
|
platform, so the following are the points most likely to differ. Please note how
|
|||
|
|
each behaves:
|
|||
|
|
|
|||
|
|
1. Opening a **HEIC/HEIF** photo from an iPhone.
|
|||
|
|
2. Display scaling at 125% and 150% — menu, panel, and control sizing.
|
|||
|
|
3. The keyboard shortcuts (press **Shift+Alt** for the overlay) — all use Ctrl
|
|||
|
|
on Windows.
|
|||
|
|
4. Saving to a folder whose path contains spaces.
|
|||
|
|
5. Building an executable with `pyinstaller rectify.spec` (see
|
|||
|
|
[Building a standalone executable](#building-a-standalone-executable)) and
|
|||
|
|
launching the resulting `dist\rectify.exe`.
|
|||
|
|
|
|||
|
|
## Usage
|
|||
|
|
|
|||
|
|
### GUI (primary interface)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python -m rectify --gui # launch empty, open file later
|
|||
|
|
python -m rectify --gui photo.jpg # launch with an image
|
|||
|
|
python -m rectify --gui /path/to/photos/ # launch with file dialog in that directory
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The GUI provides:
|
|||
|
|
- **Input formats** — JPEG, PNG, TIFF, BMP, WebP, and **HEIC/HEIF** (iPhone photos). HEICs are decoded to their SDR base image and converted from Display P3 to sRGB, so colors are correct; any HDR gain map is ignored (the right behavior for SDR documentation). Output is saved in the standard formats below
|
|||
|
|
- **Two-panel view** — original image with detected quad (left), rectified result (right)
|
|||
|
|
- **Zoom and pan** — mouse wheel to zoom (anchors under cursor), right-click to reset zoom, drag outside the quad to pan when zoomed in
|
|||
|
|
- **Automatic detection** — opens the image, picks the best strategy (grayscale or saturation) and sensitivity by sweeping all combinations, and presents the result. No knobs to set in the GUI; for fine-tuning the detected quad, use the manual editing gestures below
|
|||
|
|
- **Aspect ratio correction** — recovers the true width/height of a photographed rectangle from EXIF focal length and perspective geometry; manual override via a labelled checkbox plus value slider. The value's styling is a cue: a **gray italic** value means no action is needed from you (the correction is off, or a reliable value was recovered automatically from the photo's camera data); a **black** value means the photo has no usable camera data (e.g. a screenshot) so you should set the ratio by eye; a **red** value means the recovered ratio is wider/taller than the slider's 0.10–10.0 range, so it's pinned at the limit and the proportions can't be fully reached. Applies to Extract and Transform → Quad output; hidden in Transform → Lines mode, which has its own **Stretch** control instead (below)
|
|||
|
|
- **Bow correction** — centre-origin slider (−200…200 px) that straightens edges which bow in the extracted output (typical mild residual lens distortion). Corner-anchored: corners stay fixed and mid-edge content is moved. The value is a radial distance in output pixels — positive straightens inward-bowed (pincushion) edges by pushing content outward, negative straightens outward-bowed (barrel) edges by pulling content inward (converted internally to a curvature coefficient against the output's half-diagonal). Drag the slider, use the spin box, or type a value to find the minimum magnitude that straightens the edges. Per-image: cached so switching between images preserves each image's bow value. Extract mode only
|
|||
|
|
- **Color correct** — recover accurate color when a neutral reference is in the frame. Tick **Color correct**, then choose a reference mode — **Gray** or **White** (neither is preselected; pick the one that matches your reference). Click the swatch, then click the reference area in the left image; Rectify averages a small square around that point — its side length in pixels is the **Sample size** (1 = just the clicked pixel; default 10 = a 10×10 block) — and white-balances the output so the patch becomes neutral. **Gray** mode (a gray card, or any surface you trust as neutral gray) couples color and exposure: the **Reflectance** value is the target tone the patch is mapped to, in the photographer's unit — default **18%** (standard middle-gray card); set it to your card's rating (e.g. 12%) or adjust to taste (adjustable 3–80%). **White** mode (a white sheet or other white reference) *decouples* them: it neutralizes the color cast while keeping the patch's own brightness — so white is **not** forced to maximum and anything brighter (a highlight, a lamp) keeps its headroom — and a centered **Brightness** slider (0 = unchanged) then raises or lowers the whole result independently. The swatch shows the sampled color and turns red if the spot is too bright or too dark to use; a 2-pixel-wide red border marks the sampled region, with the averaged pixels just inside it. Sample the card outside the artwork — it's read from the source image but the correction is applied to the result, in every mode. **The measured gray is sticky:** shoot one reference frame with a gray card, pick it once, then for other images taken under the same light just tick Color correct and they reuse that gray automatically (no need for a card in every shot). Reusing a gray takes a frozen *snapshot* — re-picking the reference later won't silently change images that already borrowed it. **Left-click** the swatch to pick a gray on the current image; **right-click** it to re-apply the last-used gray (handy when an image already has its own value you want to replace). An image you picked on keeps its own measurement and shows the red marker; one using a borrowed gray shows the color in the swatch but no marker
|
|||
|
|
- **Corner dragging** — click and drag corners; arrow keys for 1-pixel nudge
|
|||
|
|
- **Edge dragging** — click near an edge and drag to move it perpendicular to itself; arrow keys for fine nudging
|
|||
|
|
- **Ctrl+Left-Click** — moves the nearest movable point (a quad corner in Extract mode, a keystone-line arrow handle in Lines mode) to the click position. Designed for precision adjustment while zoomed in; the cursor becomes a crosshair whenever Ctrl is held so you know the gesture is armed
|
|||
|
|
- **Alt+Left-Click ×2** — draws a red antialiased "alternate-line" annotation between two clicked points. Useful for sketching what auto-detection *should* produce in screenshots and bug reports. A dashed rubber-band tracks the cursor between the first and second click; multiple annotations accumulate; Esc clears them all (and any temporary marks in general); a new image load also clears.
|
|||
|
|
- **Alignment indicator** — edges turn blue when their endpoints are vertically or horizontally aligned (same x or y value); the same blue/green indicator applies to keystone-line pairs in Lines mode
|
|||
|
|
- **Subregion selection** — click in the center of the quad and drag to reposition; scroll wheel while dragging to resize. Use with Peel in to extract individual subjects from multi-subject photos
|
|||
|
|
- **Peel stack** — Peel in/out to strip successive frame layers; left panel always shows the original with mapped-back overlay
|
|||
|
|
- **Full-image perspective correction** — correct the entire image's perspective using a reference quad (e.g., a window or known rectangle), like a view camera tilt/shift. The Transform-mode **Crop** checkbox switches between a filled full canvas (default) and an auto-cropped rectangle
|
|||
|
|
- **Keystone correction** — remove vertical and/or horizontal keystone distortion using line pairs. Draw one or two pairs of lines on features known to be parallel (e.g., building edges, door frames); the correction makes them parallel in the output. Arrow-shaped handles distinguish lines from the quad overlay
|
|||
|
|
- **Stretch** (Transform → Lines) — keystone correction straightens converging lines but can't recover how wide the result should be relative to its height (the lines carry no scale, and there's no reference rectangle as in Quad mode). The **Stretch** slider is a by-eye correction for that residual width-to-height relationship: 1.00 leaves the width unchanged, above 1.00 widens, below narrows. Shown only in Lines mode; remembered per image
|
|||
|
|
- **Saving** — one **Save** button with an **Increment** checkbox. The default output name is the source image's name with **`_rectified`** appended (e.g. `hotel.png` → `hotel_rectified.png`), so a save never overwrites the input. With Increment **off**, Save opens a dialog (pre-filled with that default) where the extension you type picks the format (png/jpg/jpeg/tiff/tif/webp/bmp; an unsupported type is rejected with the supported list). With Increment **on**, Save writes the next auto-numbered file (`hotel_rectified_1`, `hotel_rectified_2`, …) with one click — no dialog. Both modes share the last-used folder and type; folder, type, and the Increment setting persist across sessions
|
|||
|
|
- **Undo/redo** — Ctrl+Z / Ctrl+Shift+Z for corner adjustments (keyboard only)
|
|||
|
|
- **Drag and drop** — drop an image file onto the window
|
|||
|
|
- **Before/after** — hold Space to compare
|
|||
|
|
- **Tooltips** — hover any control for a short explanation in a readable boxed popup; toolbar buttons also show their keyboard shortcut. Fully translated in all three interface languages
|
|||
|
|
- **Settings persistence** — all preferences (including your last save folder, file type, and Increment mode), window layout, and per-image state (corners, keystone lines, bow value, Stretch value, color-correction sample, plus any manual override of the aspect ratio) cached for every image you've touched. Switch between images with Ctrl+↑/↓ while preparing a batch; come back to any image and your tuning is intact. Saved on close and restored on next launch
|
|||
|
|
|
|||
|
|
### Keyboard shortcuts
|
|||
|
|
|
|||
|
|
| Shortcut | Action |
|
|||
|
|
|----------|--------|
|
|||
|
|
| Ctrl+O | Open image |
|
|||
|
|
| Ctrl+S | Save (opens the dialog, or writes the next auto-numbered file when Increment is on) |
|
|||
|
|
| Ctrl+D | Reset (re-detect from scratch) |
|
|||
|
|
| Ctrl+R | Re-open the current file (re-read pixels from disk; per-image cache preserves corners, keystone pairs, bow) |
|
|||
|
|
| Ctrl+↓ / Ctrl+↑ | Load next / previous image in the current directory (wraps around) |
|
|||
|
|
| Ctrl+Z | Undo |
|
|||
|
|
| Ctrl+Shift+Z | Redo |
|
|||
|
|
| + or = | Peel in |
|
|||
|
|
| - | Peel out |
|
|||
|
|
| Space (hold) | Before/after comparison |
|
|||
|
|
| Arrow keys | Nudge selected corner, edge, or whole quad |
|
|||
|
|
| Mouse wheel | Zoom (or adjust element with Shift or left-button held) |
|
|||
|
|
| Shift + wheel | Adjust quad element under cursor (corner, edge, or whole quad) |
|
|||
|
|
| Ctrl + Left-click | Snap the nearest point (corner or line endpoint) to the click position |
|
|||
|
|
| Alt + Left-click ×2 | Draw a red alternate-line annotation between two clicks |
|
|||
|
|
| Esc | Clear temporary marks (annotations, etc.) |
|
|||
|
|
| Right-click | Reset zoom on clicked panel |
|
|||
|
|
| 0 | Reset zoom on both panels |
|
|||
|
|
| Shift+Alt (hold) | Show keyboard-shortcut overlay (centered popup) |
|
|||
|
|
|
|||
|
|
The same shortcut table is printed by `rectify -k` (also `--keyboard`), so you can read it without opening the GUI.
|
|||
|
|
|
|||
|
|
### Command line
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python -m rectify photo.jpg -o rectified.jpg
|
|||
|
|
python -m rectify photo.jpg -o rectified.jpg --strategy saturation -s 0.7
|
|||
|
|
python -m rectify photo.jpg -o rectified.jpg --peel 1
|
|||
|
|
|
|||
|
|
# Full-image perspective correction
|
|||
|
|
python -m rectify photo.jpg -o corrected.jpg --full-image
|
|||
|
|
python -m rectify photo.jpg -o corrected.jpg --full-image --full-image-crop
|
|||
|
|
python -m rectify photo.jpg -o corrected.jpg --full-image --fill-color "#808080"
|
|||
|
|
|
|||
|
|
# Debug logging (writes detection details to a log file)
|
|||
|
|
python -m rectify --gui photo.jpg --debug
|
|||
|
|
python -m rectify --gui photo.jpg --debug my_debug.log
|
|||
|
|
|
|||
|
|
# Incremental output (auto-numbered)
|
|||
|
|
python -m rectify photo.jpg --dir output/ --prefix museum --ext jpg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Detection strategies
|
|||
|
|
|
|||
|
|
The GUI always runs the automatic two-pass sweep — both detection strategies, both ends of the sensitivity range, best result wins. The CLI exposes manual overrides via `--strategy {auto, grayscale, saturation}`, `-s/--sensitivity`, and the individual Canny parameters (`--blur`, `--canny-low`, `--canny-high`, `--min-area`, `--epsilon`).
|
|||
|
|
|
|||
|
|
- **Grayscale** — Edge detection on luminance. Best when subject and background differ in brightness.
|
|||
|
|
- **Saturation** — Edge detection on HSV saturation channel (no blur). Best when brightness is similar but color richness differs (e.g., tiles on brick).
|
|||
|
|
- **Auto** (default) — Evaluates both using a two-pass sensitivity sweep (coarse then fine), picks the best. Scores candidates by rectangularity, margin from image edges, and perspective plausibility (vanishing-point orthogonality). Prefers larger regions initially; smaller on peel-in.
|
|||
|
|
|
|||
|
|
## Installation methods
|
|||
|
|
|
|||
|
|
### Pre-built executables
|
|||
|
|
|
|||
|
|
Download the executable for your platform from the releases page. No Python installation is required.
|
|||
|
|
|
|||
|
|
**Linux:**
|
|||
|
|
```bash
|
|||
|
|
chmod +x rectify
|
|||
|
|
./rectify --gui photo.jpg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**macOS:**
|
|||
|
|
- Download `Rectify.dmg`, open it, and drag `Rectify.app` to your Applications folder
|
|||
|
|
- Double-click `Rectify.app` to launch — it opens the GUI with a file dialog
|
|||
|
|
- You can also drag an image file onto the `Rectify.app` icon in Finder or the Dock to open it directly
|
|||
|
|
- Or download the command-line executable and run from Terminal:
|
|||
|
|
```bash
|
|||
|
|
chmod +x rectify
|
|||
|
|
./rectify --gui photo.jpg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Windows:**
|
|||
|
|
- Download `rectify.exe`
|
|||
|
|
- Double-click to launch (opens the GUI with a file dialog)
|
|||
|
|
- Or run from Command Prompt / PowerShell:
|
|||
|
|
```powershell
|
|||
|
|
.\rectify.exe --gui photo.jpg
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Building a standalone executable
|
|||
|
|
|
|||
|
|
To create a distributable executable from source, two files are provided:
|
|||
|
|
|
|||
|
|
- **`build.sh`** — Shell script that installs PyInstaller (if needed) and runs the build. Supports `--onedir` for faster development builds.
|
|||
|
|
- **`rectify.spec`** — PyInstaller spec file with the build configuration, including hidden imports for PySide6, macOS `.app` bundle settings, and image file type associations.
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pip install pyinstaller
|
|||
|
|
./build.sh # Linux/macOS: single-file executable
|
|||
|
|
./build.sh --onedir # Linux/macOS: directory build (faster startup)
|
|||
|
|
pyinstaller rectify.spec # Windows (from command prompt)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The signed, notarized macOS `.dmg` is produced by the maintainer — it needs
|
|||
|
|
installer artwork and an Apple Developer ID, so it is not something this source
|
|||
|
|
distribution can build. Download it from the releases page instead.
|
|||
|
|
|
|||
|
|
The output appears in `dist/`. On macOS, `rectify.spec` also creates a `Rectify.app` bundle with the bundle identifier `com.andykopra.rectify`.
|
|||
|
|
|
|||
|
|
**Platform-specific build notes:**
|
|||
|
|
|
|||
|
|
- **Linux:** The resulting binary is platform-specific (not cross-platform). It may require `libxcb` libraries on the target system.
|
|||
|
|
- **macOS:** The spec file includes `BUNDLE` configuration for a `.app` bundle. Code signing may be needed for distribution outside of direct sharing.
|
|||
|
|
- **Windows:** Use `pyinstaller rectify.spec` from a command prompt. The spec sets `console=False` to suppress the console window.
|
|||
|
|
|
|||
|
|
See `doc/Rectify_user_guide.md` for full documentation including usage examples and a programmer's guide.
|