AI and agents6 filesInfrastructure2 filesDevelopment5 filesAbout4 files

Digitizing old paper photos — from a scanned tabletop to finished cropped frames

  • computer vision
  • OpenCV
  • automation

Digitizing old paper photos — from a scanned tabletop to finished cropped frames

Paper photos had been piling up at home. A photo studio charges thirty roubles per frame, which across a thousand photos becomes a line item of its own — so I laid them out on a table, shot them sheet by sheet, and wanted the individual frames without cutting each one out by hand.

The cascade detector

A single pass runs into a seesaw: raise the sensitivity and it finds pieces of background, lower it and low-contrast photos disappear. Instead of endlessly tuning thresholds I built a cascade of five passes (otsu, canny, adaptive, eroded, otsu again) where each one subtracts what has already been found and duplicates are dropped by region overlap. On a fifteen-frame test set that raised recall from 43 to 81 photos found.

Beyond that, thresholds stop helping in principle: without knowing how many photos were on the table, the algorithm cannot tell “two photos side by side” from “one photo broken apart by erosion”. So on top of the cascade there is a manual frame corrector: a local web UI on canvas where boxes are fixed in seconds. It is an honest trade: automation does 90% of the work and a human closes the rest, instead of tuning the detector forever.

The full run: 191 scans, 897 automatic cuts, 846 usable frames after manual cleanup.

Two halves of the system

The cloud one came first: a PWA on the phone takes the shot, a presigned upload through a Yandex Cloud Function puts it in a bucket, a processor on the Mac picks it up, crops and corrects it, and a Telegram bot sends failures back asking for a retake — while the photo is still on the table. Later came a local pipeline with no cloud at all: an input folder, an output folder, HEIC, tabletop cropping, the cascade, and my own web tools for correction and before/after comparison.

The bug that cost two frames

The first iPhone shooting test failed silently: photos settled in IndexedDB and never left. Three causes at once — the queue never started on app launch, errors were swallowed by an empty catch, and, crucially, WebKit loses Blob contents in IndexedDB between sessions, so the upload went out zero bytes long. The fix is storing an ArrayBuffer instead of a Blob with a checksum at enqueue time, starting the queue on launch, rejecting empty bodies, and showing a visible “N queued” indicator. Two morning frames could not be recovered — they simply did not exist.

A separate lesson: the CORS preflight to the cloud function only failed from a browser. Everything worked from the console because curl does not send OPTIONS.

The finish

All 846 frames went through restoration on a local stack: face-based auto-orientation, face restoration through CodeFormer, colour correction. Two decisions came out of the test run: I dropped background upscaling (it smeared the film grain and tripled the runtime) and cancelled automatic colourization of black-and-white shots — one out of ten came out acceptable.