Гид по технологиям

Сканер документов на Python

5 min read Компьютерное зрение Обновлено 05 Dec 2025
Сканер документов на Python
Сканер документов на Python

Важно: код работает с OpenCV, imutils, scikit-image и NumPy. Проверяйте корректность путей к изображениям и разрешение исходных фотографий.

Ключевые варианты использования

  • Оцифровка контрактов и чеков для хранения в облаке
  • Быстрое получение «чистого» изображения для OCR
  • Подготовка документов для архивирования и отправки по почте

Подготовка окружения

Перед началом убедитесь, что у вас установлен Python (рекомендовано 3.7+). Откройте IDE и создайте два файла: main.py и transform.py.

Установите зависимости командой:

pip install OpenCV-Python imutils scikit-image NumPy

Установка Python-библиотек в терминале

Примечание: имена пакетов чувствительны к регистру в некоторых средах. Если OpenCV-Python не устанавливается, попробуйте opencv-python.

Структура решения — кратко

  1. Загрузить и изменить размер входного изображения.
  2. Перевести в оттенки серого и сгладить шум.
  3. Найти границы и контуры, выбрать контур документа (4 угла).
  4. Сделать перспективное преобразование (warp) — получить вид сверху.
  5. Применить адаптивный порог, сохранить результат.

Импорт библиотек (main.py)

Откройте main.py и импортируйте нужные модули:

import cv2
import imutils
from skimage.filters import threshold_local
from transform import perspective_transform

Если IDE подчёркивает perspective_transform, то это ожидаемо — функция появится в transform.py.

Загрузка и изменение размера входного изображения

Сделайте фото документа так, чтобы были видны все четыре угла. Поместите файл рядом с main.py.

Документ на столе, готовый к сканированию

Пример кода для загрузки и изменения размера:

# Passing the image path
original_img = cv2.imread('sample.jpg')
copy = original_img.copy()

# The resized height in hundreds
ratio = original_img.shape[0] / 500.0
img_resize = imutils.resize(original_img, height=500)

# Displaying output
cv2.imshow('Resized image', img_resize)

# Waiting for the user to press any key
cv2.waitKey(0)

Вы получите изображение с высотой 500 пикселей (сохранится соотношение сторон).

Результат программы в IDE: изображение после изменения размера

Перевод в оттенки серого

Большинство операций выделения границ проще на 1‑канальном изображении.

gray_image = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)
cv2.imshow('Grayed Image', gray_image)
cv2.waitKey(0)

Результат в IDE: изображение в оттенках серого

Применение детектора краёв

Сначала убираем шум фильтром Гаусса, затем используем Canny для поиска краёв.

blurred_image = cv2.GaussianBlur(gray_image, (5, 5), 0)
edged_img = cv2.Canny(blurred_image, 75, 200)
cv2.imshow('Image edges', edged_img)
cv2.waitKey(0)

Результат в IDE: обнаруженные границы на изображении

Совет: пороги Canny (75, 200) — отправная точка. При слабой контрастности увеличьте разницу между порогами.

Поиск наибольшего контура (документа)

Ищем все контуры, сортируем по площади и проверяем, какой из пяти крупнейших имеет 4 вершины — это, скорее всего, документ.

cnts, _ = cv2.findContours(edged_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5]

for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)

    if len(approx) == 4:
        doc = approx
        break

Если doc не найдено — см. раздел «Когда это может не работать».

Отметить четыре угла контура

Нарисуем точки в углах на уменьшенном изображении, чтобы визуально проверить обнаружение.

p = []

for d in doc:
    tuple_point = tuple(d[0])
    cv2.circle(img_resize, tuple_point, 3, (0, 0, 255), 4)
    p.append(tuple_point)

cv2.imshow('Circled corner points', img_resize)
cv2.waitKey(0)

Результат в IDE: углы документа отмечены окружностями

Если точки находятся не на углах документа — возможно, мешают другие объекты или фон. Попробуйте обрезать фон вручную или скорректировать порог Canny.

