이 글은 누구를 위한 것인가
- 카메라에서 실시간으로 텍스트, 바코드, 얼굴을 인식하려는 팀
- Core ML 모델을 앱에 통합하려는 iOS 개발자
- Vision API로 이미지 분석 기능을 구현하려는 팀
들어가며
Vision Framework는 이미지 분석을 위한 Apple의 고수준 API다. 얼굴 감지, 텍스트 인식(OCR), 바코드 스캔, Core ML 모델 추론을 일관된 인터페이스로 제공한다. AVFoundation과 결합해 카메라 스트림을 실시간으로 분석한다.
이 글은 bluefoxdev.kr의 iOS Vision Framework Core ML 가이드 를 참고하여 작성했습니다.
1. Vision Framework 주요 기능
[Vision Request 유형]
텍스트 인식:
VNRecognizeTextRequest: OCR (한국어 지원)
정확도: accurate/fast 선택
얼굴 감지:
VNDetectFaceRectanglesRequest: 얼굴 위치
VNDetectFaceLandmarksRequest: 눈/코/입 랜드마크
VNDetectFaceQualityRequest: 얼굴 품질 점수
바코드/QR:
VNDetectBarcodesRequest: QR, EAN, Code128 등
이미지 분류:
VNClassifyImageRequest: 1000개 카테고리
VNCoreMLRequest: 커스텀 Core ML 모델
텍스트 위치:
VNDetectTextRectanglesRequest: 텍스트 영역만 (빠름)
[처리 흐름]
이미지/카메라 프레임 → VNImageRequestHandler
→ [VNRequest 배열] → 결과 (VNObservation)
[성능 팁]
여러 요청을 하나의 Handler로 묶어 처리
GPU 활용: preferBackgroundProcessing = false
카메라: 불필요한 프레임 스킵
2. Vision Framework 구현
import Vision
import AVFoundation
import SwiftUI
// 텍스트 인식 (OCR)
class TextRecognizer {
func recognizeText(in cgImage: CGImage, completion: @escaping ([String]) -> Void) {
let request = VNRecognizeTextRequest { request, error in
guard error == nil else { return }
let texts = (request.results as? [VNRecognizedTextObservation])?.compactMap {
$0.topCandidates(1).first?.string
} ?? []
DispatchQueue.main.async { completion(texts) }
}
request.recognitionLevel = .accurate
request.recognitionLanguages = ["ko-KR", "en-US"]
request.usesLanguageCorrection = true
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
try? handler.perform([request])
}
}
// 바코드/QR 스캔 (실시간 카메라)
class BarcodeScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
private var captureSession: AVCaptureSession?
var onBarcodeDetected: ((String) -> Void)?
func startScanning() {
let session = AVCaptureSession()
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
let input = try? AVCaptureDeviceInput(device: device) else { return }
session.addInput(input)
let output = AVCaptureVideoDataOutput()
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "barcodeQueue"))
session.addOutput(output)
captureSession = session
session.startRunning()
}
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let request = VNDetectBarcodesRequest { [weak self] request, _ in
guard let observation = (request.results as? [VNBarcodeObservation])?.first,
let payload = observation.payloadStringValue else { return }
DispatchQueue.main.async { self?.onBarcodeDetected?(payload) }
}
request.symbologies = [.qr, .ean13, .ean8, .code128, .dataMatrix]
try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request])
}
}
// Core ML 이미지 분류
class ImageClassifier {
private var model: VNCoreMLModel?
init() {
// Xcode에서 .mlmodel 추가 후 자동 생성된 클래스 사용
// let mlModel = try? MobileNetV2(configuration: MLModelConfiguration()).model
// model = try? VNCoreMLModel(for: mlModel!)
}
func classify(image: CIImage, completion: @escaping (String, Double) -> Void) {
guard let model = model else { return }
let request = VNCoreMLRequest(model: model) { request, _ in
guard let results = request.results as? [VNClassificationObservation],
let top = results.first else { return }
DispatchQueue.main.async { completion(top.identifier, Double(top.confidence)) }
}
request.imageCropAndScaleOption = .centerCrop
try? VNImageRequestHandler(ciImage: image, options: [:]).perform([request])
}
}
// 얼굴 감지 + 랜드마크 (SwiftUI)
struct FaceDetectionView: View {
@State private var detectedFaces: [VNFaceObservation] = []
let image: UIImage
var body: some View {
ZStack {
Image(uiImage: image).resizable().aspectRatio(contentMode: .fit)
GeometryReader { geo in
ForEach(detectedFaces.indices, id: \.self) { i in
let face = detectedFaces[i]
let rect = VNImageRectForNormalizedRect(
face.boundingBox,
Int(geo.size.width), Int(geo.size.height)
)
Rectangle()
.stroke(Color.green, lineWidth: 2)
.frame(width: rect.width, height: rect.height)
.offset(x: rect.minX, y: geo.size.height - rect.maxY)
}
}
}
.onAppear { detectFaces() }
}
private func detectFaces() {
guard let cgImage = image.cgImage else { return }
let request = VNDetectFaceLandmarksRequest { req, _ in
DispatchQueue.main.async {
detectedFaces = (req.results as? [VNFaceObservation]) ?? []
}
}
try? VNImageRequestHandler(cgImage: cgImage, options: [:]).perform([request])
}
}
마무리
Vision Framework의 핵심은 VNImageRequestHandler에 여러 요청을 한 번에 묶어 처리하는 것이다. OCR은 recognitionLanguages에 "ko-KR"을 추가해야 한국어를 인식하고, 실시간 카메라 처리는 AVCaptureVideoDataOutput의 샘플 버퍼를 Vision 요청에 직접 전달한다. Core ML 모델은 .mlmodel 파일을 Xcode에 추가하면 자동으로 Swift 클래스가 생성된다.