$ cat browser-document-scanner-opencv.jsonDocument Scanner — Computer Vision in the Browser
Turns a phone photo into a clean, flat scan with Python and OpenCV running entirely in the browser; the photo is never uploaded. Open-source code and a live demo.

Project Overview
The goal was a scanner like CamScanner, built with classic computer vision only, without anyone having to upload their documents to someone else's server. The textbook pipeline (Canny, contour detection, perspective transform, thresholding) worked perfectly on synthetic data, and not on real photos.
Most of the work was closing that gap: a background touching the page edge and corrupting the contour, a halo around every word after shadow removal, JPEG and paper texture, and a spiral notebook with no closed outline at all. Each cause was found from the intermediate images, and each fix got a test that fails with the old code and passes with the new one.
Finally, the same Python code moved into the browser with Pyodide: the server only serves static files, processing costs nothing, and photos never leave the user's device.
Key Highlights
- Python and OpenCV entirely in the browser, no processing server
- Photos never leave the user's device
- Two page detectors, with a sanity check against wrong answers
- Every real-world bug covered by a regression test
- Color, grayscale, black-and-white and PDF output
- Open source, with a full case study on GitHub
Technical Implementation
About 350 lines of Python with OpenCV and NumPy. Two page detectors: an edge-based one (using RETR_LIST so the clean inner contour survives) and a paper-color one (bright, low-saturation in HSV) for when no closed outline exists, with a sanity check that lets it reject its own wrong answer. Shadow removal uses morphological closing on a downscaled copy, and levels instead of a percentile stretch.
It runs on Pyodide (CPython compiled to WebAssembly) with a small bridge between the canvas and NumPy, and an HTML/JS interface with draggable corners that work on touch. The site's strict CSP is relaxed only for the tool's path, and the Python files are revalidated on load so browsers never run a stale version.


