검색결과 리스트
고차원 함수에 해당되는 글 2건
- 2015.11.10 [swift2] Core Image Filter 예제
- 2015.06.22 [swift] Map, Filter, Reduce
글
[swift2] Core Image Filter 예제
절차지향 또는 객체지향 프로그래밍에 익숙한 상태에서, 함수형 프로그래밍에 적응하기란 쉽지 않습니다. 다른 프로그래밍 방법론을 공부했을 때처럼 많은 함수형 프로그램 코드를 읽고 이해하고, 또 많은 예제를 실제로 작성해봐야만 숙달될 수 있습니다.
swift 함수형 프로그램의 예로 Core Image 필터 예제를 소개합니다. 이 예제는 objc.io 에서 출판된 Funtional Programming in Swift 에서 발췌하였습니다.
- 이 예제에서는 MaxOSX Core Image 프레임워크의 함수형 wrapper API 작성을 목표로 합니다.
- 또한 고차원함수(high-order function)와 같은 함수형 프로그래밍의 실질적인 활용예를 보는 것입니다.
Core Image
- Core Image는 강력한 이미지 프로세싱 프레임워크지만 사용하기가 불편합니다.
- Core Image API는 키밸류 코딩을 사용해서 설정할 수 있는 느슨하게 타이핑된 API 입니다.
- 파라미터 이름을 입력할 때 실수할 수 있는 여지가 많아 런타임 에러를 발생시킬 수 있습니다.
- 이번에 개발할 API 는 안전하고, 모듈화되었으며, (오타로 인한)런타임 에러가 발생하지 않도록 타입을 장점을 활용합니다.
Filter Type
- Core Image의 핵심클래스는 이미지 필터를 생성하는데 사용하는 CIFilter 클래스입니다.
- CIFilter 객체 생성시 반드시 kCIInputImageKey 키에 해당하는 input image를 전달해야합니다.
- kCIOutputImageKey 키를 사용해서 필터가 적용된 결과 이미지를 얻어올 수 있습니다.
- 또한 이 결과 이미지는 다른 필터의 입력으로 사용할 수 있습니다.
CIImage 타입을 파라미터로 받아 CIImage 타입의 결과 이미지를 반환하는 함수타입 Filter를 정의합니다.
typealias Filter = CIImage -> CIImage
Blur 필터 함수
- 이미지에 블러를 적용하는 blur 함수를 정의해보겠습니다.
- 이 블러필터는 한개의 blur radius 파라미터를 받습니다.
func blur(radius:Double) -> Filter {
return { image in
let parameters = [
kCIInputRadiusKey : radius,
kCIInputImageKey : image
]
let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters)
return filter!.outputImage!
}
}
- blur 함수는 CIImage 타입의 이미지를 인자로 받고, 결과 이미지(filter!.outputImage)를 리턴하는 Filter 함수를 반환합니다.
Color Overlay 필터 함수
- 이미지 위에 특정 컬러 레이어로 덮는 오버레이 필터를 정의해보겠습니다.
- CoreImage 에는 오버레이 필터가 없지만 이미 있는 필터들을 조합하여 만들것입니다.
- 컬러를 생성하는 color generator 필터와 레이어를 얹는 source-over compositing 필터를 사용하겠습니다.
Color 레이어 생성필터
func colorGenerator(color:NSColor) -> Filter {
return { image in
let parameters = [kCIInputColorKey:CIColor(color: color)!]
let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters)
return filter!.outputImage!.imageByCroppingToRect(image.extent)
}
}
- blur 필터와 유사하지만 input 이미지가 필요하지 않다는 차이가 있습니다.
- 생성된 컬러 레이어 이미지를 특정크기(인자로 받은 이미지의 크기)로 잘라주어야 합니다.
- 다음으로 두 이미지를 복합하는 필터를 정의합니다.
레이어 오버필터
func compositeSourceOver(overlay:CIImage) -> Filter {
return { image in
let parameters = [
kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay
]
let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters)
return filter!.outputImage!.imageByCroppingToRect(image.extent)
}
}
- 마지막에 input 이미지의 크기만큼 결과 이미지를 자릅니다.
- 두 필터를 결합하여 color overlay 필터를 생성합니다.
Color Overlay 필터
func colorOverlay(color:NSColor) -> Filter {
return { image in
let overlay = colorGenerator(color)(image)
return compositeSourceOver(overlay)(image)
}
}
- colorOverlay 함수는 컬러를 인자로 받아 필터함수를 반환합니다.
- 이 필터함수에 이미지를 전달하면, 앞서 전달한 컬러로 오버레이된 이미지를 반환합니다.
Filter 복합하기
- blur 필터와 color overlay 필터를 복합해서 사용해보겠습니다.
- 원본 이미지 파일을 읽습니다.
let path = NSBundle.mainBundle().pathForResource("16.jpg", ofType: nil)!
let image = CIImage(contentsOfURL: NSURL(fileURLWithPath: path))!- blur 필터를 적용합니다.
let blurRadius = 5.0
let blurredImage = blur(blurRadius)(image)- 빨간색 레이어를 오버레이합니다.
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let overlaidImage = colorOverlay(overlayColor)(blurredImage)Function Composition
- 위 예제처럼 두 필터함수를 한문장의 표현식으로 표현할 수 있습니다.
let result = colorOverlay(overlayColor)(blur(blurRadius)(image))
- 하지만 여러 괄호들로 인해 조합된 필터의 수가 많아질 수록 가독성이 나타집니다.
- 더 좋은 방법은 필터 조합을 위한 커스텀 연산자를 정의하는 것입니다.
- 먼저 필터를 조합하는 함수를 정의해보겠습니다.
func composeFilters(filter1 filter1:Filter, filter2:Filter) -> Filter {
return { image in filter2(filter1(image)) }
}
- 위 함수는 두개의 필터를 인자로 받아 조합하여 새로운 필터를 정의합니다.
- 반환된 필터 함수는 CIImage 타입의 image 인자를 filter1과 filter2에 순차적으로 전달합니다.
- 아래와 같이 사용할 수 있습니다.
let myFilter1 = composeFilters(filter1: blur(blurRadius), filter2: colorOverlay(overlayColor))
let result1 = myFilter1(image)
- 이제 필터 조합을 위한 연산자를 정의하여 좀더 가독성 있게 만들어보겠습니다.
- 모든 곳에 자체 연산자를 정의할 필요는 없지만, 필터 조합은 이미지 처리 라이브러리에서 반복해서 발생하는 작업이기 때문에 그 용도가 적합합니다.
infix operator >>> { associativity left }
func >>> (filter1:Filter, filter2:Filter) -> Filter {
return { img in filter2(filter1(img)) }
}
- 이제 >>> 연산자를 이전에 정의했던 composeFilters 함수처럼 사용할 수 있습니다.
let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)
- >>> 연산자를 left-associativity 로 정의했기 때문에, 유닉스 파이프처럼 왼쪽에서 오른쪽으로 필터가 적용됩니다.
- 우리가 정의한 필터 조합은 Function Composition 의 한 예입니다.
전체소스
import Cocoa
import XCPlayground
// define Filter Type
typealias Filter = CIImage -> CIImage
// blur function
func blur(radius:Double) -> Filter {
return { image in
let parameters = [
kCIInputRadiusKey : radius,
kCIInputImageKey : image
]
let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters)
return filter!.outputImage!
}
}
// color layer function
func colorGenerator(color:NSColor) -> Filter {
return { image in
let parameters = [kCIInputColorKey:CIColor(color: color)!]
let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters)
return filter!.outputImage!.imageByCroppingToRect(image.extent)
}
}
// overlay function
func compositeSourceOver(overlay:CIImage) -> Filter {
return { image in
let parameters = [
kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay
]
let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters)
return filter!.outputImage!.imageByCroppingToRect(image.extent)
}
}
// color overlay function
func colorOverlay(color:NSColor) -> Filter {
return { image in
let overlay = colorGenerator(color)(image)
return compositeSourceOver(overlay)(image)
}
}
// 원본 이미지 읽기
let path = NSBundle.mainBundle().pathForResource("16.jpg", ofType: nil)!
let image = CIImage(contentsOfURL: NSURL(fileURLWithPath: path))!
// apply blur
let blurRadius = 5.0
let blurredImage = blur(blurRadius)(image)
// apply color overlay
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let overlaidImage = colorOverlay(overlayColor)(blurredImage)
// define composition function
func composeFilters(filter1 filter1:Filter, filter2:Filter) -> Filter {
return { image in filter2(filter1(image)) }
}
let myFilter1 = composeFilters(filter1: blur(blurRadius), filter2: colorOverlay(overlayColor))
let result1 = myFilter1(image)
// define composition operator
infix operator >>> { associativity left }
func >>> (filter1:Filter, filter2:Filter) -> Filter {
return { img in filter2(filter1(img)) }
}
let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)
'Swift' 카테고리의 다른 글
| [swift2] 키워드로 보는 Swift (0) | 2015.11.30 |
|---|---|
| [swift2] Currying 함수 (0) | 2015.11.25 |
| [swift2] Core Image Filter 예제 (0) | 2015.11.10 |
| [swift2.1] Xcode7.1 Playground 변경사항 (0) | 2015.10.27 |
| [swift2] Functor 와 Monad (0) | 2015.10.25 |
| [swift2] 타입변환 연산자 (is, as, as?, as!) (0) | 2015.10.18 |
설정
트랙백
댓글
글
[swift] Map, Filter, Reduce
함수를 인자로 취하는 함수를 고차원 함수(higher-order function)라고 합니다.
Swift 표준라이브러리에서 array의 메서드로 제공하는 고차원 함수인 map, filter, reduce에 대해서 알아보겠습니다.
1. Map
func map<U>(transform: (T) -> U) -> Array<U>
Map은 배열 각 요소 x에 변환함수 transform을 적용하고 그 결과값으로 구성된 배열을 반환하는 함수입니다.
배열요소들을 다른 값으로 맵핑하는 함수이지요.
2. Filter
func filter(includeElement: (T) -> Bool) -> Array<T>
Filter는 조건식을 인자로 받아, 조건식이 true를 만족하는 요소들로만 구성된 배열을 반환하는 함수입니다.
쉽게 말하면, 배열요소들을 필터링하는 함수입니다.
3. Reduce
func reduce<U>(initial: U, combine: @noescape (U, T) -> U) -> U
Reduce는 U를 초기값으로 하여,각 배열요소들과 순차적으로 결합연산을 하여 누적된 단일값을 반환하는 함수입니다.
4. 사용예
이제 사용법에 대해서 알아볼까요? 다음과 같은 정수형 배열이 있습니다.
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- 1. 정수형 배열의 각 요소에 1을 더한 배열을 얻고 싶다.
- 2. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
- 3. 정수형 배열에서 짝수로만 구성된 배열을 얻고 싶다.
- 4. 정수형 배열 값들의 총합을 얻고 싶다.
map 함수를 사용하여 1,2번 문제의 답을 쉽게 얻을 수 있습니다.
// 1. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
arr.map { (x) -> Int in
return x+1
}
// 2. 정수형 배열의 각 요소에 2를 곱한 배열을 얻고 싶다.
arr.map { (x) -> Int in
return x*2
}
filter를 사용하여 3번을 해결해보겠습니다.
// 3. 정수형 배열에서 짝수로만 구성된 배열을 얻고 싶다.
arr.filter { (x) -> Bool in
return x%2 == 0
}
당연히 4번은 reduce의 몫입니다.
// 4. 정수형 배열 값들의 총합을 얻고 싶다.
arr.reduce(0, combine: { (result, x) -> Int in
return result + x
})
Trailing Closure 를 사용하여 다소 낯선 형태지만 코드가 너무 짧아 놀라셨죠?
(Trailing Closure는 함수의 마지막 인자가 클로져(함수)일 경우, 클로져를 함수 호출부의 꼬리(trailing)에 표기하는 방식입니다. )
이러한 고차원 함수들을 복합적으로 사용하여 프로그래밍하는 방식을 함수형 프로그래밍이라고 합니다.
함수형 프로그래밍은 짧고 명료한 코드를 작성할 수 있게하고, 버그의 가능성 또한 훨씬 줄인다고 합니다.
낯선 함수형 프로그래밍 방식에 익숙해지려면 다소 시간이 걸리겠지요? ^^
5. Map 함수 구현하기
학습한 내용을 좀더 잘 이해하기 위해 직접 map 함수를 만들어 보겠습니다.
일반적인 방법으로 1,2번 문제를 구현하는 코드를 작성했습니다.
var arr = [1, 2, 3, 4, 5]
func pluseOne(arr:[Int]) -> [Int] {
var result:[Int] = []
for x in arr {
result.append(x + 1)
}
return result
}
func multiplyTwo(arr:[Int]) -> [Int] {
var result:[Int] = []
for x in arr {
result.append(x * 2)
}
return result
}
두 함수는 연산하는 행위만 제외하고 거의 비슷합니다. 연산하는 행위를 함수로 묶어내면 일반화가 가능해보이네요.
func map(arr:[Int], transform:(Int->Int)) -> [Int] {
var result:[Int] = []
for x in arr {
result.append(transform(x))
}
return result
}
이제 제네릭을 사용하여 범용타입으로 대치하면
func map<T, U>(arr:[T], transform:(T->U)) -> [U] {
var result:[U] = []
for x in arr {
result.append(transform(x))
}
return result
}
map 함수가 완성되었습니다. 우리는 글로벌 함수로 구현했지만 표준라이브러리에서는 array의 메소드로 제공한다는 점이 유일한 차이입니다.
5. Filter 함수 구현하기
Map 함수를 구현해보아서 Filter 함수 구현은 좀더 간단합니다.
// 일반적인 방법으로 구현하고
func filter(arr:[Int]) -> [Int] {
var result:[Int] = []
for x in arr {
if x%2 == 0 {
result.append(x)
}
}
return result
}
// 함수로 공통 행위를 묶어낸 다음
func filter(arr:[Int], includeElement:(Int->Bool)) -> [Int] {
var result:[Int] = []
for x in arr {
if includeElement(x) {
result.append(x)
}
}
return result
}
// 제네릭 적용
func filter<T>(arr:[T], includeElement:(T->Bool)) -> [T] {
var result:[T] = []
for x in arr {
if includeElement(x) {
result.append(x)
}
}
return result
}
6. Reduce 함수 구현하기
단계적으로 구현해보니 고차원 함수 구현이 어렵지 않죠? 이제 reduce를 구현해 볼까요?
// 일반적으로 구현하고
func reduce(arr:[Int]) -> Int {
var result:Int = 0
for x in arr {
result = result + x
}
return result
}
// 초기값 할당과 누적하는 행위를 파라미터로 분리하고
func reduce(arr:[Int], initial:Int, combine:(Int,Int)->Int) -> Int {
var result:Int = initial
for x in arr {
result = combine(result, x)
}
return result
}
// 제네릭 적용
func reduce<T, U>(arr:[T], initial:U, combine:(U, T)->U) -> U {
var result:U = initial
for x in arr {
result = combine(result, x)
}
return result
}
지금까지 Swift 표준라이브러리의 Map, Filter, Reduce에 대해 알아보았습니다.
'Swift' 카테고리의 다른 글
| [swift2] Xcode7 beta2 Swift 언어 변경사항 (0) | 2015.06.24 |
|---|---|
| [swift] 문자열 Indexing과 Slicing (0) | 2015.06.23 |
| [swift] Map, Filter, Reduce (0) | 2015.06.22 |
| [swift2] New in Swift2.0 (0) | 2015.06.19 |
| [swift] Apple Swift Blog (0) | 2015.06.10 |
| [swift] Swift 기초 - 4 (0) | 2014.10.29 |