vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

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を使うときの問題として、取得できる画像が横向き、というものがあります。デバイスの向きを取得して、それに合わせて設定してやる方が良いと思いますが、簡略化のためにどの場合でも右向きに表示しています。

参考