読者です 読者をやめる 読者になる 読者になる

Swift + OpenCVでリアルタイムに顔認識してみた1

iPhoneのカメラを使って、リアルタイムに顔認識する機能を調べてみました。

まずはカメラで撮影し、それを画面に表示させるところまで作ります。
その後にカメラから入ってきた画像データをOpenCVでごにょごにょして顔認識を行います。

プロジェクト作成

新しいSingle View Applicationのプロジェクトを作成します。
カメラを使うので実機で動作確認する必要がありますので、そのための必要な設定を行っておきます。

OpenCVインストール

OpenCVはCocoaPodsを使ってインストールします。 一旦プロジェクトを閉じてターミナルを実行し、プロジェクトのディレクトリへ移動します。
「Podfile」ファイルを作成し、次の通りに記述します。

platform :ios, "7.0"
pod 'OpenCV'

ターミナルでpod installとすると、OpenCVがインストールされ、.xcworkspaceファイルが作成されます。 Xcodeで.xcworkspaceファイルを開き、ここからプログラムを書いていきます。 f:id:gibachan03:20141019175317p:plain

UI

StoryBoardでImageViewを作成し、ViewController.swiftにOutletを作成します。 f:id:gibachan03:20141019185908p:plain

カメラから画像データを取得

ViewController.swiftにカメラで動画を処理するコードを記述します。

ViewController.swift

//
//  ViewController.swift
//  OpenCVSample
//
//  Created by gibachan on 2014/10/19.
//  Copyright (c) 2014年 gibachan. All rights reserved.
//

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {

    @IBOutlet weak var imageView: UIImageView!

    // セッション
    var mySession : AVCaptureSession!
    // カメラデバイス
    var myDevice : AVCaptureDevice!
    // 出力先
    var myOutput : AVCaptureVideoDataOutput!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // カメラを準備
        if initCamera() {
            // 撮影開始
            mySession.startRunning()
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // カメラの準備処理
    func initCamera() -> Bool {
        // セッションの作成.
        mySession = AVCaptureSession()
        
        // 解像度の指定.
        mySession.sessionPreset = AVCaptureSessionPresetMedium
        
        
        // デバイス一覧の取得.
        let devices = AVCaptureDevice.devices()
        
        // バックカメラをmyDeviceに格納.
        for device in devices {
            if(device.position == AVCaptureDevicePosition.Back){
                myDevice = device as AVCaptureDevice
            }
        }
        if myDevice == nil {
            return false
        }
        
        // バックカメラからVideoInputを取得.
        let myInput = AVCaptureDeviceInput.deviceInputWithDevice(myDevice, error: nil) as AVCaptureDeviceInput
        
        
        // セッションに追加.
        if mySession.canAddInput(myInput) {
            mySession.addInput(myInput)
        } else {
            return false
        }
        
        // 出力先を設定
        myOutput = AVCaptureVideoDataOutput()
        myOutput.videoSettings = [ kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA ]
        
        // FPSを設定
        var lockError: NSError?
        if myDevice.lockForConfiguration(&lockError) {
            if let error = lockError {
                println("lock error: \(error.localizedDescription)")
                return false
            } else {
                myDevice.activeVideoMinFrameDuration = CMTimeMake(1, 15)
                myDevice.unlockForConfiguration()
            }
        }
        
        // デリゲートを設定
        let queue: dispatch_queue_t = dispatch_queue_create("myqueue",  nil)
        myOutput.setSampleBufferDelegate(self, queue: queue)

        
        // 遅れてきたフレームは無視する
        myOutput.alwaysDiscardsLateVideoFrames = true
        
        
        // セッションに追加.
        if mySession.canAddOutput(myOutput) {
            mySession.addOutput(myOutput)
        } else {
            return false
        }
        
        // カメラの向きを合わせる
        for connection in myOutput.connections {
            if let conn = connection as? AVCaptureConnection {
                if conn.supportsVideoOrientation {
                    conn.videoOrientation = AVCaptureVideoOrientation.Portrait
                }
            }
        }
 
        return true
    }


    // 毎フレーム実行される処理
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)
    {
        dispatch_async(dispatch_get_main_queue(), {

            /**
             *  ここでSampleBufferからUIImageを作成し、imageViewへ反映させる
             */

        })
    }
    

}

このようにすると、カメラで撮影しているデータが毎フレームcaptureOutputメソッドへ送られます。(引数のsampleBuffer)

画像データを表示

次に、sampleBufferをUIImageへ変換します。ここでは新しいクラスを作成してその中に処理を書きます。

CameraUtil.swift

//
//  CameraUtil.swift
//  OpenCVSample
//
//  Created by gibachan on 2014/10/19.
//  Copyright (c) 2014年 gibachan. All rights reserved.
//

import Foundation
import UIKit
import AVFoundation

class CameraUtil {
    
    // sampleBufferからUIImageへ変換
    class func imageFromSampleBuffer(sampleBuffer: CMSampleBufferRef) -> UIImage {
        let imageBuffer: CVImageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)
        
        // ベースアドレスをロック
        CVPixelBufferLockBaseAddress(imageBuffer, 0)
        
        // 画像データの情報を取得
        let baseAddress: UnsafeMutablePointer<Void> = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, UInt(0))
        
        let bytesPerRow: UInt = CVPixelBufferGetBytesPerRow(imageBuffer)
        let width: UInt = CVPixelBufferGetWidth(imageBuffer)
        let height: UInt = CVPixelBufferGetHeight(imageBuffer)
        
        // RGB色空間を作成
        let colorSpace: CGColorSpaceRef = CGColorSpaceCreateDeviceRGB()
        
        // Bitmap graphic contextを作成
        let bitsPerCompornent: UInt = 8
        var bitmapInfo = CGBitmapInfo((CGBitmapInfo.ByteOrder32Little.toRaw() | CGImageAlphaInfo.PremultipliedFirst.toRaw()) as UInt32)
        let newContext: CGContextRef = CGBitmapContextCreate(baseAddress, width, height, bitsPerCompornent, bytesPerRow, colorSpace, bitmapInfo) as CGContextRef
        
        // Quartz imageを作成
        let imageRef: CGImageRef = CGBitmapContextCreateImage(newContext)
        
        // UIImageを作成
        let resultImage: UIImage = UIImage(CGImage: imageRef)
        
        return resultImage
    }

}

そして、変換したUIImageをUIImageViewへ表示してあげれば撮影できている様子が確認できます。

ViewController.swift

    // 毎フレーム実行される処理
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)
    {
        dispatch_async(dispatch_get_main_queue(), {
            // UIImageへ変換して表示させる
            self.imageView.image = CameraUtil.imageFromSampleBuffer(sampleBuffer)
        })
    }

参考

AV Foundation プログラミングガイド

長くなって来たで今回はここまでです。
これだけでも画像をいろいろいじって遊べそうですね!
次回にOpenCVを使って顔認識を試してみます。