Перспективное преобразование (warp) — вид сверху

Перспективное преобразование выровняет документ, сделав вид со стороны камеры прямым.

warped_image = perspective_transform(copy, doc.reshape(4, 2) * ratio)
warped_image = cv2.cvtColor(warped_image, cv2.COLOR_BGR2GRAY)
cv2.imshow("Warped Image", imutils.resize(warped_image, height=650))
cv2.waitKey(0)

Чтобы функция работала, реализуйте transform.py (см. ниже).

Результат в IDE: исправленное (perspective warp) изображение документа

Модуль трансформации (transform.py)

Откройте transform.py и импортируйте NumPy и OpenCV:

import numpy as np
import cv2

В модуле две функции: одна упорядочивает четыре точки, вторая — выполняет perspective transform.

def order_points(pts):
   # initializing the list of coordinates to be ordered
   rect = np.zeros((4, 2), dtype = "float32")

   s = pts.sum(axis = 1)

   # top-left point will have the smallest sum
   rect[0] = pts[np.argmin(s)]

   # bottom-right point will have the largest sum
   rect[2] = pts[np.argmax(s)]

   '''computing the difference between the points, the
   top-right point will have the smallest difference,
   whereas the bottom-left will have the largest difference'''
   diff = np.diff(pts, axis = 1)
   rect[1] = pts[np.argmin(diff)]
   rect[3] = pts[np.argmax(diff)]

   # returns ordered coordinates
   return rect
def perspective_transform(image, pts):
   # unpack the ordered coordinates individually
   rect = order_points(pts)
   (tl, tr, br, bl) = rect

   '''compute the width of the new image, which will be the
   maximum distance between bottom-right and bottom-left
   x-coordinates or the top-right and top-left x-coordinates'''
   widthA = np.sqrt(((br[0] - bl[0])  2) + ((br[1] - bl[1])  2))
   widthB = np.sqrt(((tr[0] - tl[0])  2) + ((tr[1] - tl[1])  2))
   maxWidth = max(int(widthA), int(widthB))

   '''compute the height of the new image, which will be the
   maximum distance between the top-left and bottom-left y-coordinates'''
   heightA = np.sqrt(((tr[0] - br[0])  2) + ((tr[1] - br[1])  2))
   heightB = np.sqrt(((tl[0] - bl[0])  2) + ((tl[1] - bl[1])  2))
   maxHeight = max(int(heightA), int(heightB))

   '''construct the set of destination points to obtain an overhead shot'''
   dst = np.array([
      [0, 0],
      [maxWidth - 1, 0],
      [maxWidth - 1, maxHeight - 1],
      [0, maxHeight - 1]], dtype = "float32")

   # compute the perspective transform matrix
   transform_matrix = cv2.getPerspectiveTransform(rect, dst)

   # Apply the transform matrix
   warped = cv2.warpPerspective(image, transform_matrix, (maxWidth, maxHeight))

   # return the warped image
   return warped

Применение адаптивного порога и сохранение

Адаптивный порог придаёт изображению вид настоящего скана — белые поля и чёрный текст.

T = threshold_local(warped_image, 11, offset=10, method="gaussian")
warped = (warped_image > T).astype("uint8") * 255
cv2.imwrite('./'+'scan'+'.png',warped)

Формат PNG сохраняет резкость букв и контуров. Можно также сохранить в JPEG с высокой степенью качества.

Отображение финального результата

cv2.imshow("Final Scanned image", imutils.resize(warped, height=650))
cv2.waitKey(0)
cv2.destroyAllWindows()

Результат в IDE: отсканированный документ, вид сверху

Тонкости и рекомендации по качеству снимка

  • Снимайте при ровном освещении без бликов.
  • Разместите документ на контрастном фоне, чтобы грани выделялись.
  • Чем больше разрешение исходного фото — тем лучше итог после warp.

Когда это может не работать

  • Фон схожего цвета с краями документа — контуры сливаются.
  • Очень сильные тени или блики — детектор краёв даст шум.
  • Неравномерная текстура поверхности (скатерть или узор) может «вписать» лишние контуры.

