ON THE HAND

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

スポンサーサイト

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

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

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

Swift Realm を導入する

アプリ開発系の話が続きますが、書いておかないと忘れてしまうので。
今回はiOS アプリで Swift Realm を導入する方法です。

Swift Realmって何?

Realm は組み込み用DB(データベース)です。
iOS と android のどちらでも使えます。
iOS には Objective C 版と Swift 版があり、Swift 版が Swift Realm。

ただし、DBと言ってもSQL文などの高度な検索言語は使えません。
その代わり、クラスのメンバ変数がそのままDBカラムになっていたり、
オブジェクトがDBのレコードを表してしていたりします。
そのため、クラス定義でDBスキーマを定義し、
オブジェクト操作がレコード操作になるなど、
データベースの知識がなくても組み込みDBを扱うことができます。

ここまでで「は?宇宙語?」と思った方は気にせず先に進んでください。

Cocoa Pods

Swift Realm を導入する前に、まずは Cocoa Pods を入れます。
Cocoa Pods は Mac のライブラリ管理ツールです。

まず、Mac 上でターミナルを開きます。
開いたら次のコマンドを実行します。
上から順に
「Ruby のアップデート」
「Cocoa Podsのインストール」
「Cocoa Podsの初期化」です。
$ sudo gem update --system
$ sudo gem install cocoapods
$ pod setup

これだけです。

Swift Realm のインストール

Swift Realm を導入します。

まず、Xcode のプロジェクトフォルダに移動します。
例えば、デスクトップに ProjectName というプロジェクトがあるならこんな感じ。
$ cd ~/Desktop/ProjectName/

次に、空の pod ファイルを作ります。
$ pod init

Podfile というファイルが出来上がるので、これに設定を書き込みます。
$ vi Podfile

vi コマンドは k, j, h, l キーがそれぞれカーソルの上下左右移動。
i キーで入力モード。
ESC で入力モードを抜けて、「:wq」で保存して終了、です。
(正確には「:w」が保存。「:q」が終了。)
target 'ProjectName' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ProjectName
  pod 'Realm'
  pod 'RealmSwift'
    :

終了したら Swift Realm をインストール。
$ pod install

インストールに成功すると、プロジェクトフォルダ内に
ワークスペースファイルが出来上がります。
Xcode でプロジェクトファイルを開いている場合は、
一旦閉じてワークスペースファイル(〜.xcworkspace)を開きます。

workspace に Pods プロジェクトができて、Realm がインストールされていると思います。
Xcode のファイルツリー上でプロジェクト名をクリックします。
TARGETS でプロジェクト名をクリックします。
General タブの一番下に Linked Frameworks and Libraries があるので Realm.framework を追加。

出来上がったら、試しにビルドしてみて問題なければインストール終わり。

余談ですが、vim (vi) は軽量なコマンドラインエディタです。

コマンドラインなのでとっつきにくいですが、
正規表現での一括置換や、テキストブロックでのコピペなど
通常のエディタではできない機能がかなりたくさんあります。

vim に慣れてくると、いちいちマウスでポチポチしながら
編集するのが馬鹿馬鹿しくなってきます。

Swift Realm を使う

Swift Realm では1つのテーブルを1つのクラスで表現します。
試しに、SampleDB.swift というファイルを作ります。
import Foundation
import RealmSwift

class Sample : Object {
    dynamic var id:Int = 0
    dynamic var name:String = ""
    dynamic var password:String = ""
    
    override static func primaryKey() -> String? {
        return "id"
    }
    
    override static func indexedProperties() -> [String] {
        return ["id","name","password"]
    }
    
}

class SampleDB {
    var realm = SampleDB.getRealm()

    static func getRealm() -> Realm {
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let realmPath = (paths[0] as NSString).appendingPathComponent("sample.realm")
        let realmURL = URL(fileURLWithPath: realmPath)
        
        return try! Realm(fileURL: realmURL)
    }
}

Sample クラスがDBテーブル構造を表しています。
このサンプルコードの場合は「id Int型」「name String型」「password String型」の3種類のカラムが存在しています。

SampleDB クラスはDBアクセスクラスです。
static に Realm オブジェクトを持ってしまうのが楽かもしれません。

getRealm() 関数の中は、本当は「return try! Realm()」だけでも構いません。
ただし、その場合は Sample クラスで定義されているテーブルにだけアクセスできます。

Realm(fileURL) 関数を使うと、fileURL で渡したファイルパスのテーブルを参照します。
今回のサンプルコードの場合は「sample.realm」というファイルのテーブルを使います。

このようにしておくと、DBアクセスクラスを複数作成して、
それぞれ異なるテーブル構造を定義することが可能です。

ここまでできてしまえば、SampleDB クラスに関数を追加して
realm オブジェクトにレコードを追加したり検索したりすればOK。

Realm へのアクセス

realm オブジェクトへのアクセスは main スレッドから行う必要があります。
例えば、レコードを追加する場合はこんな感じ。
    func append( item:Sample ) {
        DispatchQueue.main.async {
            try! self.realm.write() {
                self.realm.add(item, update: true)  // データが無ければ追加、あったら上書き
            }
        }
    }

add 関数で update:true を指定しておくとSQL 文のUPSERT相当の動きになります。

その他、Realm の詳細については公式サイトのマニュアルにサンプルコード付きで説明されています

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

スポンサーサイト
  1. 2017/01/21(土) 01:31:23|
  2. iOS
  3. | コメント:0

iOSアプリ わたしのほんやさん

iOS でこんなアプリを作ってみました。
わたしのほんやさん
MyBooksTop

うちの5歳女児が積み木でレジ風の物体を作ってお店屋さんごっこをしていたので、
「iPad でレジ作ってあげるよ」
と安請け合いしたのが運の尽き。

深夜にコーディング
  ↓
明朝、子供たちに使わせてフィードバック
  ↓
カミさんが画面パーツの画像作成
  ↓
深夜に組み込み
  ↓
以下、繰り返し

というブラック企業ばりのアジャイル開発をしていくうちに
モリモリと余計な機能が増えた上にiPhone対応までしてしまいました。

実装上は次のようなことをやっています。
気が向いたら追い追いと説明を載せていきます。

  • 全画面ではないViewでカメラプレビュー

  • MetaDataObject でバーコードリーダー

  • AVSpeechSynthesizer で音声合成

  • Amazon Product Advertising API から書籍情報を取得

  • Realm で組み込みDB

  • iPad と iPhone を1つのStoryBoardでレスポンシブデザイン

  • iPad は Container View で埋め込み。iPhone は画面遷移。

  • Collection View で Cell をランダムに傾ける

  • Google AdMobを埋め込み

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

  1. 2016/12/10(土) 21:02:52|
  2. iOS
  3. | コメント:0

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