Auto Image Cropper: Using OpenCV in the Browser to Fix Scanned Photos

How I built a browser-based tool that uses OpenCV.js (WebAssembly) to automatically detect, straighten, and crop individual photos from flatbed scanner output — and what I learned about computer vision at the edge.

My family has a box of printed photos from the 80s and 90s, and I wanted to digitize them. The workflow was: put four or five photos on the flatbed scanner, scan once, get a TIFF with several photos at odd angles, then crop each one by hand in Photoshop.

After about twenty minutes of that I started thinking about automating it.

Auto Image Cropper is the result. It’s a browser-based tool that takes a folder of scanned images and automatically detects, straightens, and extracts the individual photos. There’s no server and nothing to install; OpenCV runs in WebAssembly.

How it works

The detection pipeline runs entirely in the browser via @techstark/opencv-js (OpenCV 4.12 compiled to WASM):

  1. Grayscale and blur, to reduce noise before edge detection
  2. Canny edge detection, to find strong edges
  3. Dilation, to close small gaps that break contours
  4. Contour finding with approxPolyDP, to isolate closed polygons
  5. Quadrilateral filtering, which keeps only 4-sided shapes with corners close to 90°
  6. Deduplication, which removes overlapping detections by centroid distance
  7. Perspective warp: getPerspectiveTransform and warpPerspective map each quad to a flat rectangle

If no clean quads turn up (say, the photo edges sit too close to the edge of the scanner bed), there’s a fallback that runs minAreaRect on an adaptively thresholded image.

Results show up in a grid. You can rotate or delete individual crops, then download everything as a ZIP via JSZip.

Why run this in the browser?

A Python script with OpenCV would have been the usual choice. For this use case a browser tool has real advantages. Anyone with a modern browser can use it without installing anything. Family photos never leave the device. You see crops as they’re detected and can fix and re-export without re-running a script. And one codebase works on every platform.

The cost is load time. The OpenCV WASM binary is large (~8 MB). I load it lazily in a Web Worker so the UI stays responsive, and cache it aggressively, so after the first visit it loads instantly.

The challenges

Getting OpenCV to run in a Web Worker

OpenCV’s WASM build assumes a browser context and touches document and window in a few places, which breaks inside a Worker. I used the @techstark/opencv-js fork because it patches those out.

Even then, initializing OpenCV in the Worker needs careful sequencing: wait for the WASM binary to load, wait for the Module to initialize, then tell the main thread it’s ready. Getting that handshake right without race conditions took a few iterations.

The right-angle filter

The naive quad filter just looks for closed 4-sided contours. Scanners produce plenty of near-rectangular artifacts, like cable shadows, the lid edge, and paper edges, and those pass that filter too.

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°. I tuned the tolerance by trial and error and settled on ±20° from 90°. Too tight and you miss photos placed 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). To get output dimensions that aren’t stretched, you need the real-world aspect ratio of the photo.

I don’t know the actual photo size, so I approximate it from the quad’s side lengths. That works well for standard sizes (4×6, 5×7) but can stretch non-standard crops slightly. A future version could let the user confirm the output dimensions before downloading.

Batch processing performance

Processing a folder of twenty large TIFFs one after another is slow. I parallelized the pipeline across multiple Web Workers, one per CPU core, with a job queue dispatched from the main thread. On a modern machine that roughly halves total processing time. The UI shows a live progress bar per file so you can see which ones are still running.

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 isn’t perfect. Photos with very dark edges, or ones that overlap on the scanner bed, sometimes trip it up. But it reliably handles the common case of well-separated photos on a white scanner bed, and it saves a lot of manual cropping.