Користувач підносить телефон до документа. На екрані з'являється зелена рамка, але скан виходить розмитим. Причина — перспективні спотворення та тіні від пальців. Щоб отримати читабельний 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 тижнів. Вартість базової реалізації залежить від складності та розраховується індивідуально. Отримайте консультацію інженера по вашому сценарію використання — зв'яжіться з нами для обговорення деталей.







