Сканер документов на Python
Важно: код работает с OpenCV, imutils, scikit-image и NumPy. Проверяйте корректность путей к изображениям и разрешение исходных фотографий.
Ключевые варианты использования
- Оцифровка контрактов и чеков для хранения в облаке
- Быстрое получение «чистого» изображения для OCR
- Подготовка документов для архивирования и отправки по почте
Подготовка окружения
Перед началом убедитесь, что у вас установлен Python (рекомендовано 3.7+). Откройте IDE и создайте два файла: main.py и transform.py.
Установите зависимости командой:
pip install OpenCV-Python imutils scikit-image NumPyПримечание: имена пакетов чувствительны к регистру в некоторых средах. Если OpenCV-Python не устанавливается, попробуйте opencv-python.
Структура решения — кратко
- Загрузить и изменить размер входного изображения.
- Перевести в оттенки серого и сгладить шум.
- Найти границы и контуры, выбрать контур документа (4 угла).
- Сделать перспективное преобразование (warp) — получить вид сверху.
- Применить адаптивный порог, сохранить результат.
Импорт библиотек (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 пикселей (сохранится соотношение сторон).
Перевод в оттенки серого
Большинство операций выделения границ проще на 1‑канальном изображении.
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)Совет: пороги 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)Если точки находятся не на углах документа — возможно, мешают другие объекты или фон. Попробуйте обрезать фон вручную или скорректировать порог 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 (см. ниже).
Модуль трансформации (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 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Применение адаптивного порога и сохранение
Адаптивный порог придаёт изображению вид настоящего скана — белые поля и чёрный текст.
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()Тонкости и рекомендации по качеству снимка
- Снимайте при ровном освещении без бликов.
- Разместите документ на контрастном фоне, чтобы грани выделялись.
- Чем больше разрешение исходного фото — тем лучше итог после warp.
Когда это может не работать
- Фон схожего цвета с краями документа — контуры сливаются.
- Очень сильные тени или блики — детектор краёв даст шум.
- Неравномерная текстура поверхности (скатерть или узор) может «вписать» лишние контуры.
В этих случаях попробуйте: увеличить контраст, поиграть с порогами Canny, использовать морфологические операции (erode/dilate), либо вручную задать четыре точки для perspective_transform.
Альтернативные подходы
- Использовать готовые мобильные SDK (если нужна мобильная интеграция).
- Применять алгоритмы глубокого обучения для обнаружения документов (например, модели на основе детекторов объектов), если фотографии очень шумные.
- Для пакетной обработки: сначала выполнить детекцию границ любым быстрым методом, затем прогнать только найденные зоны через полноценный пайплайн.
Быстрая методология (mini-method)
- Предобработка: resize -> grayscale -> blur.
- Детекция: Canny -> findContours -> select 4-point contour.
- Коррекция: perspective_transform -> threshold_local.
- Сохранение и проверка качества.
Чеклист для разработчика перед релизом
- Обработаны разные форматы входа (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.
- Тонкая настройка порогов и предобработка важны для качества результата.
Спасибо за чтение. Попробуйте на серии фотографий, добавьте логирование и конфигурируемые параметры — и вы получите надёжный инструмент для оцифровки бумажных архивов.
Похожие материалы
Анализ тональности на Python с VADER и Tkinter
Проверить прокси в Windows
Темы рабочего стола в Ubuntu 18.04 LTS
Что делать, если Logitech G Pro Wireless не работает
Dev Drive в Windows 11 — как начать