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

Создание простого сканера документов на Python

6 min read Компьютерное зрение Обновлено 04 Jan 2026
Сканер документов на Python
Сканер документов на Python

TL;DR

Код на Python позволяет превратить фотографию бумаги в «скан» с видом сверху. Используйте OpenCV для обработки изображений, imutils для изменения размера, scikit-image для адаптивного порога и NumPy для работы с массивами. В статье есть готовый модуль трансформации перспективы, примеры кода, контрольные сценарии и советы по отладке.

Важно: тестируйте на разных снимках (фон, освещение, наклон). Перед обработкой убедитесь, что весь документ виден и углы не обрезаны.

Человек использует сканер документов

Вы можете оцифровать документ, чтобы сэкономить место или сделать резервную копию. Задача — написать программу, которая превращает фотографию бумажного файла в стандартизованный «скан». Python хорош для этого: он поддерживает набор подходящих библиотек и простую работу с изображениями.

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

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

Требования: базовое знание Python и понимание работы с библиотекой NumPy. Откройте любой Python-IDE и создайте два файла: main.py и transform.py.

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

pip install OpenCV-Python imutils scikit-image NumPy

Коротко о ролях библиотек:

  • OpenCV — ввод/вывод изображений и большинство операций обработки;
  • imutils — удобное изменение размера и вспомогательные функции;
  • scikit-image — адаптивный порог (threshold);
  • NumPy — работа с массивами и геометрия.

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

Подождите, пока установка закончится и IDE обновит проект. Полный исходный код обычно выкладывают в репозитории на GitHub — это удобно для сравнения.

Импорт библиотек

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

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

Если IDE подсвечивает ошибку на perspective_transform — проигнорируйте пока. Мы реализуем этот импорт в transform.py далее.

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

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

Документ на столе, готовый к съёмке

Перед обработкой загрузите изображение и создайте копию оригинала — она понадобится для трансформации перспективы. Чтобы сохранить соотношение сторон, вычислите коэффициент масштабирования по высоте и измените размер копии до фиксированной высоты (в примере 500 px).

# 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)

Результат — уменьшенное изображение для дальнейших вычислений. Это ускоряет обработку и уменьшает вероятность ошибок при поиске контуров.

IDE: уменьшенное изображение документа

Перевод изображения в градации серого

Большинство алгоритмов проще работают с одноканальными изображениями. Конвертируйте RGB/RGB-BGR в grayscale:

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: обнаруженные края документа

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

Найдите контуры на изображении с краями, отсортируйте по площади и возьмите несколько крупнейших. Затем аппроксимируйте контуры полилиниями и ищите четырёхточечный контур — это, скорее всего, документ.

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

Контур с четырьмя вершинами обычно соответствует прямоугольному документу.

Обведение четырёх углов документа

Чтобы визуально убедиться, что документ найден правильно, обведите найденные точки кружками.

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)

Наносите точки на уменьшенную RGB-версию, чтобы легче было увидеть. Если точки совпадают с углами листа — всё готово к трансформации.

IDE: документ с отмеченными углами

Трансформация перспективы для получения вида сверху

Warp perspective — приём для исправления перспективных искажений: изображение проецируется на «плоскость», дающую вид сверху.

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 и импортируйте NumPy и OpenCV:

import numpy as np  
import cv2

В модуле будут две функции: order_points и 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

После добавления этих функций ошибка импорта в main.py исчезнет, и вы получите изображение с видом сверху.

IDE: искажённое изображение, исправленное перспективой

Применение адаптивного порога и сохранение результата

Примените адаптивный (локальный) порог, чтобы придать изображению вид скана (контрастный чёрно‑белый результат). Сохраните итог в PNG для сохранения качества.

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

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

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

Вы получите аккуратный отсканированный документ с видом сверху.

IDE: итоговый скан документа

