Реализация распознавания документов через камеру мобильного приложения
Отсканировать документ камерой смартфона — задача, которая выглядит простой, но содержит десяток технических нюансов. Перспективное искажение, тени от пальцев, бликующие поверхности, дрожание руки при съёмке — всё это нужно обрабатывать до того, как результат попадёт к пользователю.
Обнаружение границ документа
Первый шаг — найти четыре угла документа в кадре. На iOS с iOS 13+ это делает VisionKit через VNDetectRectanglesRequest:
let request = VNDetectRectanglesRequest { request, error in
guard let results = request.results as? [VNRectangleObservation],
let rect = results.first else { return }
// rect.topLeft, topRight, bottomLeft, bottomRight в нормализованных координатах [0,1]
DispatchQueue.main.async {
self.overlayView.drawQuadrilateral(observation: rect,
imageSize: self.previewLayer.frame.size)
}
}
request.minimumConfidence = 0.8
request.minimumAspectRatio = 0.5 // отфильтровываем узкие прямоугольники
request.quadratureTolerance = 30 // допуск отклонения от прямоугольника в градусах
С iOS 16 появился VNDocumentCameraViewController — готовый интерфейс от Apple с автоматическим захватом, перспективной коррекцией и мультистраничным сканированием. Для большинства задач это оптимальный выбор.
На Android — ML Kit Document Scanner API (beta, доступен через Google Play Services) или OpenCV через NDK для кастомных решений.
Перспективная коррекция (perspective correction)
После обнаружения четырёх углов применяем гомографическую трансформацию — выравниваем наклонённый документ в прямоугольник «как будто снимали сверху». На iOS это CIPerspectiveCorrection из Core Image:
func correctPerspective(image: CIImage, observation: VNRectangleObservation) -> CIImage {
let imageSize = image.extent.size
// Конвертируем нормализованные координаты Vision в пиксельные CIImage
func toPixel(_ point: CGPoint) -> CIVector {
return CIVector(x: point.x * imageSize.width,
y: point.y * imageSize.height)
}
let filter = CIFilter.perspectiveCorrection()
filter.inputImage = image
filter.topLeft = toPixel(observation.topLeft)
filter.topRight = toPixel(observation.topRight)
filter.bottomLeft = toPixel(observation.bottomLeft)
filter.bottomRight = toPixel(observation.bottomRight)
return filter.outputImage ?? image
}
Важно: система координат CIImage перевёрнута по Y относительно UIKit — topLeft в Vision это bottomLeft в CIImage. Эта ошибка встречается в 90% первых реализаций.
Постобработка изображения
Скан документа после геометрической коррекции обычно требует улучшения:
Граyscale + усиление контраста — для распознавания текста, документов для архива:
let grayscaleFilter = CIFilter.colorControls()
grayscaleFilter.saturation = 0
grayscaleFilter.contrast = 1.3
Adaptive thresholding — «чёрно-белый» эффект как в Adobe Scan. Core Image не имеет встроенного adaptive threshold, поэтому используем CIKernel или Metal Compute Shader для обработки по блокам 15×15 пикселей.
Document enhancement — на iOS 17+ доступен VNGeneratePersonInstanceMaskRequest, который помогает убрать тень от руки. Для более ранних версий — GPUImage3 или собственный Metal-шейдер для highlight recovery.
Многостраничное сканирование и PDF
Пользователь сканирует несколько страниц — они объединяются в один документ. На iOS — PDFDocument + PDFPage из PDFKit:
func createPDF(from images: [UIImage]) -> Data? {
let pdfDocument = PDFDocument()
for (index, image) in images.enumerated() {
guard let page = PDFPage(image: image) else { continue }
pdfDocument.insert(page, at: index)
}
return pdfDocument.dataRepresentation()
}
Размер PDF важен: A4 при 300 DPI = ~2500×3500 px. Для хранения и передачи сжимаем JPEG внутри PDF с quality 0.7–0.85. Для OCR-задач — сохраняем оригинальное разрешение.
Автоматический захват vs ручной
Автоматический триггер съёмки при обнаружении документа — хороший UX, но требует стабилизации: документ должен быть в кадре > 1.5 секунды с достаточной confidence перед автозахватом. Слишком агрессивный триггер раздражает — пользователь ещё выравнивает телефон, а приложение уже сфотографировало.
Процесс работы
Определение сценария: тип документов (паспорт, чек, договор, многостраничные материалы), нужен ли экспорт в PDF, интеграция с OCR.
Реализация детектора границ и live-preview с подсветкой найденного документа.
Перспективная коррекция, постобработка изображения.
Мультистраничный режим, экспорт в PDF или JPEG.
Тестирование в реальных условиях: разное освещение, различные типы бумаги (глянцевая, матовая, старые документы).
Ориентиры по срокам
Базовый сканер с VNDocumentCameraViewController (iOS only) — 1–2 дня. Кастомная реализация с ручной перспективной коррекцией, постобработкой и мультистраничным PDF — 3–5 дней на каждую платформу.







