iOS에서 ML Kit를 사용하여 이미지 속 텍스트 인식

ML Kit를 사용하여 이미지 속 텍스트를 인식할 수 있습니다. ML Kit에는 도로 표지판 텍스트와 같은 이미지 속 텍스트 인식에 적합한 범용 API와 문서의 텍스트 인식에 최적화된 API가 모두 있습니다. 범용 API에는 기기별 및 클라우드 기반 모델이 모두 있습니다. 문서 텍스트 인식은 클라우드 기반 모델에서만 사용할 수 있습니다. 클라우드 및 기기별 모델 비교에 대한 개요를 참조하세요.

시작하기 전에

  1. 앱에 Firebase를 아직 추가하지 않았다면 시작 가이드의 단계에 따라 추가합니다.
  2. Podfile에 ML Kit 라이브러리를 포함합니다.
    pod 'Firebase/MLVision', '6.25.0'
    # If using an on-device API:
    pod 'Firebase/MLVisionTextModel', '6.25.0'
    
    프로젝트의 포드를 설치하거나 업데이트한 후 .xcworkspace를 사용하여 Xcode 프로젝트를 열어야 합니다.
  3. 앱에서 Firebase를 가져옵니다.

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. 클라우드 기반 모델을 사용하려는 경우 프로젝트에 클라우드 기반 API를 아직 사용 설정하지 않았으면 지금 설정하세요.

    1. Firebase Console의 ML Kit API 페이지를 엽니다.
    2. 프로젝트를 Blaze 요금제로 아직 업그레이드하지 않은 경우 업그레이드를 클릭하여 업그레이드하세요. 프로젝트가 Blaze 요금제가 아닌 경우에만 업그레이드하라는 메시지가 표시됩니다.

      Blaze 수준 프로젝트만 클라우드 기반 API를 사용할 수 있습니다.

    3. 클라우드 기반 API가 아직 사용 설정되지 않은 경우 클라우드 기반 API 사용 설정을 클릭합니다.

    기기별 모델만 사용하려는 경우 이 단계를 건너뛸 수 있습니다.

이제 이미지 속 텍스트 인식을 시작할 수 있습니다.

입력 이미지 가이드라인

  • ML Kit가 텍스트를 정확하게 인식하려면 입력 이미지에 충분한 픽셀 데이터로 표시된 텍스트가 있어야 합니다. 라틴어 텍스트의 경우 각 문자가 16x16픽셀 이상이어야 좋습니다. Cloud 기반 API에서만 지원되는 한국어, 일본어, 중국어 텍스트의 경우 각 문자가 24x24픽셀이어야 합니다. 일반적으로 모든 언어의 문자가 24x24픽셀보다 크면 정확도가 더 이상 향상되지 않습니다.

    예를 들어 이미지의 전체 너비를 차지하는 명함을 스캔하려면 640x480픽셀 이미지가 적합합니다. 레터 사이즈 용지에 인쇄된 문서 스캔은 720x1280픽셀 이미지가 적합합니다.

  • 이미지 초점이 잘 맞지 않으면 텍스트 인식 정확도가 저하될 수 있습니다. 허용 가능한 수준의 결과를 얻지 못하는 경우 사용자에게 이미지를 다시 캡처하도록 요청합니다.

  • 실시간 애플리케이션에서 텍스트를 인식하는 경우 입력 이미지의 전체 크기를 고려해야 할 수도 있습니다. 이미지 크기가 작을수록 더 빠르게 처리될 수 있으므로 지연 시간을 줄이려면 위의 정확도 요구사항에 유의하여 낮은 해상도에서 이미지를 캡처하고 텍스트가 가능한 많은 이미지를 차지하도록 합니다. 또한 실시간 성능 향상을 위한 팁도 참조하세요.


이미지 속 텍스트 인식

기기별 모델 또는 클라우드 기반 모델을 사용하여 이미지 속 텍스트를 인식하려면 아래에 설명된 대로 텍스트 인식기를 실행합니다.

1. 텍스트 인식기 실행

이미지를 `UIImage` 또는 `CMSampleBufferRef`로 `VisionTextRecognizer`의 `process(_:completion:)` 메서드에 전달합니다.
  1. onDeviceTextRecognizer 또는 cloudTextRecognizer를 호출하여 VisionTextRecognizer의 인스턴스를 가져옵니다.

    Swift

    기기별 모델을 사용하려면 다음을 수행합니다.

    let vision = Vision.vision()
    let textRecognizer = vision.onDeviceTextRecognizer()
    

    클라우드 모델을 사용하려면 다음 안내를 따르세요.

    let vision = Vision.vision()
    let textRecognizer = vision.cloudTextRecognizer()
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    let options = VisionCloudTextRecognizerOptions()
    options.languageHints = ["en", "hi"]
    let textRecognizer = vision.cloudTextRecognizer(options: options)
    

    Objective-C

    기기별 모델을 사용하려면 다음을 수행합니다.

    FIRVision *vision = [FIRVision vision];
    FIRVisionTextRecognizer *textRecognizer = [vision onDeviceTextRecognizer];
    

    클라우드 모델을 사용하려면 다음 안내를 따르세요.

    FIRVision *vision = [FIRVision vision];
    FIRVisionTextRecognizer *textRecognizer = [vision cloudTextRecognizer];
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FIRVisionCloudTextRecognizerOptions *options =
            [[FIRVisionCloudTextRecognizerOptions alloc] init];
    options.languageHints = @[@"en", @"hi"];
    FIRVisionTextRecognizer *textRecognizer = [vision cloudTextRecognizerWithOptions:options];
    
  2. UIImage 또는 CMSampleBufferRef를 사용하여 VisionImage 객체를 만듭니다.

    UIImage를 사용하는 방법은 다음과 같습니다.

    1. 필요한 경우 imageOrientation 속성이 .up이 되도록 이미지를 회전합니다.
    2. 올바르게 회전된 UIImage를 사용하여 VisionImage 객체를 만듭니다. 회전 메타데이터를 지정하지 마세요. 기본값인 .topLeft를 사용해야 합니다.

      Swift

      let image = VisionImage(image: uiImage)

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];

    CMSampleBufferRef를 사용하는 방법은 다음과 같습니다.

    1. CMSampleBufferRef 버퍼에 포함된 이미지 데이터의 방향을 지정하는 VisionImageMetadata 객체를 만듭니다.

      이미지 방향을 가져오는 방법은 다음과 같습니다.

      Swift

      func imageOrientation(
          deviceOrientation: UIDeviceOrientation,
          cameraPosition: AVCaptureDevice.Position
          ) -> VisionDetectorImageOrientation {
          switch deviceOrientation {
          case .portrait:
              return cameraPosition == .front ? .leftTop : .rightTop
          case .landscapeLeft:
              return cameraPosition == .front ? .bottomLeft : .topLeft
          case .portraitUpsideDown:
              return cameraPosition == .front ? .rightBottom : .leftBottom
          case .landscapeRight:
              return cameraPosition == .front ? .topRight : .bottomRight
          case .faceDown, .faceUp, .unknown:
              return .leftTop
          }
      }

      Objective-C

      - (FIRVisionDetectorImageOrientation)
          imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                                 cameraPosition:(AVCaptureDevicePosition)cameraPosition {
        switch (deviceOrientation) {
          case UIDeviceOrientationPortrait:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationLeftTop;
            } else {
              return FIRVisionDetectorImageOrientationRightTop;
            }
          case UIDeviceOrientationLandscapeLeft:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationBottomLeft;
            } else {
              return FIRVisionDetectorImageOrientationTopLeft;
            }
          case UIDeviceOrientationPortraitUpsideDown:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationRightBottom;
            } else {
              return FIRVisionDetectorImageOrientationLeftBottom;
            }
          case UIDeviceOrientationLandscapeRight:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationTopRight;
            } else {
              return FIRVisionDetectorImageOrientationBottomRight;
            }
          default:
            return FIRVisionDetectorImageOrientationTopLeft;
        }
      }

      그런 다음 메타데이터 객체를 만듭니다.

      Swift

      let cameraPosition = AVCaptureDevice.Position.back  // Set to the capture device you used.
      let metadata = VisionImageMetadata()
      metadata.orientation = imageOrientation(
          deviceOrientation: UIDevice.current.orientation,
          cameraPosition: cameraPosition
      )

      Objective-C

      FIRVisionImageMetadata *metadata = [[FIRVisionImageMetadata alloc] init];
      AVCaptureDevicePosition cameraPosition =
          AVCaptureDevicePositionBack;  // Set to the capture device you used.
      metadata.orientation =
          [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                       cameraPosition:cameraPosition];
    2. CMSampleBufferRef 객체 및 회전 메타데이터를 사용하여 VisionImage 객체를 만듭니다.

      Swift

      let image = VisionImage(buffer: sampleBuffer)
      image.metadata = metadata

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
      image.metadata = metadata;
  3. 이제 이미지를 process(_:completion:) 메서드에 전달합니다.

    Swift

    textRecognizer.process(visionImage) { result, error in
      guard error == nil, let result = result else {
        // ...
        return
      }
    
      // Recognized text
    }
    

    Objective-C

    [textRecognizer processImage:image
                      completion:^(FIRVisionText *_Nullable result,
                                   NSError *_Nullable error) {
      if (error != nil || result == nil) {
        // ...
        return;
      }
    
      // Recognized text
    }];
    

