[Swift]iPhoneのカメラに映ってる画像にカスタムフィルタかけてみる(前編)
はじめに
iPhoneのカメラに写っている画像にリアルタイムでフィルタをかけてみたいと思います。 長くなったので2回に分け、前半はカメラ機能、後半はフィルタ部分を記載します。 今回は、AVCaptureVideoDataOutputを使用し、カメラに映った画像をiPhoneの画面に表示するまでになります。
実装
プロジェクトはSingleViewApplicationで作成しました。Storyboardは使用してません。 また、カメラ機能を使用するので、実機で動かす必要があります。
ソースコード
import UIKit import GLKit import AVFoundation class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate { var videoDisplayView: GLKView! var videoDisplayViewRect: CGRect! var renderContext: CIContext! var cpsSession: AVCaptureSession! override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(animated: Bool) { //画面の生成 self.initDisplay() // カメラの使用準備. self.initCamera() } override func viewDidDisappear(animated: Bool) { // カメラの停止とメモリ解放. self.cpsSession.stopRunning() for output in self.cpsSession.outputs { self.cpsSession.removeOutput(output as AVCaptureOutput) } for input in self.cpsSession.inputs { self.cpsSession.removeInput(input as AVCaptureInput) } self.cpsSession = nil } func initDisplay() { videoDisplayView = GLKView(frame: view.bounds, context: EAGLContext(API: .OpenGLES2)) videoDisplayView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2)) videoDisplayView.frame = view.bounds view.addSubview(videoDisplayView) renderContext = CIContext(EAGLContext: videoDisplayView.context) videoDisplayView.bindDrawable() videoDisplayViewRect = CGRect(x: 0, y: 0, width: videoDisplayView.drawableWidth, height: videoDisplayView.drawableHeight) } func initCamera() { //カメラからの入力を作成 var device: AVCaptureDevice! //背面カメラの検索 for device: AnyObject in AVCaptureDevice.devices() { if device.position == AVCaptureDevicePosition.Back { device = device as AVCaptureDevice } } //入力データの取得 var deviceInput: AVCaptureDeviceInput = AVCaptureDeviceInput.deviceInputWithDevice(device, error: nil) as AVCaptureDeviceInput //出力データの取得 var videoDataOutput:AVCaptureVideoDataOutput = AVCaptureVideoDataOutput() //カラーチャンネルの設定 videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey : kCVPixelFormatType_32BGRA] //画像をキャプチャするキューを指定 videoDataOutput.setSampleBufferDelegate(self, queue: dispatch_get_main_queue()) //キューがブロックされているときに新しいフレームが来たら削除 videoDataOutput.alwaysDiscardsLateVideoFrames = true //セッションの使用準備 self.cpsSession = AVCaptureSession() //Input if(self.cpsSession.canAddInput(deviceInput)) { self.cpsSession.addInput(deviceInput as AVCaptureDeviceInput) } //Output if(self.cpsSession.canAddOutput(videoDataOutput)) { self.cpsSession.addOutput(videoDataOutput) } //解像度の指定 self.cpsSession.sessionPreset = AVCaptureSessionPresetMedium self.cpsSession.startRunning() } func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) { //SampleBufferから画像を取得 let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) let opaqueBuffer = Unmanaged<CVImageBuffer>.passUnretained(imageBuffer).toOpaque() let pixelBuffer = Unmanaged<CVPixelBuffer>.fromOpaque(opaqueBuffer).takeUnretainedValue() let outputImage = CIImage(CVPixelBuffer: pixelBuffer, options: nil) //補正 var drawFrame = outputImage.extent() let imageAR = drawFrame.width / drawFrame.height let viewAR = videoDisplayViewRect.width / videoDisplayViewRect.height if imageAR > viewAR { drawFrame.origin.x += (drawFrame.width - drawFrame.height * viewAR) / 2.0 drawFrame.size.width = drawFrame.height / viewAR } else { drawFrame.origin.y += (drawFrame.height - drawFrame.width / viewAR) / 2.0 drawFrame.size.height = drawFrame.width / viewAR } //出力 videoDisplayView.bindDrawable() if videoDisplayView.context != EAGLContext.currentContext() { EAGLContext.setCurrentContext(videoDisplayView.context) } renderContext.drawImage(outputImage, inRect: videoDisplayViewRect, fromRect: drawFrame) videoDisplayView.display() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
func initDisplay()で表示用の画面を生成します。 videoDisplayViewRectにvideoDisplayViewのrectを保持しているのは出力時の補正で使用するためです。
func initCamera()でカメラの設定をします。 今回は背面カメラ(デフォルト)を使用してますが、前面カメラを使用する時は、 57行目のAVCaptureDevicePosition.Backの部分をAVCaptureDevicePosition.Frontに変更すればOKです。
self.cpsSession.sessionPresetで設定できる解像度は下記の種類があります。
AVCaptureSessionPresetPhoto | 高解像度の写真画質の出力に適した設定 |
AVCaptureSessionPresetHigh | 高品質のビデオとオーディオ出力に適した設定 |
AVCaptureSessionPresetMedium | ビデオ出力をWiFi経由で共有するために適した設定 |
AVCaptureSessionPresetLow | ビデオ出力を3G経由で共有するために適した設定 |
AVCaptureSessionPreset320x240 | 320×240ピクセルのビデオ出力 |
AVCaptureSessionPreset352x288 | CIF品質(352×288ピクセル)の映像出力 |
AVCaptureSessionPreset640x480 | VGA画質(640×480ピクセル)の映像出力 |
AVCaptureSessionPreset960x540 | HD画質(960x540のピクセル)の映像出力 |
AVCaptureSessionPreset1280x720 | 720pの画質(1280×720ピクセル)の映像出力 |
AVCaptureSessionPresetiFrame960x540 | 960x540のiFrame H.264ビデオ ※編集アプリケーションに最適 |
AVCaptureSessionPresetiFrame1280x720 | 1280×720iFrame H.264ビデオ ※編集アプリケーションに最適 |
func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)でキャプチャされたデータを変換して補正して表示します。 今回はそのまま表示しますが、後編では出力前にフィルターをかけてみたいと思います。