Когда подход может дать сбой

  • Если лист частично обрезан кадром, контур не будет корректным.
  • Сильные тени и неравномерное освещение приводят к ошибкам Canny и неправильному порогу.
  • Нелинейные деформации бумаги (сильные заломы) усложняют аппроксимацию прямоугольника.

В таких случаях помогут дополнительные предобработки: выравнивание гистограммы, морфологические операции, более плотный поиск контуров.

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

  • Использовать мобильные SDK для сканирования (например, коммерческие библиотеки с автокадрированием). Они часто дают лучшее качество и устойчивы к краям.
  • Вместо адаптивного порога сохранять цвет и применять алгоритмы выравнивания освещённости.
  • Если нужен текстовый поиск — после обработки применить OCR (Tesseract) к усиленному изображению.

Краткая методология

  1. Захват изображения и уменьшение размера для быстроты.
  2. Перевод в градации серого и сглаживание.
  3. Детектирование краёв и поиск контуров.
  4. Аппроксимация четырёхугольника и упорядочивание точек.
  5. Перспективная трансформация для получения вида сверху.
  6. Адаптивный порог и сохранение результата.

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

  • Скрипт корректно загружает изображение и не падает при вызове функции transform.
  • Для тестовой фотографии с видимыми четырьмя углами результат — прямой вид сверху без сильных искажений.
  • Сохранённый файл scan.png читаем и контрастен; текст на нём различим для человека и OCR.

Сценарии тестирования и критерии приёмки

  • Вход: фотография с чёткими углами, ровный фон. Ожидается: корректный скан.
  • Вход: фото с тенью в одном углу. Ожидается: применён адаптивный порог, текст остаётся читаемым.
  • Вход: обрезанный угол. Ожидается: программа либо сообщает об ошибке, либо возвращает предупреждение о некорректном контуре.

Роль‑ориентированные чеклисты

  • Разработчик: покрыть код модульными тестами для order_points и perspective_transform.
  • Тестировщик: подготовить набор фотографий (разные фоны, освещение, углы) и запустить скрипт.
  • Пользователь: убедиться, что в кадре видны все 4 угла и документ лежит на однородном фоне.

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

  • «findContours вернул пустой список» — проверьте параметры Canny и качество размытия.
  • «approxPolyDP не нашёл 4‑х точек» — попробуйте снизить порог аппроксимации, увеличить число рассматриваемых контуров.
  • Неверная сортировка точек — визуализируйте rect, распечатайте координаты и проверьте порядок.

Примеры команд для отладки:

print('Contours found:', len(cnts))
print('Approx points:', [len(cv2.approxPolyDP(c, 0.02*cv2.arcLength(c, True), True)) for c in cnts])

Особенности безопасности и приватности

  • Документы могут содержать чувствительные данные. Храните отсканированные файлы в защищённых папках.
  • Для GDPR/локальной защиты: не загружайте сканы на внешние сервисы без явного согласия владельца данных.

Подсказки для улучшения качества сканов

  • Используйте ровный однотонный фон при съёмке.
  • Фокусируйте камеру и избегайте сильного бокового света.
  • При слабой освещённости включайте экспозицию или используйте обработку выравнивания яркости.

Галерея типичных крайних случаев

  • Наклон более 45°: аппроксимация может дать неверный прямоугольник.
  • Бледный текст на цветном фоне: адаптивный порог может «съесть» мелкие символы.
  • Скрепки/скручивание бумаги: контур не будет аккуратным, требуется предобработка.

Краткий словарь терминов

  • Контур — набор точек, образующих границу объекта на бинарном изображении.
  • Аппроксимация — приближение кривой полилинией с заданной точностью.
  • Перспективная трансформация — проекция изображения на плоскость для исправления искажений.

Итог

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

Краткие рекомендации для следующего шага: работайте с разными фотографиями, автоматизируйте тесты и рассмотрите добавление распознавания текста (OCR) для поиска по документам.


Сводка ключевых шагов:

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

Спасибо за чтение. Удачи в разработке вашего сканера на Python!

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

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство