この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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コードリーダー」や「バーコードリーダー」を作ってみた