2. 인식된 텍스트 블록에서 텍스트 추출

텍스트 인식 작업이 성공하면 [`VisionText`][VisionText] 객체를 반환합니다. `VisionText` 객체는 이미지에서 인식되는 전체 텍스트 및 0개 이상의 [`VisionTextBlock`][VisionTextBlock] 객체를 포함합니다. 각 `VisionTextBlock`은 0개 이상의 [`VisionTextLine`][VisionTextLine] 객체를 포함하는 직사각형 모양의 텍스트 블록을 나타냅니다. 각 `VisionTextLine` 객체는 단어 및 단어와 유사한 항목(날짜, 숫자 등)을 나타내는 0개 이상의 [`VisionTextElement`][VisionTextElement] 객체를 포함합니다. 각 `VisionTextBlock`, `VisionTextLine`, `VisionTextElement` 객체에 대해 해당 영역에서 인식된 텍스트와 영역의 경계 좌표를 가져올 수 있습니다. 예를 들면 다음과 같습니다.

Swift

let resultText = result.text
for block in result.blocks {
    let blockText = block.text
    let blockConfidence = block.confidence
    let blockLanguages = block.recognizedLanguages
    let blockCornerPoints = block.cornerPoints
    let blockFrame = block.frame
    for line in block.lines {
        let lineText = line.text
        let lineConfidence = line.confidence
        let lineLanguages = line.recognizedLanguages
        let lineCornerPoints = line.cornerPoints
        let lineFrame = line.frame
        for element in line.elements {
            let elementText = element.text
            let elementConfidence = element.confidence
            let elementLanguages = element.recognizedLanguages
            let elementCornerPoints = element.cornerPoints
            let elementFrame = element.frame
        }
    }
}

Objective-C