В этих случаях попробуйте: увеличить контраст, поиграть с порогами Canny, использовать морфологические операции (erode/dilate), либо вручную задать четыре точки для perspective_transform.

Альтернативные подходы

  • Использовать готовые мобильные SDK (если нужна мобильная интеграция).
  • Применять алгоритмы глубокого обучения для обнаружения документов (например, модели на основе детекторов объектов), если фотографии очень шумные.
  • Для пакетной обработки: сначала выполнить детекцию границ любым быстрым методом, затем прогнать только найденные зоны через полноценный пайплайн.

Быстрая методология (mini-method)

  1. Предобработка: resize -> grayscale -> blur.
  2. Детекция: Canny -> findContours -> select 4-point contour.
  3. Коррекция: perspective_transform -> threshold_local.
  4. Сохранение и проверка качества.

Чеклист для разработчика перед релизом

  • Обработаны разные форматы входа (jpg, png).
  • [ ] Добавлена обработка ошибок при отсутствии doc (падение программы исключено).
  • Тесты на изображениях с тенью, бликом и сложным фоном.
  • Параметры Canny и адаптивного порога вынесены в конфиг.
  • Документация по использованию и лицензия зависимостей.

Критерии приёмки

  • Код корректно запускается внутри среды разработки и выполняет создание файла scan.png.
  • Для типичной фотографии документа (углы видны) итоговое изображение ровное и читаемое.
  • При автоматическом тесте на наборе контрольных изображений точность распознавания границ ≥ приемлемого визуального уровня (ручная проверка).

Глоссарий (1‑строчные определения)

  • Перспективное преобразование: операция, выравнивающая изображение по четырём опорным точкам, дающая вид сверху.
  • Canny: алгоритм детекции краёв на основе градиента.
  • Adaptive threshold (threshold_local): локальный порог, который зависит от окрестности пикселей.

Безопасность и приватность

Сканирование документов может содержать личные данные. При хранении или отправке за пределы локального устройства:

  • Шифруйте файлы в покое и при передаче.
  • Минимизируйте хранение лишних копий.
  • Учитывайте требования локального законодательства по защите данных.

Отладка и частые ошибки

  • Если cv2.imread возвращает None — проверьте путь к файлу.
  • Если findContours не находит подходящий контур — выводите промежуточные изображения (edged_img, blurred_image) и экспериментируйте с порогами.
  • Для мобильных фотографий с большим искажением используйте более агрессивный resize и повышенное сглаживание.

Когда использовать сложные модели

Если простая детекция контуров постоянно ошибается (например, документы на фоне множества предметов), имеет смысл перейти на модель детекции объектов (Faster R‑CNN, YOLO) для надёжного нахождения прямоугольника документа.

Краткое резюме

  • На Python с OpenCV и scikit-image можно быстро собрать рабочий сканер документов.
  • Основные шаги: resize → grayscale → blur → Canny → findContours → perspective_transform → adaptive threshold.
  • Тонкая настройка порогов и предобработка важны для качества результата.

Спасибо за чтение. Попробуйте на серии фотографий, добавьте логирование и конфигурируемые параметры — и вы получите надёжный инструмент для оцифровки бумажных архивов.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Анализ тональности на Python с VADER и Tkinter
Обработка текста

Анализ тональности на Python с VADER и Tkinter

Проверить прокси в Windows
Безопасность

Проверить прокси в Windows

Темы рабочего стола в Ubuntu 18.04 LTS
Linux

Темы рабочего стола в Ubuntu 18.04 LTS

Что делать, если Logitech G Pro Wireless не работает
Техподдержка

Что делать, если Logitech G Pro Wireless не работает

Dev Drive в Windows 11 — как начать
Разработка

Dev Drive в Windows 11 — как начать

Как сбросить и перезапустить OneDrive в Windows 10
Windows 10

Как сбросить и перезапустить OneDrive в Windows 10