이미지를 익명화하는 프로그램 – Vision + CoreImage

Vision을 이용한 얼굴 인식의 보다 상세한 예제로 자동 익명화 프로그램을 작성해보았다. 이 프로그램은 터미널에서 이미지 파일의 이름을 제시하면 해당 이미지에서 사람의 얼굴을 찾아, 해당 영역을 모자이크 처리한 PNG 파일을 생성한다.

이 프로그램은 크게 세 개의 함수로 구성된다.

  1. detectFaces(in: completionHandler:) – 주어진 이미지(CIImage)에서 얼굴을 찾아낸다.
  2. createAnonymizedImage(from: boundingBoxes) – 주어진 이미지와 사각형영역을 사용해서 해당 영역이 모자이크처리된 이미지를 합성한다.
  3. main() – 명령줄 인자로 전달된 정보를 통해 이미지 파일을 열고 1, 2의 함수를 사용해서 얼굴만 모자이크 처리한 이미지를 만들어 PNG 파일로 저장한다.
이미지를 익명화하는 프로그램 – Vision + CoreImage 더보기

Vision을 이용한 이미지 인식

Vision을 이용한 얼굴인식이나 QRCode 인식에 대해서 살펴보았는데, 사실 이러한 기능들은 정확도면에서 조금 뒤떨어질 수는 있지만 코어 이미지에서도 어느 정도 지원했었던 기능이다. 대신에 Vision은 CoreML과 결합하여 이미지 내의 오브젝트를 인식하고 그 이름을 추출할 수 있는 기능을 제공한다.  (고양이 사진을 보고 ‘고양이’를 알아내는 바로 그 기능이다.)

이번 시간에는 Vision + CoreML을 이용한 이미지 내 사물 인식 기능을 어떻게 구현할 수 있는지 알아보자.

Vision을 이용한 이미지 인식 더보기

이니셜라이저 – Swift

Swift의 클래스와 구조체, enum 객체들은 사용하기 전에 반드시 초기화되어야 한다. 그러면 초기화(initialization)이란 무엇인가? 객체의 생성 자체를 초기화과정에 포함시키는 관점과 그렇지 않은 관점이 있지만, 여기서는 “객체를 만들어서 사용가능한 상태로 준비하는 일”이라고 보자. let foo = Foo() 와 같이 특정한 타입의 인스턴스를 생성하는 구문을 실행했을 때 저 아래(?)에서 벌어지는 과정은 다음과 같다.

이니셜라이저 – Swift 더보기

Vision을 사용한 이미지 분석

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

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

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

Swift4의 키패스 표현

키패스는 어떤 객체의 프로퍼티 혹은 프로퍼티의 프로퍼티 체인에 대해서 그 이름을 통해서 값을 찾아나가는 표현을 말한다. Objective-C에서 키패스는 키패스 경로를 나타내는 문자열을 사용해서 특정 객체의 속성을 액세스하기 때문에 컴파일 타임이 아닌 런타임에 액세스해야 할 프로퍼티를 결정하는 동적 기능으로 키밸류코딩과 키밸류 옵저빙에 사용된다. Swift2까지는 Swift 내에 키패스에 대한 기능이 별도로 마련되지 않았고, NSObject의 value(forKey:)setValue(_:forKey:)를 사용하면서 문자열을 그대로 사용했다.

문자열을 통해서 키패스를 사용하는 것은 편리할 수는 있으나, 컴파일 타임에서 오타에 의해 존재하지 않는 키패스를 참조하는 것을 체크할 방법이 없어서 디버깅이 곤란한 부분이 있었다. 이 부분을 개선하기 위해 Swift3에서 #keyPath() 문법 표현이 추가되었는데, 이 문법은 코딩 시점에는 컴파일러의 도움을 받아 올바른 키패스를 확인할 수 있고, #keyPath() 표현을 통해 해당 키패스값을 문자열로 안전하게 변환할 수 있었다.

하지만 키패스를 문자열로 치환하는 이와 같은 방법은 Swift의 디자인 관점에서는 몇 가지 한계를 갖는다. 키패스 자체는 프로퍼티를 찾아가는 경로만을 정의하므로 타입 정보를 잃고 그 결과가 Any가 되어버린다든지, 파싱이 느리고 NSObject 기반의 클래스에서만 사용할 수 있었다. Swift4에서는 이러한 단점을 보완하고 클래스외의 모든 Swift 타입에서 키패스를 통해서 프로퍼티를 참조할 수 있는 범용적인 키패스 문법(과 키패스를 위한 코어 타입)이 추가되었다.

Swift4의 키패스 문법

Swift4의 키패스 문법은 단순히 백슬래시(\)로 시작하는 키패스 값을 말한다. Objective-C와 달리 self 대신에 타입 이름을 쓰거나, 타입이 분명한 경우, 타입 이름을 생략하고 바로 . 으로 시작하는 키패스를 사용할 수 있다. 다음은 Swift Evolution의 새로운 키패스 제안서에 실린 예제이다.

class Person {
  var name: String
  var friends: [Person] = []
  var bestFriend: Person? = nil
  init(name: String) {
    self.name = name
  }
}

var han = Person(name: "Han Solo")
var luke = Person(name: "Luke Skywalker")
luke.friends.append(han)

// 키패스 객체를 생성한다. 
let firstFriendsNameKeyPath = \Person.friends[0].name
// 생성한 키패스를 사용해서 프로퍼티를 액세스한다.
let firstFriend = luke[keyPath: firstFriendsNameKeyPath] // "Han Solo"

// 항상 . 으로 시작해야 한다. 이는 배열의 요소 참조시에도 마찬가지이다.
luke.friends[keyPath: \.[0].name]
luke.friends[keyPath: \[Person].[0].name] 

// 옵셔널 프로퍼티는 ?를 붙여서 액세스해야 한다.
let bestFriendsNameKeyPath = \Person.bestFriend?.name
let bestFriendsName = luke[Keypath: bestFriendsNameKeyPath] // nil

키 패스 타입

Swift4의 키패스는 KeyPath라는 타입에 의해서 관리된다. 이 타입은 하위 속성을 참조하기 위해서 다른 키패스를 이어 붙이는 것이 가능하고, 또한 루트 타입과 키패스가 가리키는 속성의 타입을 그 인자로 가질 수 있다. 이 말은 위 예제에서와 같은 표현으로 키패스를 생성하는 경우, luke의 타입인 Personname 속성의 타입인 String 에 대한 정보가 키패스 내부에 내제된다는 것이다. 따라서 위 예제에서 bestFriend 변수의 타입은 String이 라는 점을 컴파일러는 알 수 있다.