この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
iPhoneのカメラに写っている画像にリアルタイムでフィルタをかけてみたいと思います。 長くなったので2回に分け、前半はカメラ機能、後半はフィルタ部分を記載します。 今回は、AVCaptureVideoDataOutputを使用し、カメラに映った画像をiPhoneの画面に表示するまでになります。
実装
プロジェクトはSingleViewApplicationで作成しました。Storyboardは使用してません。 また、カメラ機能を使用するので、実機で動かす必要があります。
ソースコード
ViewController.swift
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!)でキャプチャされたデータを変換して補正して表示します。 今回はそのまま表示しますが、後編では出力前にフィルターをかけてみたいと思います。