NSString *resultText = result.text;
for (FIRVisionTextBlock *block in result.blocks) {
  NSString *blockText = block.text;
  NSNumber *blockConfidence = block.confidence;
  NSArray<FIRVisionTextRecognizedLanguage *> *blockLanguages = block.recognizedLanguages;
  NSArray<NSValue *> *blockCornerPoints = block.cornerPoints;
  CGRect blockFrame = block.frame;
  for (FIRVisionTextLine *line in block.lines) {
    NSString *lineText = line.text;
    NSNumber *lineConfidence = line.confidence;
    NSArray<FIRVisionTextRecognizedLanguage *> *lineLanguages = line.recognizedLanguages;
    NSArray<NSValue *> *lineCornerPoints = line.cornerPoints;
    CGRect lineFrame = line.frame;
    for (FIRVisionTextElement *element in line.elements) {
      NSString *elementText = element.text;
      NSNumber *elementConfidence = element.confidence;
      NSArray<FIRVisionTextRecognizedLanguage *> *elementLanguages = element.recognizedLanguages;
      NSArray<NSValue *> *elementCornerPoints = element.cornerPoints;
      CGRect elementFrame = element.frame;
    }
  }
}

실시간 성능 향상을 위한 팁

기기별 모델을 사용하여 실시간 애플리케이션에서 텍스트를 인식하고자 하는 경우 다음 안내에 따르면 최상의 프레임 속도를 얻을 수 있습니다.

  • 텍스트 인식기 호출을 제한합니다. 텍스트 인식기가 실행 중일 때 새 동영상 프레임을 사용할 수 있게 되면 프레임을 낮춥니다.
  • 텍스트 인식기 출력을 사용해서 입력 이미지에서 그래픽을 오버레이하는 경우 먼저 ML Kit에서 텍스트 인식 결과를 가져온 후 이미지를 렌더링하고 단일 단계로 오버레이합니다. 이렇게 하면 입력 프레임별로 한 번만 디스플레이 표면에 렌더링됩니다. 관련 예시는 쇼케이스 샘플 앱에서 previewOverlayViewFIRDetectionOverlayView 클래스를 참조하세요.
  • 낮은 해상도에서 이미지 캡처를 고려합니다. 단, API의 이미지 크기 요구사항도 유의해야 합니다.

다음 단계


문서의 이미지 속 텍스트 인식

문서의 텍스트를 인식하려면 아래에 설명된 대로 클라우드 기반 문서 텍스트 인식기를 구성하고 실행합니다.

아래에 설명된 문서 텍스트 인식 API는 문서 이미지 작업에 더욱 편리한 전용 인터페이스를 제공합니다. 하지만 희소 텍스트 API에서 제공하는 인터페이스를 선호하는 경우 클라우드 텍스트 인식기에서 밀집 텍스트 모델을 사용하도록 구성하여 이 인터페이스로 문서를 스캔할 수 있습니다.

문서 텍스트 인식 API를 사용하려면 다음을 수행합니다.

1. 텍스트 인식기 실행

