ON THE HAND

引っ越しました http://tech.andhandworks.com

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

このエントリーをはてなブックマークに追加

  1. --/--/--(--) --:--:--|
  2. スポンサー広告

iOS swift で OpenCV を使う

最近は Apple に Developer 登録しなくても自作 iOS アプリを実機で動かせるようになっています。
しかも、どうやら Xcode で OpenCV が使えるようです。

という訳で、ふと思い立ったので OpenCV を使った顔検出アプリを作ってみます。
・・・と思ったけど、顔認識の説明が長くなるのでとりあえずソーベルエッジ。

使う機材

  • Mac
    ご家庭で余っているMacを使います。
  • iPhone または iPad
    どこのご家庭にも2,3台あると思います。
  • Xcode
    iOS アプリの開発環境です。
    開発だけなら無料です。凄いです。
    AppStore からインストール。
  • OpenCV
    画像処理界のグローバルスタンダード。 今回の方法だといまのところ 3.0.0 が入るようです。

Cocoapods をインストール

OpenCV 単独であればライブラリを直接放り込んでも使えるようですが、
せっかく Cocoapods というパッケージ管理ツールがあるので使ってみます。
まずはターミナルを起動。
これ。
WS000000.jpg
インストールしようとすると依存関係で何やら大量に入りますが
エラーが出ていないなら気にしないことにします。
iMac:~ $ sudo gem install cocoapods
Fetching: i18n-0.7.0gem (100%)
Successfully installed i18n-0.7.0
   : (中略)
Parsing documentation for cocoapods-1.0.1
Installing ri documentation for cocoapods-1.0.1
23 gems installed
iMac:~ $ pod setup

Project を作成

Xcode 上でアプリを開発する際、各アプリのソースコード等が入ったデータ一式を Project と言います。
OpenCV は Project 毎にインストールするので先に Project を作成します。

このアイコンから Xcode を起動して
Create a new Xcode Project を選択します。
WS000001.jpg

とりあえず Single View Application で。
WS000002.jpg

アプリ名などは適当に。
WS000004.jpg

Projectが出来ました。
アプリを公開するときは細かく設定する必要がありますが今回は試しに動かすだけなのでデフォルトのままで。
WS000005.jpg

OpenCV のインストール

一旦 Xcode の Project を閉じます。
再びターミナルに戻って Project の xcodeproj が入っているディレクトリ内で Podfile を作り、OpenCV をインストールします。
対応する iOS のバージョンは 7.0 以上にしました。
6.x 以下に対応させようとすると泥沼化します。
iMac: ~ $ cd FaceRecogApp
iMac:FaceRecogApp $ vi Podfile
iMac:FaceRecogApp $ cat Podfile
target "FaceRecogApp" do
  echo 'platform :ios, "7.0"
  pod 'OpenCV' > Podfile
end
iMac:FaceRecogApp $ pod install

結構時間が掛かりますがエラーなく終了すればインストール完了。
xcworkspace というファイルをダブルクリックして Xcode で開きます。
WS000006.jpg

アプリでビデオキャプチャする

よくある iOS アプリの Hello World では「Storyboard で~」という感じで始まりますが、
iOS アプリの経験がない人間が Storyboard をいじり始めると余計に訳わからなくなるので
いきなりコードを書いていきます。

Xcode 上でアプリの Project 内に ViewController.swift というファイルがあるので開きます。
ここにこんな感じでガシガシ書いていくと、とりあえずカメラプレビューできるアプリが出来上がります。
コードの意味はコメントを参照。
import UIKit
import AVFoundation // カメラ映像用ライブラリ

class ViewController: UIViewController {
    
    var input:AVCaptureDeviceInput! // 映像入力

    var cameraView:UIView! // プレビュー表示用View
    var session:AVCaptureSession! // セッション
    var camera:AVCaptureDevice! // カメラデバイス

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // 画面の初期化
    override func viewWillAppear(animated: Bool) {
        
        // プレビューを画面全体に引き延ばす
        let screenWidth = UIScreen.mainScreen().bounds.size.width;
        let screenHeight = UIScreen.mainScreen().bounds.size.height;
        cameraView = UIView(frame: CGRectMake(0.0, 0.0, screenWidth, screenHeight))
        
        // 背面カメラを探す
        // 自撮りにしたければ AVCaptureDevicePosition.Front
        session = AVCaptureSession()
        for captureDevice: AnyObject in AVCaptureDevice.devices() {
            if captureDevice.position == AVCaptureDevicePosition.Back {
                camera = captureDevice as? AVCaptureDevice
                break
            }
        }
        
        // カメラ映像を取得
        do {
            input = try AVCaptureDeviceInput(device: camera) as AVCaptureDeviceInput
        } catch let error as NSError {
            print(error)
        }
        
        if( session.canAddInput(input)) {
            session.addInput(input)
        }
        
        // 画面に表示
        let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer.frame = cameraView.frame
        previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        
        self.view.layer.addSublayer(previewLayer)
        
        session.startRunning()
    }
}

