Пользователь подносит телефон к документу. На экране появляется зелёная рамка, но скан выходит размытым. Причина — перспективные искажения и тени от пальцев. Чтобы получить читаемый PDF, нужно решить цепочку задач: детекция границ, перспективная коррекция, постобработка, многостраничное объединение. За 5+ лет мы реализовали сканеры в 50+ проектах для iOS и Android, сертифицированы App Store и Google Play. На практике всплывают десятки нюансов: бликующие поверхности, дрожание руки, разные типы бумаги. Свяжитесь с нами для бесплатной оценки вашего проекта — мы подберём оптимальное решение.
Почему автоматическое обнаружение границ — первый этап?
Первый шаг — найти четыре угла документа в кадре. На 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 с автоматическим захватом, перспективной коррекцией и мультистраничным сканированием. Для большинства задач это оптимальный выбор. VNDocumentCameraViewController На Android — ML Kit Document Scanner API (beta, доступен через Google Play Services) или OpenCV через NDK для кастомных решений.
Как настроить перспективную коррекцию?
После обнаружения четырёх углов применяем гомографическую трансформацию — выравниваем наклонённый документ в прямоугольник «как будто снимали сверху». На 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% первых реализаций. Наш опыт гарантирует, что мы учтём такие нюансы.
Типичные ошибки при настройке перспективной коррекции:
- Неправильный учёт системы координат CIImage (topLeft Vision == bottomLeft CIImage).
- Слишком низкий minimumConfidence (меньше 0.8) приводит к ложным срабатываниям.
- Игнорирование quadratureTolerance для нестандартных углов документа.
Что делает постобработка изображения?
Скан документа после геометрической коррекции обычно требует улучшения:
Grayscale + усиление контраста — для распознавания текста, документов для архива:
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 доступен VNGeneratePersonInstanceMaskRequest, который помогает убрать тень от руки. Для более ранних версий — GPUImage3 или собственный Metal-шейдер для highlight recovery.
Когда автоматический захват лучше ручного?
Автоматический триггер съёмки при обнаружении документа — хороший UX, но требует стабилизации: документ должен быть в кадре более 1.5 секунды с достаточным показателем уверенности перед автозахватом. Слишком агрессивный триггер раздражает — пользователь ещё выравнивает телефон, а приложение уже сфотографировало. В таблице ниже — сравнение подходов.
| Критерий | Автоматический захват | Ручной захват |
|---|---|---|
| Удобство для пользователя | Высокое (не нужно нажимать кнопку) | Среднее (требует нажатия) |
| Стабильность результатов | Зависит от стабилизации времени | Высокая (пользователь выбирает момент) |
| Время на сканирование | Быстрее при хороших условиях | Медленнее на 1-2 секунды |
| Риск некачественного скана | Выше при плохом освещении | Минимальный (пользователь контролирует) |
При сканировании часто возникают блики от поверхности — их можно компенсировать многоснимковой обработкой. Размытие из-за дрожания решается автоматическим захватом с задержкой 1.5 секунды. Тени от рук удаляются с помощью VNGeneratePersonInstanceMaskRequest. Эти и другие нюансы учитываются при настройке сканера.
Ниже — сравнение популярных библиотек для интеграции сканера:
| Библиотека | Платформа | Готовый UI | Кастомная обработка | Сложность интеграции |
|---|---|---|---|---|
| VNDocumentCameraViewController | iOS | Да | Ограниченная | Низкая |
| ML Kit Document Scanner | Android | Да | Ограниченная | Средняя |
| OpenCV + NDK | iOS/Android | Нет | Полная | Высокая |
Как объединить страницы в 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-задач — сохраняем оригинальное разрешение. При необходимости мы добавляем распознавание текста (OCR) — на iOS через VNRecognizeTextRequest, на Android через ML Kit Text Recognition.
Что входит в нашу работу
- Анализ сценариев использования: типы документов, нужен ли экспорт в PDF и интеграция с OCR.
- Проектирование архитектуры: выбор библиотек (VisionKit, ML Kit, OpenCV) и фреймворков.
- Реализация детектора границ и live-preview с подсветкой найденного документа.
- Перспективная коррекция, постобработка изображения.
- Мультистраничный режим, экспорт в PDF или JPEG.
- Интеграция с камерой, обработка ошибок, тестирование в реальных условиях.
- Документация по API и развёртывание.
Процесс работы
- Определение сценария: тип документов (паспорт, чек, договор, многостраничные материалы), нужен ли экспорт в PDF, интеграция с OCR.
- Реализация детектора границ и live-preview с подсветкой найденного документа.
- Перспективная коррекция, постобработка изображения.
- Мультистраничный режим, экспорт в PDF или JPEG.
- Тестирование в реальных условиях: разное освещение, различные типы бумаги (глянцевая, матовая, старые документы).
Ориентиры по срокам
Базовый сканер с VNDocumentCameraViewController (iOS) — от 1 до 3 дней. Полная кастомная реализация с поддержкой двух платформ и мультистраничным PDF — от 2 недель. Стоимость базовой реализации зависит от сложности и рассчитывается индивидуально. Получите консультацию инженера по вашему сценарию использования — свяжитесь с нами для обсуждения деталей.







