чат с агентомспроситьAI и агенты7 файловИнфраструктура4 файлаРазработка6 файловОбо мне5 файлов
Оцифровка старых бумажных фото - от скана стола до готовых кадров без ручной резки
  • компьютерное зрение
  • OpenCV
  • автоматизация

Оцифровка старых бумажных фото - от скана стола до готовых кадров без ручной резки

Дома копилась пачка бумажных фотографий. Фотостудия просит тридцать рублей за кадр - на тысяче снимков это отдельная статья расходов, поэтому раскладывал фото на столе, снимал листами и хотел получить отдельные кадры без ручной обрезки каждого.

Каскадный детектор

Одиночный проход упирается в качели: поднимаешь чувствительность - находятся куски фона, опускаешь

  • теряются фото с низким контрастом. Вместо бесконечного подбора порогов сделал каскад из пяти проходов (otsu, canny, adaptive, eroded, снова otsu), где каждый следующий вычитает уже найденное, а дубли снимаются по пересечению областей. На тестовом наборе из пятнадцати кадров это подняло recall с 43 до 81 найденной фотографии.

Дальше пороги перестают помогать принципиально: не зная, сколько фото лежало на столе, алгоритм не отличит «два снимка рядом» от «одного, разбитого эрозией на части». Поэтому поверх каскада - ручной корректор рамок: локальный веб-интерфейс на canvas, где рамки поправляются за секунды. Это честный размен: автоматика делает 90% работы, человек закрывает остаток, вместо того чтобы бесконечно тюнить детектор.

Боевой прогон: 191 скан, 897 автоматических вырезок, после ручной чистки 846 годных кадров.

Две части системы

Первой была облачная: PWA на телефоне снимает, presigned-загрузка через Yandex Cloud Function кладёт кадр в бакет, обработчик на Mac забирает, режет и правит цвет, Telegram-бот присылает брак обратно с просьбой переснять - пока фото ещё лежит на столе. Потом добавился локальный конвейер без облака: папка на входе, папка на выходе, HEIC, обрезка стола, каскад, свои веб-инструменты для коррекции и сравнения «до/после».

Баг, который стоил двух кадров

Первый тест съёмки с iPhone провалился молча: снимки оседали в IndexedDB и не уходили. Три причины сразу - очередь не запускалась при старте приложения, ошибки глотались пустым перехватом, а главное WebKit теряет содержимое Blob в IndexedDB между сессиями, и на сервер уходил PUT нулевой длины. Лечится хранением ArrayBuffer вместо Blob с контрольной суммой при постановке в очередь, запуском очереди на старте, отбивкой пустого тела и видимым индикатором «N в очереди». Два утренних кадра восстановить не удалось - их просто не существовало.

Отдельная поучительная деталь: CORS-preflight к облачной функции падал только из браузера. Из консоли всё работало, потому что curl не шлёт OPTIONS.

Финал

Все 846 кадров прошли реставрацию локальным стеком: автоориентация по лицам, восстановление лиц через CodeFormer, коррекция цвета. Два решения по итогам теста: апскейл фона убрал (он замыливал плёночное зерно и утраивал время), автоколоризацию чёрно-белых отменил - из десяти снимков приемлемо вышел один.