Создание простого сканера документов на 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 — работа с массивами и геометрия.
Подождите, пока установка закончится и 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)Результат — уменьшенное изображение для дальнейших вычислений. Это ускоряет обработку и уменьшает вероятность ошибок при поиске контуров.
Перевод изображения в градации серого
Большинство алгоритмов проще работают с одноканальными изображениями. Конвертируйте RGB/RGB-BGR в grayscale:
gray_image = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY)
cv2.imshow('Grayed Image', gray_image)
cv2.waitKey(0)Обратите внимание на разницу: цветная таблица становится чёрно‑белой, что упрощает дальнейшую сегментацию.
Детектирование краёв
Уберите шум с помощью гауссова размытия, затем примените детектор границ 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)Края будут видны на выходе — вам важно выделить контур листа документа.
Поиск наибольшего контура
Найдите контуры на изображении с краями, отсортируйте по площади и возьмите несколько крупнейших. Затем аппроксимируйте контуры полилиниями и ищите четырёхточечный контур — это, скорее всего, документ.
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-версию, чтобы легче было увидеть. Если точки совпадают с углами листа — всё готово к трансформации.
Трансформация перспективы для получения вида сверху
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 rectdef 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 исчезнет, и вы получите изображение с видом сверху.
Применение адаптивного порога и сохранение результата
Примените адаптивный (локальный) порог, чтобы придать изображению вид скана (контрастный чёрно‑белый результат). Сохраните итог в 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()Вы получите аккуратный отсканированный документ с видом сверху.
Когда подход может дать сбой
- Если лист частично обрезан кадром, контур не будет корректным.
- Сильные тени и неравномерное освещение приводят к ошибкам Canny и неправильному порогу.
- Нелинейные деформации бумаги (сильные заломы) усложняют аппроксимацию прямоугольника.
В таких случаях помогут дополнительные предобработки: выравнивание гистограммы, морфологические операции, более плотный поиск контуров.
Альтернативные подходы
- Использовать мобильные SDK для сканирования (например, коммерческие библиотеки с автокадрированием). Они часто дают лучшее качество и устойчивы к краям.
- Вместо адаптивного порога сохранять цвет и применять алгоритмы выравнивания освещённости.
- Если нужен текстовый поиск — после обработки применить OCR (Tesseract) к усиленному изображению.
Краткая методология
- Захват изображения и уменьшение размера для быстроты.
- Перевод в градации серого и сглаживание.
- Детектирование краёв и поиск контуров.
- Аппроксимация четырёхугольника и упорядочивание точек.
- Перспективная трансформация для получения вида сверху.
- Адаптивный порог и сохранение результата.
Критерии приёмки
- Скрипт корректно загружает изображение и не падает при вызове функции 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!
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone