Swiftでカメラを使う
OpenCVを使ってリアルタイムに画像編集をしたい。
ということで、まずはiOSでカメラを使ってみたメモ。
はじめに
OpenCVには「CvVideoCamera」があり、以下のページのように書けばとりあえず実現できます。
ただ、細かいところがイマイチわからなかったのと、Deprecatedの警告がたくさん表示されたので見送りとしました
(Xcode6、iOS8、OpenCV3.0Alphaで確認)。
OpenCV iOS - Video Processing — OpenCV 2.4.9.0 documentation
結局、AVFoundationを使用して画面キャプチャ(UIImage)を取得することとしました。
せっかくなのでSwiftを使って。
※UIImageに変換する部分やデバイスの向きに合わせて画像の向きを変更する部分など、未完成の部分もありますが現状でまとめてみます。
ViewController.swift
import UIKit
import AVFoundation
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate
{
@IBOutlet weak var imgCameraView: UIImageView!
var cpsSession: AVCaptureSession!
var imcImageController: ImageController!
override func viewDidLoad()
{
super.viewDidLoad()
imcImageController = ImageController()
}
override func viewWillAppear(animated: Bool)
{
// カメラの使用準備.
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
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
func initCamera()
{
var cpdCaptureDevice: AVCaptureDevice!
// 背面カメラの検索.
for device: AnyObject in AVCaptureDevice.devices()
{
if device.position == AVCaptureDevicePosition.Back
{
cpdCaptureDevice = device as AVCaptureDevice
}
}
// カメラが見つからなければリターン.
if (cpdCaptureDevice == nil) {
println("Camera couldn't found")
return
}
// FPSの設定.
cpdCaptureDevice.activeVideoMinFrameDuration = CMTimeMake(1, 30)
// 入力データの取得. 背面カメラを設定する.
var deviceInput: AVCaptureDeviceInput = AVCaptureDeviceInput.deviceInputWithDevice(cpdCaptureDevice, error: nil) as AVCaptureDeviceInput
// 出力データの取得.
var videoDataOutput:AVCaptureVideoDataOutput = AVCaptureVideoDataOutput()
// カラーチャンネルの設定.
let dctPixelFormatType : Dictionary = [kCVPixelBufferPixelFormatTypeKey : kCVPixelFormatType_32BGRA]
videoDataOutput.videoSettings = dctPixelFormatType
// デリゲート、画像をキャプチャするキューを指定.
videoDataOutput.setSampleBufferDelegate(self, queue: dispatch_get_main_queue())
// キューがブロックされているときに新しいフレームが来たら削除.
videoDataOutput.alwaysDiscardsLateVideoFrames = true
// セッションの使用準備.
self.cpsSession = AVCaptureSession()
// Input、Outputの追加.
if(self.cpsSession.canAddInput(deviceInput))
{
self.cpsSession.addInput(deviceInput as AVCaptureDeviceInput)
}
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から画像を取得してUIImageViewにセット.
imgCameraView.image = imcImageController.createImageFromBuffer(sampleBuffer)
}
}
クラスでプロトコル「AVCaptureVideoDataOutputSampleBufferDelegate」を実装しておき、「AVCaptureVideoDataOutput」で、デリゲートにselfをセットすると「captureOutput」が毎フレーム実行されます。
引数として渡される「CMSampleBuffer」からUIImageを取得するのですが、ここで使用されている「kCGBitmapByteOrder32Little」のSwiftへの置き換え方法がわからず、Objective-Cで書くことにしています。 (「kCGImageAlphaPremultipliedFirst」は、このページを見ると「CGBitmapInfo(CGImageAlphaInfo.PremultipliedFirst.toRaw()」に置き換えられそうです)
ocvCameraImage-Bridging-Header.h
#import < Foundation / Foundation.h > #import < UIKit / UIKit.h > #import < AVFoundation / AVFoundation.h > @interface ImageController : NSObject - (UIImage *) createImageFromBuffer:(CMSampleBufferRef) sbrBuffer; @end
ImageController.mm
#import "ocvCameraImage-Bridging-Header.h" #import < opencv2 / opencv.hpp > #import " opencv2 / imgcodecs / ios.h " @interface ImageController() @property (nonatomic)CVImageBufferRef ibrImageBuffer; @property (nonatomic)CGColorSpaceRef csrColorSpace; @property (nonatomic)uint8_t *baseAddress; @property (nonatomic)size_t sztBytesPerRow; @property (nonatomic)size_t sztWidth; @property (nonatomic)size_t sztHeight; @property (nonatomic)CGContextRef cnrContext; @property (nonatomic)CGImageRef imrImage; @end @implementation ImageController - (UIImage *) createImageFromBuffer:(CMSampleBufferRef) sbrBuffer { // CVImageBufferRefへの変換. _ibrImageBuffer = CMSampleBufferGetImageBuffer(sbrBuffer); // ピクセルバッファのベースアドレスをロックする. CVPixelBufferLockBaseAddress(_ibrImageBuffer, 0); // ベースアドレスの取得. _baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(_ibrImageBuffer, 0); _sztBytesPerRow = CVPixelBufferGetBytesPerRow(_ibrImageBuffer); // サイズの取得. _sztWidth = CVPixelBufferGetWidth(_ibrImageBuffer); _sztHeight = CVPixelBufferGetHeight(_ibrImageBuffer); // RGBの色空間. _csrColorSpace = CGColorSpaceCreateDeviceRGB(); // ビットマップグラフィックスコンテキストの生成. _cnrContext = CGBitmapContextCreate(_baseAddress , _sztWidth , _sztHeight , 8 , _sztBytesPerRow , _csrColorSpace , kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); // ビットマップグラフィックスコンテキストからCGImageRefの生成. _imrImage = CGBitmapContextCreateImage(_cnrContext); // CGImageRefからUIImageを生成. // 一旦デバイスの向きに関わらず画像を右向きに表示. _imgCreatedImage = [UIImage imageWithCGImage:_imrImage scale:1.0f orientation:UIImageOrientationRight]; // 解放 CGImageRelease(_imrImage); CGContextRelease(_cnrContext); CGColorSpaceRelease(_csrColorSpace); // ベースアドレスのロックを解除 CVPixelBufferUnlockBaseAddress(_ibrImageBuffer, 0); return _imgCreatedImage; } @end
- AVFoundationを使うときの問題として、取得できる画像が横向き、というものがあります。デバイスの向きを取得して、それに合わせて設定してやる方が良いと思いますが、簡略化のためにどの場合でも右向きに表示しています。
参考
- 実践! iPhoneアプリ開発 (41) ビデオカメラアプリの作り方(2) - ビデオ画像の表示 | マイナビニュース
- SwiftでAVFoundationを使ったカメラアプリのサンプル - Qiita
- AVFoundationを使ったカメラ機能はちゃんとメモリを開放しましょう - PILOG
- ios - Alternatives to deprecated AVCaptureConnection frame-duration properties? - Stack Overflow
- OpenCVを利用したリアルタイムフィルタリングの基本 - Developers.IO
- 【Objective-CとSwift比較】※今後追加予定 - Qiita
- お手軽iPhoneアプリ開発メモ AVCaptureVideoDataOutput メモ_1