ビルド、実行、そして失敗

カメラのキャプチャをするのでシミュレータでは動きません。
Mac に USB ケーブルで iPhone を接続してから Xcode の実行ボタンを押します。

・・・が、ビルドに失敗します。
Xcode7 では bitcode という中間データを使用しますが、
OpenCV は bitcode を含んでいないのでリンクエラーになるようです。

Xcode 上でプロジェクト名を選択し、Build Settings から bitcode を disable にします。
WS000009.jpg

で、再度試すと今度は iPhone 上でアプリが起動します。

処理対象の画像を取り出す

このままだと撮影した映像はそのまま画面に出てしまうので、 UIImage という画像オブジェクトの形でデータを取り出します。
// バッファ処理用 delegate を作れるように AVCaptureVideoDataOutputSampleBufferDelegate を継承
class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
    
    var input:AVCaptureDeviceInput! // 映像入力
    var output:AVCaptureVideoDataOutput! // 映像出力

撮影した映像を queue に放り込んで delegate で処理するように変更。
処理が重くなって追いつかなくなるので間に合わなかったフレームは捨てます。
本当はフレームレートを落とした方が良いのですがやってません。
本体が発熱するかも。
    // 画面の初期化
    override func viewWillAppear(animated: Bool) {
        
        // プレビューを画面全体に引き延ばす
        let screenWidth = UIScreen.mainScreen().bounds.size.width;
        let screenHeight = UIScreen.mainScreen().bounds.size.height;
        cameraView = UIImageView(frame: CGRectMake(0.0, 0.0, screenWidth, screenHeight))
        
        // 背面カメラを探す
        // 自撮りにしたければ AVCaptureDevicePosition.Front
        session = AVCaptureSession()
        for captureDevice: AnyObject in AVCaptureDevice.devices() {
            if captureDevice.position == AVCaptureDevicePosition.Back {
                camera = captureDevice as? AVCaptureDevice
                break
            }
        }
        
        // カメラ映像を取得
        do {
            input = try AVCaptureDeviceInput(device: camera) as AVCaptureDeviceInput
        } catch let error as NSError {
            print(error)
        }
        
        if( session.canAddInput(input)) {
            session.addInput(input)
        }
        
        // カメラ映像を画像処理に回す.
        output = AVCaptureVideoDataOutput()
        output.videoSettings = [kCVPixelBufferPixelFormatTypeKey : Int(kCVPixelFormatType_32BGRA)]
        
        // デリゲート
        let queue: dispatch_queue_t = dispatch_queue_create("videoqueue" , nil)
        output.setSampleBufferDelegate(self, queue: queue)
        
        // 処理が遅れたフレームを捨てる
        output.alwaysDiscardsLateVideoFrames = true
        
        // セッションに出力を追加
        if session.canAddOutput(output) {
            session.addOutput(output)
        }
        
        // カメラ向きを固定
        for connection in output.connections {
            if let conn = connection as? AVCaptureConnection {
                if conn.supportsVideoOrientation {
                    conn.videoOrientation = AVCaptureVideoOrientation.Portrait
                }
            }
        }
        
        // 画面に表示 → captureOutputで出力
        self.view.addSubview(cameraView)
        //let previewLayer = AVCaptureVideoPreviewLayer(session: session)
        //previewLayer.frame = cameraView.frame
        //previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        //
        //self.view.layer.addSublayer(previewLayer)
        
        session.startRunning()
    }

delegate で画像バッファを拾って UIImage を作ります。
正直言って、このコードは何をやっているのか全く分かりません。
訳も分からずサンプルコードを swift2 に対応させただけ。

