Core Image의 개념과 이미지 내 필터 적용하는 방법

오늘은 PHAsset에 이어 Core Image에 대해서 다뤄보려고 한다. 가져온 이미지를 바탕으로 효과를 넣어 사용할 수 있는 Core Image에는 정말 다양한 내용이 있다.

 

먼저 Core Image는 애플에서 제공하는 아주 강력한 이미지 처리 및 분석 프레임 워크이다. 다양한 이미지의 필터, 변형, 분석 기능을 제공한다. 이 프레임 워크는 GPU 가속을 활용해서 이미지에 효과를 적용할 수 있도록 하고 모든 iOS 버전에서 사용이 가능하다 🙂

 

여기서 이미지를 처리한다는 개념은 필터를 적용하는건데 input 이미지를 픽셀단위로 검토하고 이미지에 특정한 효과를 적용해서 output 이미지를 만들어내는 것 이다.

 

Core Image 구성요소

먼저 주요 구성 요소를 살펴보면

  • CIImage
    • CIImage는 이미지 데이터와 함께 Core image 필터를 처리할 수 있는 객체이다. CIImage는 단순히 이미지 데이터만 포함하는 것이 아니라, 이미지의 소스(파일, 데이터, 비트맵 등)와 함께 이미지의 연산에 대한 정보도 포함하고 있다.
  • CIFilter
    • CIFilter는 이미지에 적용할 수 있는 다양한 필터를 정의하는 클래스이다. CIFilter에는 블러, 색상 조정, 왜곡, 스타일화 등 다양한 이미지 효과가 포함 되어 있다.
  • CIVector, CIColor, CIVector
    • 이 클래스는 필터의 입력 파라미터로 사용되는 다양한 유형의 값을 정의 한다. 예를 들어 필터의 입력 파라미터로 위치를 지정할 때 CIVector를 사용하며, 색상을 지정할 땐 CIColor를 사용하게 된다.

Core Image 주요 기능

  • 이미지 필터링
    • 이미지 필터링은 특정 알고리즘을 사용해 이미지를 수정하거나 변형하는 과정이다. Core Image에서는 CIFilter 클래스를 사용해 다양한 필터를 적용할 수 있다.
    • (ex 색상 조정 필터, 왜곡 필터 등)
  • 이미지 합성
    • 두개 이상의 이미지를 결합하여 새로운 이미지를 생성하는 과정이다. Core Image에서는 CIFilter를 사용해 이미지를 합성할 수 있다.
  • 이미지 변형
    • 이미지의 크기, 회전, 스케일 등을 변경하는 기능이다. CIAffineTransform을 사용해 이미지를 회전하거나, CIStraightenFilter를 사용해 이미지를 정렬할 수 있다.
  • 이미지 분석
    • CIDetector를 사용해 이미지에서 얼굴을 인식하거나, 이미지의 텍스트를 인식할 수 있다.

CIContext

CIContext는 Core Image의 필터링 및 렌더링 작업을 수행하는데 필요한 모든 리소스와 정보를 관리하는 객체이다. CIContext는 CPU 또는 GPU를 통해 이미지를 처리하고 렌더링할 수 있으며, 이를 통해 최적화된 성능을 제공한다.

  1. 이미지 렌더링
    1. CIContext는 필터 체인을 거친 최종 CIImage를 실제로 화면에 표시하거나 저장할 수 있는 형식으로 렌더링한다. 필터를 적용한 후 CIImage 객체를 생성하지만, 이는 lazy evaluation 방식으로 처리되므로 실제 이미지 테이터는 렌더링되기 전까지 계산되지 않는다.
    2. CIContext는 이 최종 계산을 수행하며 GPU 또는 CPU를 사용하여 최종 이미지를 생성한다.
  2. 메모리 관리
    1. Core Image의 이미지 처리 과정에서 CIContext 는 메모리 관리를 담당한다. 이미지가 데이터가 렌더링 되고 나면, 필요한 메모리 리소스를 효율적으로 관리하여 성능을 최적화한다.
  3. 멀티스레딩
    1. CIContext 는 멀티스레딩을 지원하여, 여러 스레드에서 동시에 이미지 처리 작업을 수행할 수 있다. 이를 통해 복잡한 이미지 필터링 작업도 빠르게 처리할 수 있다.
let context = CIContext()

이렇게 간단하게 생성해서 전역적으로 사용할 수 있도록 선언해두는 것이 좋다.

let ciImage = CIFilter(name: "CISepiaTone", parameters: [kCIInputImageKey: inputImage, kCIInputIntensityKey: 0.8])!.outputImage!

let context = CIContext()
if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
    let uiImage = UIImage(cgImage: cgImage)
}

예시를 통해 한번 살펴보면 필터가 적용된 CIImage 객체가 생성된 후 CIContext를 사용해 CGImage로 변환한다.

변환된 CGImage는 UIImage로 다시 변환할 수 있다.

 

Core Image를 활용해 필터를 적용하는 예시

FilterButton(
    filterName: "Sepia",
    filterAction: {
        editFilterViewModel.applySepiaTone(to: image)
    },
    image: editFilterViewModel.applyPreviewFilter(filterName: "CISepiaTone", to: image)
)

FilterButton을 통해 필터를 선택하는 것을 만들어 주고 Sepia필터를 선택하면 FilterAction 클로저가 호출되도록 구현한다.

 

이 클로저는 VM의 applySepiaTone 메서드를 호출해 선택된 이미지를 CISepiaTone 필터를 사용해 필터링한다.

func applySepiaTone(to image: UIImage) -> UIImage? {
    return applyFilter(image, filterName: "CISepiaTone")
}

applySepiaTone 메서드는 내부적으로 applyFilter 메서드 호출하여 이미지를 필터링한다.

 

필터 이름으로 CISepiaTone을 전달해 CoreImage 필터를 적용하도록 설정한다.

private let context = CIContext() // 이미지 처리에 사용되는 CoreImage 컨텍스트입니다.

func applyFilter(_ image: UIImage, filterName: String) -> UIImage? {
    guard let ciImage = CIImage(image: image) else { return nil }
    let filter = CIFilter(name: filterName)
    filter?.setValue(ciImage, forKey: kCIInputImageKey)

    // 필터가 적용된 이미지를 출력합니다.
    if let outputImage = filter?.outputImage,
       let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
        return UIImage(cgImage: cgImage)
    }
    return nil
}

applyFilter 메서드는 Core Image의 필터를 적용하는 핵심 로직이다. 먼저 UIImage를 CIImage로 변환한다. CIImage는 Core Image에서 필터를 적요하기 위해 사용하는 데이터 형식이다.

 

필터 이름을 사용해 CIFilter 객체를 생성하고 입력 이미지를 kCIInputImageKey로 설정한다.

 

필터가 적용된 결과는 outputImage로 변환되며, 이 이미지는 여전히 CIImage 형식이다.

 

CIContext를 사용해 CIImage를 CGImage로 변환한 후 최종적으로 UIImage로 변환하여 반환한다.

self.filteredImage = filteredImage

마지막으로 필터가 적용된 이미지를 filteredImage에 넣어주면 해당 필터가 적용되는 이미지가 완성된다!

오늘은 이미지를 필터 적용해 새로운 이미지로 만드는 방법에 대해서 알아봤다 🙂 내일은 이미지를 자를 수 있는 크롭박스를 가져와서 자유롭게 사용하고 이미지도 자를 수 있는 방법에 대해서 알아볼 예정이다!

 

오늘은 여기까지!