카메라에 실시간 필터 적용하기

1 답변 글타래를 보이고 있습니다
  • 글쓴이
    • 광현
      참가자
      • 글작성 : 15
      • 답글작성 : 26

      안녕하세요 오늘은 카메라에 실시간 필터(real-time filter)를 적용해 보았습니다. 다만 실시간 필터 중에서도 apple이 미리 구현해 놓은 필터 중 하나인 comic effect 필터를 사용해 보았습니다.

      실시간 핊터 구현에서 previewView의 구현이 이전 카메라 앱 구현에서 AVCaptureVideoPreviewLayer 를 사용했던 것과 달리 이번에는 AVCaptureVideoDataOutputSampleBufferDelegate 프로토콜을 채택해서 사용해야 합니다.

      • AVCaptureVideoDataOutputSampleBufferDelegate 는 video data output 에서 sample buffer를 받아오며, 받아오는 video data output의 상태를 감시하는 메쏘드들을 가지고 있는 프로토콜입니다. 이 프로토콜에 있는 메쏘드들은 필수적으로 구현해야 하는 것은 아닙니다.
      • Sample buffer의 행동을 조작하는 method는 두가지가 있습니다.

        • func captureOutput(:didOutput:from:)
        • func captureOutput(:didDrop:from:)
      • 실시간 필터 적용되는 preview를 구현하기 위해 필요한 메쏘드는 func captureOutput(:didOutput:from:) 입니다.

      지난번 시간에서 해왔듯이 AVCaptureSession을 이용해야 합니다.

      추가된 점

      • 실시간 필터를 구현에 있어서 AVCaptureVideoPreviewLayer 를 사용하지 않을 것이기 때문에 AVCaptureVideoDataOutput 을 AVCaptureSession에 추가해야 합니다.
    • CIContext 를 사용해야 합니다. 코어 이미지 모든 프로세싱이 일어나는 부분으로 CIFilter를 사용시에 반드시 필요합니다.

    • func captureOutput(:didOutput:from:) 구현 부분 설명

      1. 첫 번째 인자에는 AVCaptureOutput 하위 클래스가 들어옵니다. 여기서는 아마 AVCaptureVideoDataOutput 가 들어올 거라고 생각됩니다. 그리고 AVCaptureOutput 은 최상위 추상 클래스입니다. 간단히 말하자면 AVCaptureSession의 addOutput에 인자가 될 수 있는 클래스들의 부모 클래스(AVCaptureVideoDataOutput, AVCapturePhotoOutput 등)입니다.

      2. 두 번째 인자는 CMSampleBuffer 클래스의 인스턴스 입니다. 여기서 CM은 apple의 CoreMedia framework를 말합니다. CMSampleBuffer는 변경할 수 없는 CMSampleBufferRef 객체에 대한 참조입니다. CMSampleBuffer 는 압축되어 있거나 압축되어 있지 않은 특정 미디어 타입(audio, video 등)의 samples 를 가지고 있습니다.

      3. 세 번째 인자로는 AVCaptureConnection 클래스 타입이 변수로 들어옵니다. AVCaptureSession에 있는 특정한 input 과 output 사이의 연결이라고 합니다. 다만 addInput(_:)addOutput(_:) 을 사용했다면 자동으로 모든 input 과 output에 연결이 생성된다고 합니다.

      4. 두 번째인자 sampleBuffer 로부터 CMSampleBufferGetImageBuffer(_:)를 이용해서 CVImageBuffer를 받아옵니다.

      5. CIFilter 를 이용해서 이미 구현된 필터를 불러옵니다. 이미 구현된 filter 들은

        Core Image Filter Reference 에서 확인할 수 있습니다.

      6. 이제는 CIFilter를 통과할 CIImage가 필요합니다. CIImage 클래스는 Core Image filter 들을 거칠 또는 생산된 image를 말합니다. 상당히 많은 init이 존재하는 데
        init(cvImageBuffer:) 를 이용합니다.

      7. CIComicEffect filter를 통과한 이미지를 얻기 위해서 CIContext의 createCGImage(_:from:) 함수를 이용해서 CGImage를 생성합니다. 첫 번째 인자는 filter를 통과할 CIImage 객체이고, 두 번째 인자는 CGImage가 그려질 영역으로 CGRect 객체 입니다.

        1. func captureOutput(_:didOutput:from:) 부분의 코드입니다.
          func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
           guard let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let _ = CMSampleBufferGetFormatDescription(sampleBuffer) else {return}
        
           let comicEffect = CIFilter(name: "CIPhotoEffectMono")
           let cameraImage = CIImage(cvImageBuffer: videoPixelBuffer)
        
           // setValue 부분은 잘 이해가 되지 않습니다.
           comicEffect!.setValue(cameraImage, forKey: kCIInputImageKey)
        
           let cgImage = self.context.createCGImage(comicEffect!.outputImage!, from: cameraImage.extent)!
        
           DispatchQueue.main.async {
               let filteredImage = UIImage(cgImage: cgImage)
               self.imageView.image = filteredImage
           }
         }
        
        

      참조 링크

      이해 되지 않는 부분

      1. func captureOutput(_:didOutput:from) 에서 commicEffect!.setValue 부분의 의미가 무엇인지 궁금합니다.

      2. func captureOutput(_:didOutput:from) 에서 DispatchQueue 코드의 의미는 UI 변경에 관한 코드라서 main thread 에서 실행해서 해야하는 걸로 생각하고 있는 데 맞게 생각하는 건지 궁금합니다.

      전체 코드입니다.


      import UIKit import AVFoundation // CIFilter(CIComicFilter) 실시간 적용하기 class ViewController: UIViewController{ @IBOutlet weak var imageView: UIImageView! var captureSession: AVCaptureSession? var backCamera: AVCaptureDevice? var frontCamera: AVCaptureDevice? var captureInput: AVCaptureInput? var photoOutput: AVCapturePhotoOutput? var photoSetting: AVCapturePhotoSettings? var videoOutput: AVCaptureVideoDataOutput? var orientation: AVCaptureVideoOrientation = .portrait let context = CIContext() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.setAuthorization() self.setSession() self.setDevice() self.setInputOutput() self.imageView.transform = self.imageView.transform.rotated(by: .pi / 2) } func setAuthorization() { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .authorized: print("Get Authorization Success") case .denied: AVCaptureDevice.requestAccess(for: .video) { access in if access { DispatchQueue.main.async { self.setInputOutput() } } } default: print("Get Authorization failed") } } func setSession(){ self.captureSession = AVCaptureSession() self.captureSession?.sessionPreset = .photo } func setDevice(){ self.backCamera = AVCaptureDevice.default(for: .video) // or using default(_:for:position:) // self.backCamera = AVCaptureDevice.default(.builtInDualWideCamera, for: .video, position: .back) } func setInputOutput(){ guard let captureDevice = self.backCamera else {return} do{ // Set photoInput self.captureInput = try AVCaptureDeviceInput(device: captureDevice) // Set photoOutput self.photoOutput = AVCapturePhotoOutput() self.photoOutput?.isHighResolutionCaptureEnabled = true // Set videoDataOutput self.videoOutput = AVCaptureVideoDataOutput() self.videoOutput?.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_32BGRA)] self.videoOutput?.setSampleBufferDelegate(self, queue: DispatchQueue.main) guard let photoinput = self.captureInput else {return} guard let photoOutput = self.photoOutput else {return} guard let videoOutput = self.videoOutput else {return} self.captureSession?.addInput(photoinput) self.captureSession?.addOutput(photoOutput) self.captureSession?.addOutput(videoOutput) self.captureSession?.startRunning() }catch{ print(error.localizedDescription) } } } extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let _ = CMSampleBufferGetFormatDescription(sampleBuffer) else {return} let comicEffect = CIFilter(name: "CIComicEffect") let cameraImage = CIImage(cvImageBuffer: videoPixelBuffer) comicEffect!.setValue(cameraImage, forKey: kCIInputImageKey) let cgImage = self.context.createCGImage(comicEffect!.outputImage!, from: cameraImage.extent)! DispatchQueue.main.async { let filteredImage = UIImage(cgImage: cgImage) self.imageView.image = filteredImage } } }
      • 이 게시글은 광현에 의해 4 years, 5 months 전에 수정됐습니다.
      • 이 게시글은 광현에 의해 4 years, 5 months 전에 수정됐습니다. 이유: 오타 및 소제목 수정.수정
  • 광현
    참가자
    • 글작성 : 15
    • 답글작성 : 26

    atom editor 에서 md 로 만든 후에 미리보기로 보면서 작성한 다음에 복사했는데 이쁘게 들어가지는 않네요..ㅠㅠ

    • 야곰
      키 마스터
      • 글작성 : 37
      • 답글작성 : 579

      아녜요 잘 작성해 주셨는데, 웹페이지 코드가 좀 문제가 있는것 같아요. +_+ 좋은 내용 고맙습니다!

  • 1 답변 글타래를 보이고 있습니다
    • 답변은 로그인 후 가능합니다.

    logo landscape small

    사업자번호 : 743-81-02195
    통신판매업 신고번호 : 제 2022-충북청주-1278 호
    고객센터 : 카카오톡채널 @yagom