Vision을 사용한 이미지 분석

애플의 Vision은 컴퓨터 시각화 기술을 사용하여 이미지 내에서 얼굴이나 문자, 바코드등을 인식하는 기능을 제공하는 프레임워크이다. 이미 기존에 Core ImageAVFoundation에서 비슷한 기능을 제공하고 있지만, Vision은 여기서 몇 가지 개선된 기능을 제공한다. 먼저 얼굴인식의 정확도면에서 기존 API보다 우수하며, 단순히 얼굴이 존재하는 영역만 찾는 것이 아니라 윤곽, 눈, 코, 입을 각각 찾아낼 수 있다. 그외에 CoreML과 연동하여 훈련된 기계학습 모델을 적용해서 이미지로부터 특정한 형상이 무엇인지를 파악하는 기능등을 제공한다.

이번 글에서는 Vision을 사용해서 얼굴 인식 및 QRCode 인식을 처리하는 예제를 살펴보도록 하겠다.

Vision을 사용한 이미지 분석 더보기

코어 이미지를 통한 이미지 분석 예제

코어 이미지(Core Image)는 흔히 알려진 바와 같이 이미지에 대한 고성능 필터 효과 처리를 지원하는 프레임워크이면서 이미지에서 사람의 얼굴이나 QR코드, 텍스트를 탐지해내는 탐지 기능도 제공하고 있다. 코어 이미지가 제공하는 이미지 분석 기술을 제공하면 이러한 탐지를 빠르게 수행할 수 있을 뿐 아니라, 코어 이미지의 여러 필터 기능을 활용해서 찾아낸 부분을 하이라이트 처리하는 등의 기능을 손쉽게 구현할 수 있다. 이 글에서는 코어 이미지의 디텍터 클래스인 CIDetector를 사용하여 이미지에서 특정한 형상을 찾는 방법에 대해서 알아보고자 한다.

이미지에서 특정한 형상을 찾기

코어이미지가 제공하는 이미지 분석 기능을 사용하면 이미지 내에서 특정한 형상(feature)을 찾을 수 있다. 이 작업에서는 크게 두 가지 클래스를 다루게 된다.

  1. CIDetector : 이미지 분석처리를 담당한다.
  2. CIFeature : 분석된 결과에 대한 정보를 담는다.

CIDetector 클래스는 매우 간단한 API를 가지며, 다음의 절차를 거쳐 사용할 수 있다.

  1. init?(ofType: context: options:)를 통해서 새로운 디텍터를 생성한다.
  2. 생성된 디텍터에게 이미지를 전달하여 목표로 하는 형상을 탐지한다. 이 때 사용하는 메소드는 feature(in: options:) 이다.
  3. 탐지된 결과는 [CIFeature] 타입의 값으로 리턴된다.

CIFeature – 탐지된 결과에 대한 정보

CIFeature는 이미지 분석 결과에서 탐지된 매 항목에 대한 정보를 담고 있는데, 기본적으로 bounds 속성으로 이미지 내에서 해당 형상이 차지하는 부분을 알아 낼 수 있다. CIFeature는 여러 타입의 분석 결과에 대한 추상 클래스 타입이며, 어떤 것을 탐지하려고 했는가에 따라서 구체적인 서브 클래스를 사용하게 된다.

  1. CIRectangleFeature : 이미지 내에서 사각형을 탐지한 결과이다. 사각형은 딱 떨어지는 직사각형이 아니기 때문에 bottomLeft, bottomRight, topLeft, topRight 의 4개의 모서리 점 위치에 대한 정보를 추가적으로 가지고 있다.
  2. CITextFeature :  이미지에서 글자를 찾았을 때, 사용된다.
  3. CIQRCodeFeature : 이미지 내에서 QR코드를 탐지한 결과이다. QR코드를 디코딩한 문자열을 messageString 이라는 프로퍼티로 액세스할 수 있다.
  4. CIFaceFeature : 이미지 내에서 사람의 얼굴을 탐지한 결과이다.

이미지에서 사람 얼굴을 찾기

CIDetector를 사용하면 사람의 이미지 내에서 사람의 얼굴을 포착할 수 있다. 사람의 얼굴을 찾기 위해서는 CIDetector의 타입을 CIDetectorTypeFace로 주어 인스턴스를 생성한다. 이 때 컨텍스트는 기본적으로 nil을 전달할 수 있는데, 만약 소스로 주어지는 이미지와 연관된 CIContext 객체가 있다면 이를 넘겨줄 수 있다. (그렇게 하여 성능을 향상시킬 수 있다.) 주어진 이미지가 있을 때, 사람의 얼굴이 표현된 영역을 찾는 것은 다음과 같은 코드를 통해서 구현할 수 있다.

