티스토리 뷰

iOS/iOS

[iOS] OCR(3) - Google MLKit

HarryJeonn 2022. 5. 22. 19:46

Google MLKit ?

/*  Google MLKit은 머신러닝 전문 지식을 앱에 제공하는 모바일 SDK
		MLKit 중 Text Recognize v2(Beta)를 사용할 것이다. */

/*  카메라에서 셔터버튼을 누르면 누른 순간의 텍스트를 인식해오는 실습 */

How to use ?

// Podfile

pod 'GoogleMLKit/TextRecognitionKorean', '2.6.0'
// Camera

/*  카메라 사용을 위한 CameraView */

import SwiftUI
import AVFoundation

struct ScanIdCameraView: UIViewRepresentable {
    let viewModel: ScanIdViewModel
    
    class VideoPreviewView: UIView {
        override class var layerClass: AnyClass {
            AVCaptureVideoPreviewLayer.self
        }
        
        var videoPreviewLayer: AVCaptureVideoPreviewLayer {
            return layer as! AVCaptureVideoPreviewLayer
        }
    }
    
    func makeUIView(context: Context) -> VideoPreviewView {
        let view = VideoPreviewView()
        
        view.backgroundColor = .black
        view.videoPreviewLayer.videoGravity = .resizeAspectFill
        view.videoPreviewLayer.cornerRadius = 0
        view.videoPreviewLayer.session = viewModel.session
        view.videoPreviewLayer.connection?.videoOrientation = .portrait
        viewModel.requestAndCheckPermissions()

        return view
    }
    
    func updateUIView(_ uiView: VideoPreviewView, context: Context) {
        
    }
}
// ViewModel

import MLKitTextRecognitionKorean
import MLKitVision
import AVFoundation

class ScanIdViewModel: NSObject, ObservableObject {
    private let koreanOptions: KoreanTextRecognizerOptions
    private let koreanTextRecognizer: TextRecognizer
    
    override init() {
        self.koreanOptions = KoreanTextRecognizerOptions()
        self.koreanTextRecognizer = TextRecognizer.textRecognizer(options: self.koreanOptions)
    }
    
    @Published private var model: IdCardModel = IdCardModel()
    @Published var session: AVCaptureSession = AVCaptureSession()
    
    var isConfigure = false
    let output = AVCapturePhotoOutput()
    
    var name: String {
        model.name ?? ""
    }
    
    var address: String {
        model.address ?? ""
    }
    
    // 권한 체크
    func requestAndCheckPermissions() {
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .notDetermined:
            // 권한 요청
            AVCaptureDevice.requestAccess(for: .video) { authStatus in
                if authStatus {
                    DispatchQueue.main.async {
                        self.setupCamera()
                    }
                }
            }
        case .restricted:
            break
        case .authorized:
            // 이미 권한 받은 경우 셋업
            setupCamera()
        default:
            // 거절했을 경우
            print("Permession declined")
        }
    }
    
    func setupCamera() {
        if isConfigure == false {
            initInput()
            initOutput()
            isConfigure = true
        }
    }
    
    func initInput() {
        // Video 방식으로 촬영하겠다.
        guard let captureDevice = AVCaptureDevice.default(for: AVMediaType.video) else {
            return
        }
        
        do {
            let input = try AVCaptureDeviceInput(device: captureDevice)
            session.addInput(input)
        } catch {
            print("error")
        }
    }
    
    func initOutput() {
        session.addOutput(output)
    }
    
    func capturePhoto() {
        let photoSettings = AVCapturePhotoSettings()
        output.capturePhoto(with: photoSettings, delegate: self)
    }
    
    func imageOrientation(deviceOrientation: UIDeviceOrientation, cameraPosition: AVCaptureDevice.Position) -> UIImage.Orientation {
        switch deviceOrientation {
        case .portrait:
            return cameraPosition == .front ? .leftMirrored : .right
        case .landscapeLeft:
            return cameraPosition == .front ? .downMirrored : .up
        case .portraitUpsideDown:
            return cameraPosition == .front ? .rightMirrored : .left
        case .landscapeRight:
            return cameraPosition == .front ? .upMirrored : .down
        case .faceDown, .faceUp, .unknown:
            return .up
        }
    }
    
    func process(_ imageData: Data) {
        guard let image = UIImage(data: imageData) else { return }
        let visionImage = VisionImage(image: image)
        visionImage.orientation = imageOrientation(deviceOrientation: UIDevice.current.orientation, cameraPosition: .back)
        koreanTextRecognizer.process(visionImage) { result, error in
            guard error == nil, let result = result else {
                // Error handling
                print("error")
                return
            }
            self.model.name = result.text
            print("resultText: \\(result.text)")
        }
    }
}

extension ScanIdViewModel: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
        AudioServicesDisposeSystemSoundID(1108)
    }
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        guard let imageData = photo.fileDataRepresentation() else { return }
        process(imageData)
    }
}
// View

import SwiftUI

struct ScanIdView: View {
    @ObservedObject private var viewModel: ScanIdViewModel
    
    init() {
        viewModel = ScanIdViewModel()
    }
    
    var body: some View {
        VStack {
            Spacer()
            ScanIdCameraView(viewModel: viewModel)
                .ignoresSafeArea()
                .onAppear {
                    viewModel.session.startRunning()
                }
                .onDisappear {
                    viewModel.session.stopRunning()
                }

						// 카메라 셔터 버튼
            Circle()
                .frame(width: 100, height: 100)
                .foregroundColor(.orange)
                .onTapGesture {
                    viewModel.capturePhoto()
                }
            Text(viewModel.name)
            Spacer()
        }
    }
}

struct ScanIdView_Previews: PreviewProvider {
    static var previews: some View {
        ScanIdView()
    }
}

Review

/*  OCR 기술을 사용하고 싶은데 어떤 것을 써야할 지 모르겠어서 하나씩 다 맛만봤다.
		Tesseract나 SwiftOCR은 실제로 적용하는 것도 쉽지않았고, 성능도 전혀 만족스럽지 않았다.
		현재 사용한 MLKit은 베타버전이고 실제로 사용하려면 더 다듬어야겠지만 성능도 만족스럽고 사용성도 좋았다. */

Reference

https://developers.google.com/ml-kit/vision/text-recognition/v2/ios

'iOS > iOS' 카테고리의 다른 글

[iOS] 다크모드 미 지원  (0) 2022.06.15
[WWDC2022] Platforms State of the Union  (0) 2022.06.09
[iOS] OCR(2) - SwiftOCR  (0) 2022.05.22
[iOS] OCR(1) - Tesseract  (0) 2022.05.22
[iOS] MVC, MVVM 패턴  (1) 2022.04.21
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함