[iOS] AVFoundation+OpenCVで劇画調カメラを作ってみた
1 はじめに
AVFoundationで、AVCaptureVideoDataOutputを出力に指定すると、1/30フレームなどの画像を取得可能です。
今回は、取得した画像データをフィルターで白黒の2値画像にすることで、劇画調の写真を撮るカメラを作成してみました。
iOSでフィルターを掛ける方法は、CIFilterなど、方法が色々ありますが、今回は、比較的自由に調整が可能なOpenCV(Ver3.x)を使用しました。
AVFoundationを使用する場合の共通的な処理については、下記に纏めましたので、是非御覧ください。 [iOS] AVFundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた
2 入出力
劇画調カメラは、AVCaptudeSessionの入力として、カメラ、出力に、動画データであるAVCaptureVideoDataOutputを繋ぎます。 そして、この出力をプレビュー(UIImageVIew)に表示する前に、OpenCVでフィルターを掛けています。
下記のコードは、AVCaptureSessionを生成して、上記のとおり入出力をセットしている例です。
<br />// セッションのインスタンス生成 let captureSession = AVCaptureSession() // 入力(背面カメラ) let videoDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) let videoInput = try! AVCaptureDeviceInput.init(device: videoDevice) captureSession.addInput(videoInput) // 出力(映像) let videoDataOutput = AVCaptureVideoDataOutput() captureSession.addOutput(videoDataOutput)
3 AVCaptureVideoDataOutputのデータ
AVCaptureVideoDataOutputでは、AVCaptureVideoDataOutputSampleBufferDelegateプロトコルを実装したクラスのcaptureOutput(_:didOutputMetadataObjects:from:)でフレーム毎のデータを受け取ることが出来ます。
今回は、ここで受け取ったデータを、一旦UIImageに変換してから、OpenCVでフィルターを掛け、それをプレビュー画面であるUIImageViewにに表示しています。(AVCaptureSessionにプレビューは設定していません)
// 新しいキャプチャの追加で呼ばれる func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { // UIImageへの変換 let image = imageFromSampleBuffer(sampleBuffer: sampleBuffer) // OpenCVでフィルタしてプレビューに表示 imageView.image = openCv.filter(image) } func imageFromSampleBuffer(sampleBuffer :CMSampleBuffer) -> UIImage { let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! // イメージバッファのロック CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) // 画像情報を取得 let base = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)! let bytesPerRow = UInt(CVPixelBufferGetBytesPerRow(imageBuffer)) let width = UInt(CVPixelBufferGetWidth(imageBuffer)) let height = UInt(CVPixelBufferGetHeight(imageBuffer)) // ビットマップコンテキスト作成 let colorSpace = CGColorSpaceCreateDeviceRGB() let bitsPerCompornent = 8 let bitmapInfo = CGBitmapInfo(rawValue: (CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) as UInt32) let newContext = CGContext(data: base, width: Int(width), height: Int(height), bitsPerComponent: Int(bitsPerCompornent), bytesPerRow: Int(bytesPerRow), space: colorSpace, bitmapInfo: bitmapInfo.rawValue)! as CGContext // 画像作成 let imageRef = newContext.makeImage()! let image = UIImage(cgImage: imageRef, scale: 1.0, orientation: UIImageOrientation.right) // イメージバッファのアンロック CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0)) return image }
4 OpenCV
(1) CocoaPods
OpenCVは、CococaPodsでインストール可能です。
Podfile
pod 'OpenCV'
2017.02.19現在の最新は、3.1.0.1です。
(2) Bridgingファイルの作成
C++のライブラリであるOpenCVをSwiftから使用するには、Bridgingファイルが必要です。今回は、SwiftのプロジェクトにObjective-Cのファイルを追加した際に起動する自動生成を利用しました。
File > New > File から Objective-C Fileを選択し、Objective-Cのファイルを追加します。(ここでは名前をOpenCv.mとしました)
ここで表示されるポップアップでCreate Bridging Headerを選択します。
Bridgingファイルは、プロジェクト名-Bridging-Header.hという名前で生成されます。 その後、ファイルの拡張子を.mmに変更しています。(OpenCv.mm)
(3) フィルタークラスの実装
作成した、OpenCVによるフィルターの実装は、次のようなものです。
最初に、受け取ったUIImageの縦横を調整した後、Mat形式に変換してグレースケールにしています。 (グレースケールにしている理由は、以降のフィルターがグレースケールにしか適用できないためです。)
フィルタの種類は、「GaussianBlur」「treshold」「adaptiveThreshold」の3種類です。
アプリからの設定値に基づいてフィルターを掛け、最終的にUIImageに戻しています。
OpenCv.mm
@implementation OpenCv : NSObject -(UIImage *)Filter:(UIImage *)image { // 縦横を修正 UIGraphicsBeginImageContext(image.size); [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); //UIImageをcv::Matに変換 cv::Mat mat; UIImageToMat(image, mat); //グレースケールに変更 cv::cvtColor(mat,mat,CV_BGR2GRAY); //Blur ぼかし if(_useBlur) { // kSizeは奇数のみ int kSize = _blur0; if(kSize % 2 == 0) { kSize += 1; } cv::GaussianBlur(mat, mat, cv::Size(kSize,kSize), _blur1); } // 閾値 if(_useTreshold) { cv::threshold(mat, mat, 0, 255, cv::THRESH_BINARY|cv::THRESH_OTSU); } // 適応閾値 if(_useAdaptiveTreshold) { // blockSizeは奇数のみ int blockSize = _adaptiveThreshold0; if(blockSize % 2 == 0) { blockSize += 1; } cv::adaptiveThreshold(mat, mat, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, blockSize, _adaptiveThreshold1); } return MatToUIImage(mat); } @end
これをSwiftから利用するためのBridgingファイルは、以下のとおりです。
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface OpenCv : NSObject - (UIImage *)Filter:(UIImage *)image; @property bool useBlur; @property int blur0; @property int blur1; @property bool useTreshold; @property bool useAdaptiveTreshold; @property int adaptiveThreshold0; @property int adaptiveThreshold1; @end
Swiftから利用する場合は、ファンクション名が、lower camel caseになっている事に注意が必要です。
imageView.image = openCv.filter(image)
@IBAction func changeBlurSwitch(_ sender: UISwitch) { openCv.useBlur = sender.isOn blur0Slider.isEnabled = sender.isOn blur1Slider.isEnabled = sender.isOn } @IBAction func changeBlur0Slider(_ sender: UISlider) { openCv.blur0 = Int32(sender.value) }
5 動作確認
サンプルアプリを実行している様子です。
プレビュー画面をタップすると、フィルターの設定値を変更するビューが表示されます。ここでの変更は、リアルタイムで画面で確認できます。 画面下部のシャッターを押すと、その時のUIImageが、カメラロールに保存されます。
6 最後に
今回は、OpneCVでリアルタイムにフィルターを掛けることで、色々な設定値を試しながら、劇画調の写真を撮影するカメラを作成してみました。 OpenCVを利用することで、画像を自由な操作できますので、色々面白そうなものが作れそうな予感がします。
コードは下記に置いています。気になるところが有りましたら、ぜひ教えてやってください。
[GitHub] https://github.com/furuya02/GekigaCamera
参考リンク
OpenCVを利用したリアルタイムフィルタリングの基本
API Reference AVCaptureVideoDataOutput
[iOS] AVFundationを使用して、「ビデオ録画」や「連写カメラ」や「QRコードリーダー」や「バーコードリーダー」を作ってみた