let image: CIImage = .... // #1
// #2
let detector = CIDetector(ofType: CIDetectorTypeFace,
                          context: nil,
                          options:
            [CIDetectorAccuracy: CIDetectorAccuracyHigh])!
// #3
if let features = detector.features(in: image) as? [CIFaceFeature] {
  let rects = features.map{ $0.bounds } // #4
  for rect in rects {
    // ... do something with face rect
  }
}
  1. 이미지는 이미 주어졌다고 가정한다.
  2. 디텍터를 생성한다. 디텍터 생성시 컨텍스트는 nil을 보낼 수 있고, 옵션은 정확도 수준을 지정할 수 있다.
  3. 이미지 내에 탐지되는 결과는 1개 이상일 수 있다. [CIFaceFeature]로 캐스팅한다.
  4. 각각의 CIFeature에 대해서 bounds 속성을 이용해서 이미지 내에 각 얼굴이 들어있는 영역을 지정할 수 있다.

얼굴이 들어간 부분을 하이라이트하기

얼굴을 찾아서 얼굴이 들어간 부분을 하이라이트하려면 어떻게 해야할까? CIImage 타입의 원본으로부터 이를 분석하여 얼굴의 영역들을 얻은 다음, 원본위에 하이라이트 영역을 칠한 결과물을 만드는 함수가 있다면 여기에 원본과 영역의 배열을 넘겨주어 최종 결과 이미지를 얻을 수 있다.

입력과 출력이 이렇게 결정되면 이 함수의 타입이 (CIImage, [CGRect]) -> CIImage라는 것을 알 수 있고, 이러한 함수를 구현하면 되는 것이다. 이를 구현하는 방법은 크게 코어 이미지를 이용해서 원본을 그린 후, 그 위에 하이라이트 영역을 그리는 방법이 있을 수 있고 또 하이라이트 영역에 대한 이미지를 만든 후에 이를 블렌드모드 필터를 이용해서 합성하는 방법이 있다. 여기서는 후자의 방법을 사용해보도록 하겠다.

/// 이미지의 특정 영역을 하이라이트 하기
func highlight(in source: CIImage, rects: [CGRect]) -> CIImage {
  let imageSize = source.extent.size
  let mask: CGImage = {
    let ctx = CGContext(data: nil, width: Int(imageSize.width), height: Int(imageSize.height),
                        bitsPerComponent: 8, bytesPerRow: 0,
                        space: CGColorSpaceCreateDeviceRGB(),
                        bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
    ctx.setFillColor(NSColor.yellow.cgColor)
    ctx.fill(rects)
    return ctx.makeImage()!
  }()

  // mask를 InputImage로 하고 source를 BackgroundImage로 해서 하이라이트된 이미지를 생성
  let inputImage = CIImage(cgImage: mask)
  return inputImage.applyingFilter("CIOverlayBlendMode", parameters:
         [kCIInputBackgroundImageKey: source])
}

사실 이 함수에서 이미지를 합성하는 방법만 약간 바꾸면, 어떤 사진 내에서 사람 얼굴만 모자이크 처리하는 익명화 프로그램을 만드는 도구를 간단히 생성할 수 있을 것이다. (원본을 모자이크/블러처리한 이미지를 만들고, 영역 내에 사각형을 그린 이미지와 컴포지팅하여 다시 원본에 덧그리는 방식이다. 이는 재미있는 토픽으로 보이니 조만간 살펴보도록 하겠다.)

QRCode를 찾기

이미지 분석에서 유용한 기술 중 하나는 QRCode를 탐지하고, QRCode내에 인코딩된 메시지를 얻는 것이다. 이는 위의 예제에서 Face 대신 QRCode만 넣으면 된다고 할 정도로 간단한 작업이다. QR코드 탐지 결과인 CIQRCodeFeaturemessageString이라는 프로퍼티를 통해서 인코딩된 메시지에 액세스할 수 있다.

다음 함수는 주어진 CIImage에 대해서 QRCode를 찾고, 그 속에 인코딩된 메시지를 완료 핸들러로 전달하는 함수이다.

/// QRCode를 찾아서 해석하기
func detectQRCode(in image: CIImage, completionHandler: ((String) -> Void)?) 
{
  let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil,
                   options:[CIDetectorAccuracy: CIDetectorAccuracyHigh])!
  if let result = detector.features(in: image).first as? CIQRCodeFeature,
     let message = result.messageString
  {
    completionHandler?(message)
  }
}

정리

코어 이미지가 제공하는 이미지 분석 도구는 Vision에 비해서 정확도는 아주 약간 떨어질 수 있지만, 여전히 빠르고 쓸만하게 동작한다. 또 API의 디자인 역시 심플하고 쉽게 사용할 수 있기 때문에 이를 사용해서 여러가지 유용한 도구들을 만들 수 있다.

참고자료