이미지를 UIImage 또는 CMSampleBufferRefVisionDocumentTextRecognizerprocess(_:completion:) 메서드에 전달합니다.

  1. cloudDocumentTextRecognizer을 호출하여 VisionDocumentTextRecognizer 인스턴스를 가져옵니다.

    Swift

    let vision = Vision.vision()
    let textRecognizer = vision.cloudDocumentTextRecognizer()
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    let options = VisionCloudDocumentTextRecognizerOptions()
    options.languageHints = ["en", "hi"]
    let textRecognizer = vision.cloudDocumentTextRecognizer(options: options)
    

    Objective-C

    FIRVision *vision = [FIRVision vision];
    FIRVisionDocumentTextRecognizer *textRecognizer = [vision cloudDocumentTextRecognizer];
    
    // Or, to provide language hints to assist with language detection:
    // See https://cloud.google.com/vision/docs/languages for supported languages
    FIRVisionCloudDocumentTextRecognizerOptions *options =
            [[FIRVisionCloudDocumentTextRecognizerOptions alloc] init];
    options.languageHints = @[@"en", @"hi"];
    FIRVisionDocumentTextRecognizer *textRecognizer = [vision cloudDocumentTextRecognizerWithOptions:options];
    
  2. UIImage 또는 CMSampleBufferRef를 사용하여 VisionImage 객체를 만듭니다.

    UIImage를 사용하는 방법은 다음과 같습니다.

    1. 필요한 경우 imageOrientation 속성이 .up이 되도록 이미지를 회전합니다.
    2. 올바르게 회전된 UIImage를 사용하여 VisionImage 객체를 만듭니다. 회전 메타데이터를 지정하지 마세요. 기본값인 .topLeft를 사용해야 합니다.

      Swift

      let image = VisionImage(image: uiImage)

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithImage:uiImage];

    CMSampleBufferRef를 사용하는 방법은 다음과 같습니다.

    1. CMSampleBufferRef 버퍼에 포함된 이미지 데이터의 방향을 지정하는 VisionImageMetadata 객체를 만듭니다.

      이미지 방향을 가져오는 방법은 다음과 같습니다.

      Swift

      func imageOrientation(
          deviceOrientation: UIDeviceOrientation,
          cameraPosition: AVCaptureDevice.Position
          ) -> VisionDetectorImageOrientation {
          switch deviceOrientation {
          case .portrait:
              return cameraPosition == .front ? .leftTop : .rightTop
          case .landscapeLeft:
              return cameraPosition == .front ? .bottomLeft : .topLeft
          case .portraitUpsideDown:
              return cameraPosition == .front ? .rightBottom : .leftBottom
          case .landscapeRight:
              return cameraPosition == .front ? .topRight : .bottomRight
          case .faceDown, .faceUp, .unknown:
              return .leftTop
          }
      }

      Objective-C

      - (FIRVisionDetectorImageOrientation)
          imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                                 cameraPosition:(AVCaptureDevicePosition)cameraPosition {
        switch (deviceOrientation) {
          case UIDeviceOrientationPortrait:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationLeftTop;
            } else {
              return FIRVisionDetectorImageOrientationRightTop;
            }
          case UIDeviceOrientationLandscapeLeft:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationBottomLeft;
            } else {
              return FIRVisionDetectorImageOrientationTopLeft;
            }
          case UIDeviceOrientationPortraitUpsideDown:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationRightBottom;
            } else {
              return FIRVisionDetectorImageOrientationLeftBottom;
            }
          case UIDeviceOrientationLandscapeRight:
            if (cameraPosition == AVCaptureDevicePositionFront) {
              return FIRVisionDetectorImageOrientationTopRight;
            } else {
              return FIRVisionDetectorImageOrientationBottomRight;
            }
          default:
            return FIRVisionDetectorImageOrientationTopLeft;
        }
      }

      그런 다음 메타데이터 객체를 만듭니다.

      Swift

      let cameraPosition = AVCaptureDevice.Position.back  // Set to the capture device you used.
      let metadata = VisionImageMetadata()
      metadata.orientation = imageOrientation(
          deviceOrientation: UIDevice.current.orientation,
          cameraPosition: cameraPosition
      )

      Objective-C

      FIRVisionImageMetadata *metadata = [[FIRVisionImageMetadata alloc] init];
      AVCaptureDevicePosition cameraPosition =
          AVCaptureDevicePositionBack;  // Set to the capture device you used.
      metadata.orientation =
          [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                       cameraPosition:cameraPosition];
    2. CMSampleBufferRef 객체 및 회전 메타데이터를 사용하여 VisionImage 객체를 만듭니다.

      Swift

      let image = VisionImage(buffer: sampleBuffer)
      image.metadata = metadata

      Objective-C

      FIRVisionImage *image = [[FIRVisionImage alloc] initWithBuffer:sampleBuffer];
      image.metadata = metadata;
  3. 이제 이미지를 process(_:completion:) 메서드에 전달합니다.

    Swift

    textRecognizer.process(visionImage) { result, error in
      guard error == nil, let result = result else {
        // ...
        return
      }
    
      // Recognized text
    }
    

    Objective-C

    [textRecognizer processImage:image
                      completion:^(FIRVisionDocumentText *_Nullable result,
                                   NSError *_Nullable error) {
      if (error != nil || result == nil) {
        // ...
        return;
      }
    
        // Recognized text
    }];
    