作った UIImage は最後の ImageProcessing.SobelFilter という行で OpenCV に渡して処理します。
    // 表示更新
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) {
        
        let image:UIImage = self.captureImage(sampleBuffer)
        
        dispatch_async(dispatch_get_main_queue()) {
            // 描画
            self.cameraView.image = image
        }
    }
    
    // sampleBuffer から UIImage を作成
    func captureImage(sampleBuffer:CMSampleBufferRef) -> UIImage {

        // 画像取得
        let imageBuffer: CVImageBufferRef = CMSampleBufferGetImageBuffer(sampleBuffer)!
        
        // ベースアドレスをロック
        CVPixelBufferLockBaseAddress(imageBuffer, 0 )
            
        // 画像データ情報
        let baseAddress: UnsafeMutablePointer<Void> = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0)
            
        let bytesPerRow: Int = CVPixelBufferGetBytesPerRow(imageBuffer)
        let width: Int = CVPixelBufferGetWidth(imageBuffer)
        let height: Int = CVPixelBufferGetHeight(imageBuffer)
        let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue|CGBitmapInfo.ByteOrder32Little.rawValue as UInt32
 
        //RGB色空間
        let colorSpace: CGColorSpaceRef = CGColorSpaceCreateDeviceRGB()!
        let newContext: CGContextRef = CGBitmapContextCreate(baseAddress,                                      width, height, 8, bytesPerRow, colorSpace, bitmapInfo)!
        // Quartz Image
        let imageRef: CGImageRef = CGBitmapContextCreateImage(newContext)!
            
        // UIImage
        let cameraImage: UIImage = UIImage(CGImage: imageRef)
        
        // OpenCV で SobelFilter を掛ける
        let resultImage: UIImage = ImageProcessing.SobelFilter(cameraImage)

        return resultImage

    }

OpenCV を組み込む

OpenCV は swift 非対応で、swift には C++ を直接書けないのでそのままでは繋がりません。
まず、Xcode のファイルリストを右クリックして「New File」で C++ 用のファイルを作ります。
その際に Xcode が自動的にヘッダファイルを作ってくれます。
今回、ImageProcessing.m というファイルを作成したら
ヘッダファイル名は{プロジェクト名}-Bridging-Header.h でした。なんでやねん。

ヘッダファイルにクラスを定義します。
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface ImageProcessing: NSObject
+(UIImage *)SobelFilter:(UIImage *)image;
@end

C++ のファイルに実装したいところですが、
拡張子が自動的に「.m」になっており C++ が書けません。
画面右上の「Identity and Type」で拡張子を「.mm」に変更します。

で、UIImage をOpenCVの画像バッファに変換して
画像処理してUIImageに戻して返します。
素晴らしきかなOpenCV。超簡単。
#import <Foundation/Foundation.h>
#import >UIKit/UIKit.h>

#import "FaceRecogApp-Bridging-Header.h"

#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>

@implementation ImageProcessing: NSObject

+(UIImage *)SobelFilter:(UIImage *)image{
    
    //UIImageをcv::Matに変換
    cv::Mat mat;
    UIImageToMat(image, mat);
    
    //グレースケールに変換
    cv::Mat gray;
    cv::cvtColor(mat, gray, cv::COLOR_BGR2GRAY);
    
    // エッジ抽出
    cv::Mat edge;
    cv::Canny(gray, edge, 100, 200);
    
    //cv::Mat を UIImage に変換
    UIImage *result = MatToUIImage(edge);
    return result;
}
@end

ところで、ググると「#import <opencv2/highgui/ios.h>」と
なっている説明が大半ですが、OpenCV 3.0.0 では
imgcodecs に移っています。

また、「cv::cvColor」を使っているサンプルコードが多いですが
これも 3.0.0 では「cv::cvtColor」です。

実行

iPad で動かすとこんな感じになります。

このエントリーをはてなブックマークに追加

  1. 2016/06/25(土) 01:03:10|
  2. iOS
  3. | コメント:2
関連記事
<<Raspberry Pi で M3D The Micro を小型無線3Dプリンタにする | ホーム | WiiリモコンとArduinoでQuadcopterを飛ばす(6):Wiiリモコンをプロポにする>>

コメント

カメラアプリについて

始めまして、よしなりと申します。

たけやす様の記事を読み、アプリ開発に興味を持ちました。
まずは記事のカメラアプリを動かして見ようとしたのですが、添付画像の様なエラーが出てしまいます。

色々と調べてみたのですが、解決には至りませんでした。

解決方法についてご助言頂けないでしょうか。
宜しくお願い致します。

---動作環境---
OS:El Capitan 10.11.6
Xcode:7.3.1
OpenCV:3.1.0.1
  1. 2016/09/02(金) 01:20:18 |
  2. URL |
  3. よしなり #O2BvZV2Q
  4. [ 編集 ]

Re: カメラアプリについて

>よしなり様

せっかく試してくださったのにすみません。
どうやらFC2ブログが「<Void>」の部分をHTMLだと誤解したようで
自動的に余計な閉じタグ「</Void>」を付け加えてくださりやがっていました。
FC2ブログに誤解されないように修正したので再度コピペしてみていただけますか。
  1. 2016/09/02(金) 22:09:54 |
  2. URL |
  3. たけやす #-
  4. [ 編集 ]

コメントの投稿


管理者にだけ表示を許可する

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。