My family has a box of printed photos from the 80s and 90s. I wanted to digitize them. The workflow: place four or five photos on the flatbed scanner, scan once, get a TIFF with multiple photos at odd angles, then manually crop each one in Photoshop.
After doing that for about twenty minutes I started thinking about automating it.
Auto Image Cropper is the result: a browser-based tool that takes a folder of scanned images and automatically detects, straightens, and extracts individual photos — no server, no installation, just OpenCV running in WebAssembly.
How it works
The detection pipeline runs entirely in the browser via @techstark/opencv-js (OpenCV 4.12 compiled to WASM):
- Grayscale + blur — reduce noise before edge detection
- Canny edge detection — find strong edges in the image
- Dilation — close small gaps that break contours
- Contour finding +
approxPolyDP— isolate closed polygons - Quadrilateral filtering — keep only 4-sided shapes whose corners are close to 90°
- Deduplication — remove overlapping detections by centroid distance
- Perspective warp —
getPerspectiveTransform+warpPerspectivemaps each quad to a flat rectangle
If no clean quads are found (e.g., the photo edges are too close to the scanner bed edge), there’s an adaptive fallback using minAreaRect on an adaptively thresholded image.
The results are displayed in a grid. You can rotate or delete individual crops, then download everything as a ZIP via JSZip.
Why run this in the browser?
The obvious approach is a Python script with OpenCV. But a browser-based tool has real advantages for this use case:
- No installation — works for anyone with a modern browser
- Private by default — family photos never leave the device
- Instant feedback — see crops as they’re detected, fix and re-export without re-running a script
- Cross-platform — one codebase, works everywhere
The trade-off is load time. The OpenCV WASM binary is large (~8 MB). I mitigate this by loading it lazily in a Web Worker so the UI remains responsive, and by caching it aggressively. After the first load, subsequent visits are instant.
The challenges
Getting OpenCV to run in a Web Worker
OpenCV’s WASM build assumes a browser context — it touches document and window in a few places that break in a Worker. I had to use the @techstark/opencv-js fork specifically because it patches out those assumptions.
Even then, initializing OpenCV in the Worker requires careful sequencing: wait for the WASM binary to load, wait for the Module to initialize, then signal the main thread that it’s ready. Getting this handshake right without race conditions took a few iterations.
The right-angle filter
The naive version of the quad filter just looks for closed 4-sided contours. The problem is that scanners produce a lot of near-rectangular artifacts — cable shadows, the scanner lid edge, paper edges — that also pass this filter.
The right-angle filter measures the angle at each corner of the quad. A photo edge at a slight angle might be 80° at each corner; the scanner lid at the image boundary might be 89.9°. Tuning the tolerance (I settled on ±20° from 90°) was an empirical process — too tight and you miss photos shot at a slight angle, too loose and you get false positives.
Perspective warp accuracy
The perspective transform takes four source points (the corners of the detected quad) and four destination points (the corners of the output rectangle). Getting the output dimensions right — so the result isn’t stretched — requires knowing the real-world aspect ratio of the photo.
Since I don’t know the actual photo size, I approximate it from the quad’s side lengths. This works well for standard photo sizes (4×6, 5×7) but can produce slightly stretched output for non-standard crops. A future improvement would let the user confirm the output dimensions before downloading.
Batch processing performance
Processing a folder of twenty large TIFFs sequentially is slow. I parallelized the pipeline using multiple Web Workers — one per CPU core — with a job queue dispatched from the main thread. On a modern machine this cuts total processing time roughly in half. The UI shows a live progress bar per file so you can see which ones are still being processed.
Try it
The app is live at auto-image-cropper.netlify.app. Open it, select a folder of scanned images, and watch it work. The source is on GitHub.
It’s not perfect — photos with very dark edges or that overlap each other on the scanner bed will sometimes trip it up — but it handles the common case (well-separated photos on a white scanner bed) reliably and saves a lot of manual cropping.