2. 인식된 텍스트 블록에서 텍스트 추출

텍스트 인식 작업이 성공하면 VisionDocumentText 객체가 반환됩니다. VisionDocumentText 객체는 이미지에서 인식된 전체 텍스트 및 인식된 문서 구조를 반영하는 객체의 계층 구조를 포함합니다.

VisionDocumentTextBlock, VisionDocumentTextParagraph, VisionDocumentTextWord, VisionDocumentTextSymbol 객체에 대해 해당 영역에서 인식된 텍스트와 영역의 경계 좌표를 가져올 수 있습니다.

예를 들면 다음과 같습니다.

Swift

let resultText = result.text
for block in result.blocks {
    let blockText = block.text
    let blockConfidence = block.confidence
    let blockRecognizedLanguages = block.recognizedLanguages
    let blockBreak = block.recognizedBreak
    let blockCornerPoints = block.cornerPoints
    let blockFrame = block.frame
    for paragraph in block.paragraphs {
        let paragraphText = paragraph.text
        let paragraphConfidence = paragraph.confidence
        let paragraphRecognizedLanguages = paragraph.recognizedLanguages
        let paragraphBreak = paragraph.recognizedBreak
        let paragraphCornerPoints = paragraph.cornerPoints
        let paragraphFrame = paragraph.frame
        for word in paragraph.words {
            let wordText = word.text
            let wordConfidence = word.confidence
            let wordRecognizedLanguages = word.recognizedLanguages
            let wordBreak = word.recognizedBreak
            let wordCornerPoints = word.cornerPoints
            let wordFrame = word.frame
            for symbol in word.symbols {
                let symbolText = symbol.text
                let symbolConfidence = symbol.confidence
                let symbolRecognizedLanguages = symbol.recognizedLanguages
                let symbolBreak = symbol.recognizedBreak
                let symbolCornerPoints = symbol.cornerPoints
                let symbolFrame = symbol.frame
            }
        }
    }
}

Objective-C

NSString *resultText = result.text;
for (FIRVisionDocumentTextBlock *block in result.blocks) {
  NSString *blockText = block.text;
  NSNumber *blockConfidence = block.confidence;
  NSArray<FIRVisionTextRecognizedLanguage *> *blockRecognizedLanguages = block.recognizedLanguages;
  FIRVisionTextRecognizedBreak *blockBreak = block.recognizedBreak;
  CGRect blockFrame = block.frame;
  for (FIRVisionDocumentTextParagraph *paragraph in block.paragraphs) {
    NSString *paragraphText = paragraph.text;
    NSNumber *paragraphConfidence = paragraph.confidence;
    NSArray<FIRVisionTextRecognizedLanguage *> *paragraphRecognizedLanguages = paragraph.recognizedLanguages;
    FIRVisionTextRecognizedBreak *paragraphBreak = paragraph.recognizedBreak;
    CGRect paragraphFrame = paragraph.frame;
    for (FIRVisionDocumentTextWord *word in paragraph.words) {
      NSString *wordText = word.text;
      NSNumber *wordConfidence = word.confidence;
      NSArray<FIRVisionTextRecognizedLanguage *> *wordRecognizedLanguages = word.recognizedLanguages;
      FIRVisionTextRecognizedBreak *wordBreak = word.recognizedBreak;
      CGRect wordFrame = word.frame;
      for (FIRVisionDocumentTextSymbol *symbol in word.symbols) {
        NSString *symbolText = symbol.text;
        NSNumber *symbolConfidence = symbol.confidence;
        NSArray<FIRVisionTextRecognizedLanguage *> *symbolRecognizedLanguages = symbol.recognizedLanguages;
        FIRVisionTextRecognizedBreak *symbolBreak = symbol.recognizedBreak;
        CGRect symbolFrame = symbol.frame;
      }
    }
  }
}

다음 단계