Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード Twitter

2015-05-27

「Apple Watch 間通信」 #gunosywatch

Gunosy さん主催、Freakout さん会場提供のイベント『Apple Watch meetup @ HillsGarage』に登壇させていただきました。



Apple Watch の機能として、友達と心拍を共有したり、手書きスケッチを共有したりといった「Digital Touch」というものがあるのですが、


f:id:shu223:20150527204104p:image:w500


WatchKit にこういうことをやる API はないので、サードパーティ製アプリで同様のことをやるにはどうするか、という内容です。


メッセージのやりとり、心拍数のやりとりは実際に試してなかなかいい感じになりました。


f:id:shu223:20150527202543p:image:w240

(受信側では送信者の心拍数の値と、それに応じてハートの画像が拡大縮小アニメーションします)


ボツネタ集

WatchKit、ご存知の通り現状ではできることがかなり限られているので、既出だったり、発表者同士でネタがかぶったり、ということを懸念して発表内容を考えるのにかなり悩みました。。以下その過程で出てきて消えたボツネタ集です。


  • 心拍センサーについていろいろ調べてみる

現在のApple Watchにおける計測手法(センサのしくみ)とか、心拍数の値を使ったこういう応用事例(移動距離と組み合わせて坂道の角度を推定する研究があります、みたいな)とか


ボツ理由:専門ではないので、結局ググってわかる範囲のことしかわからない。あとニッチすぎる。


  • Apple Watch の Service / Characteristic をリバースエンジニアリング

ボツ理由:リバースエンジニアリングはApple的に禁止されてて、100人のイベントでそういう話するのはよろしくないかなと。あとたぶん自分のリバースエンジニアリング力だと大して解明できなそう


  • Apple Watchを開発ツールとして使う

Xcodeのプラグイン ↔ WatchKit Extension で通信して、開発中の何かの情報を Apple Watch に表示すると便利かなと。コード書き続けてる時間とか。


ボツ理由:Apple Watch間通信を試したあと、Apple Watch と Mac 間の通信としてやろうかなと思ってたけど、そこまでやる時間がなかった。


関連記事


追記

発表後、HealthKitを利用したアプリを開発されている Flask さん より重要情報をいただきました。


iPhone側がロックされていると、HealthKit からデータを取得できない」とのことです。


なんと、そうだとすると、上のスライドで「心拍数もバックグラウンドで取って通信すればOK」といってるところの利便性はグッと落ちてしまいます。確かに、プライバシーの関係からロック状態ではHealth関連データもロックするというのは納得のいく仕様ではあります。まだ自分で検証&裏とりしてない情報で恐縮ですが速報ということで追記させていただきました。


2015-05-18

『OpenCV 3.0 on iOS』 #yidev 第19回勉強会

第19回 yidev(横浜iPhone開発者勉強会)にて、『OpenCV 3.0 on iOS』という発表をさせていただきました。



概要

OpenCV 3.0 の話、というよりは、最新版の3.0をベースとしつつ、「Core Image や vImage や GPUImage という便利で高速な画像処理ライブラリが存在する昨今においても OpenCV も依然として魅力的ですよ 」というのが発表の主題です。


なぜ今OpenCVか?

スライド内では、理由として以下の3つを提示しています。


圧倒的に機能が豊富

この点については、正直なところ Core Image、vImage、GPUImage は目じゃないかと。「2500以上のアルゴリズム・機能」と言われてもピンと来ないと思うので、具体的に「Core Image 等にはない OpenCV の機能」の一例をこの後のスライドで示しています。


Google のサポートもあり、コンピュータビジョン分野における最新の研究成果/アルゴリズムが日々 OpenCV に実装されていっています。


クロスプラットフォーム

iOS、Android、Linux、Mac OS X、Windows 等々をサポートしてます。


言語は C++ で、ちょうどゲームフレームワーク業界における Cocos2d-x を彷彿とさせられますが、ただ言語的に共用できます、というだけではなくて、iOSでは並列化処理としてGCDが用いられるようになっていたりと、ちゃんとそれぞれのプラットフォームにおける最適化も考慮されてたりもします。


今が熱い

2.0 が出たのが2009年で、そして今年 4/24 に 3.0.0 RC1 がリリースされたばかり。


6年ぶりのメジャーアップデートで、個人的には今が熱いと感じています。


OpenCVでできることの一例

スライドより一部抜粋します。繰り返しになりますが、下記に挙げたものは Core Image や vImage ではできない機能ばかりです。


顔「以外」のものを検出

同梱の学習ツールを使い、分類器自体を自作できるので、車・動物・ロゴ等々、任意の対象を検出可能です。


f:id:shu223:20150518071822j:image:w600


参考:iOS - 「顔以外」のものを画像認識する - Qiita


顔を「認識」する

Core Image の CIDetector は顔の位置を「検出」するものであり、その顔が誰のものであるかという「認識」はできません。OpenCV では「Eigenfaces」「Fisherfaces」など複数の手法が実装されています。


f:id:shu223:20150518071823j:image:w400


文字の「検出」と「認識」

3.0.0 より、下記画像のように、画像内の文字の領域を検出する機能が入りました。


f:id:shu223:20150518071824j:image:w400


その内容を「認識」する機能も追加されていて、そちらについてはオープンソースのOCRエンジン「Tesseract」を利用する方式と、HMM方式の2種類が実装されています。


物体追跡

映像内の物体を追跡する手法は3.0.0以前にもいくつかあったようですが、3.0.0よりTrackerというクラスが追加されていて、その中の TLD(Tracking Learning Detection)というやつは紹介動画のインパクトがすごいです。



3:14あたりからのシーンを見ると、パンダが向きを変えてまったく見え方が変わっていっても、トラッキングがはずれていないことが見て取れます。右側には逐次学習している様子が表示されています。


f:id:shu223:20150518071825j:image:w317


さすがにこれがiOSでリアルタイムに動くかというと直感的には(処理性能的に)厳しそうな気がしますが、いずれ試してみたいと思っています。


High Dynamic Range Imaging (HDR)

下記画像のように、露出を変えて撮影した複数の写真を統合して、HDR写真を生成することができます。


f:id:shu223:20150518071826j:image:w550


画像修復・補間(Inpainting)

写真に意図せず写りこんでしまった物体等を取り除き、それによって欠損した領域を自動修復してくれる機能です。


f:id:shu223:20150518071827j:image:w500


この機能は実際にiOSで実装してみましたが、実質的には関数を1つ呼ぶだけの簡単実装で上記画像の効果を得られました。

cv::inpaint(srcMat, maskMat, dstMat, 3, cv::INPAINT_NS);

参考:OpenCV for iOS で画像の自動補間・修復 - Over&Out その後


vImage との併用

下記のように cv::Mat から vImage_Buffer に変換することで、 OpenCV と vImage を併用することができます。

#import <Accelerate/Accelerate.h>

// ...

cv::Mat src, dst;

// ...

dst.create(cvRound(src.rows*0.75), cvRound(src.cols*0.75), src.type());

vImage_Buffer srcbuf = { src.data, src.rows, src.cols, src.step };
vImage_Buffer dstbuf = { dst.data, dst.rows, dst.cols, dst.step };

vImageScale_Planar8( &srcbuf, &dstbuf, NULL, kvImageNoFlags ); 

先ほどから vImage(Accelerate.framework)を比較対象のように書いてますが、やはり Apple 自身によって CPU にカリカリに最適化された vImage は処理性能の面で非常に魅力的です。この手法により、vImage にある拡大縮小や畳込み等の処理はそちらを使う、という選択肢がとれるようになります。


その他スライドに書いたこと

  • 導入方法について(CocoaPods)
  • Swift からの利用
  • パフォーマンスについて
    • GPUの利用
    • 並列化技術のサポート
    • NEON

OpenCV について書いたその他の記事


2015-05-14

殺しても死なないアプリ 〜Core Bluetooth の「状態の保存と復元」機能〜 #potatotips

昨日、第17回 potatotips という iOS / Android の開発Tips共有会(勉強会)で標題の発表をしてきました。



概要

Core Bluetooth のバックグラウンド実行モードについて。内訳としては、

  • 対応方法
  • バックグラウンドでできること
  • バックグラウンドにおける制約
  • 「状態の保存と復元」機能について

という感じです。


発表時間はきっちり5分しかなかったので結局デモはできず、なんとなく様子がわかる(かもしれない)スクショを貼り付けてあります。


Core Bluetooth の「状態の保存と復元」

この Core Bluetooth の「状態の保存と復元」(State Preservation and Restoration)機能、アプリが停止しても(プロセスが殺されても)システムがバックグラウンドでの処理を引き継いでくれて、必要に応じてアプリをバックグラウンドで起こしてくれるという超画期的なもの。BLEをバックグラウンドで利用する場合には必ずおさえておきたい機能です。


対応方法としては、セントラルマネージャ、ペリフェラルマネージャ初期化時にオプションを渡すだけ。

let options: Dictionary = [
    CBCentralManagerOptionRestoreIdentifierKey: "myKey"
]

self.centralManager = CBCentralManager(
    delegate: self,
    queue: nil,
    options: options)

let options: Dictionary = [
    CBPeripheralManagerOptionRestoreIdentifierKey: "myKey",
]

self.peripheralManager = CBPeripheralManager(
    delegate: self,
    queue: nil,
    options: options)

基本的にはこれだけなのですが、システムがバックグラウンドでアプリケーションを起こしてくれる際に、そのアプリのすべてが復元されるわけではないので、必要に応じてプロパティの中身等を `centralManager:willRestoreState:` 及び `peripheralManager:willRestoreState:` で復元してやる必要があります。そのあたりの詳細は、拙著もご参考にしていただけると幸いです。(バックグラウンドでの制約についても詳しく書いています。)


iOS×BLE Core Bluetoothプログラミング
堤 修一 松村 礼央
ソシム
売り上げランキング: 898


【電子版】(PDF・達人出版会)

iOS×BLE Core Bluetoothプログラミング
堤 修一, 松村 礼央
ソシム
発行日: 2015-03-23
対応フォーマット: PDF


紹介記事:

『iOS×BLE Core Bluetoothプログラミング』という本を書きました - Over&Out その後


Apple Watch 対応とバックグラウンド処理

これはスライドには書いてない話ですが、たとえば、(スライド内にあるスマートロックのケースのように)直接的にバックグラウンドで外部デバイスとBLE通信する必要があるアプリ「ではない」としても、下記記事に書いたように、WatchKit アプリでもBLE機能を利用したいとなると、(現状だと)親アプリがバックグラウンドで動けるようにしておく必要があるかと思います。

WatchKit App をトリガとして Parent App にBLE関連処理を行わせる際のポイントとなるのは、BLEはスキャン、接続、サービス/キャラクタリスティック探索、Read/Write 等々、基本的には非同期的にレスポンスが返ってくる処理ばかりである、というところです。


`openParentApplication:reply:` は同期処理だし、非同期処理完了後にバックグラウンドの親アプリ側から WatchKit App を起こしたりデータを渡したりするAPIはありません。プッシュ通知やローカル通知を使う方法はありますが、プッシュ通知はタイムラグもありますし、ローカル通知は上で行った検証の通りウォッチ側で受け取れないケースが多すぎます。

そんなわけで、BLE利用アプリで WatchKit app も用意する場合にも、この「状態の保存と復元」はかなり大事かと。


ユーザーが明示的にアプリを停止させた場合

「殺しても死なない」と書きましたが、何度か試したところ、ホームボタン2回押しの状態から強制終了させた場合は、復元してくれないようです。(iOS 8.3 で確認。Appleのドキュメント類でそう明記してあるものは見つからず。。)


とはいってもシステムによるアプリの停止を意図的に起こすことは難しいので(メモリをあふれさせるとかしないといけない)、僕が復元機能を確認するときは、以下の手順でやっています。

  • Xcode からアプリを Run する
  • アプリを操作して、ペリフェラルと接続するなり、復元を試したい状態まで持っていく
  • Xcode の Run を止める(アプリのプロセスも消える)
  • ホームボタン2回押しでアプリを消す(プロセスはもう死んでるので、これは履歴を消してるだけ)
  • 復元されるか試す(ペリフェラル側でキャラクタリスティックを更新してNotification飛ばすとか)

おわりに

自分にとってこの話は Core Bluetooth 周りのとっておきの話ではあったのですが、なにぶん5分しかなく「BLE(Bluetooth Low Energy)とは?」という説明をする時間もなかったため、普段さわってない方々にはイマイチ良さが伝わらない発表になってしまったかもしれません。まぁでもどこかで話したいと思ってたことなので機会があってよかったです。


いろいろな方のTipsを聞けて刺激になりましたし、久々にお会いする方も多く、とても楽しかったです。主催の Wantedly さん、発表者・参加者のみなさまどうもありがとうございました!


2015-05-12

OpenCV for iOS で画像の自動補間・修復

写真に意図せず写りこんでしまった物体等を取り除き、それによって欠損した領域を自動修復する技術を、画像修復/画像補間/インペインティング(Inpainting)と呼びます。


で、OpenCV にその機能があったので iOS で実装してみました。

関数 inpaint は,選択された画像領域を,その領域境界付近のピクセルを利用して再構成します.この関数は,スキャンされた写真からごみや傷を除去したり,静止画や動画から不要な物体を削除したりするために利用されます.

opencv documentation より)


実装方法

基本的には `inpaint()` 関数を呼ぶだけです。OpenCV 3.0.0 における inpaint 関数のリファレンスによると、次のように定義されています。

C++: void inpaint(InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags)

Parameters:

  • src – Input 8-bit 1-channel or 3-channel image.
  • inpaintMask – Inpainting mask, 8-bit 1-channel image. Non-zero pixels indicate the area that needs to be inpainted.
  • dst – Output image with the same size and type as src .
  • inpaintRadius – Radius of a circular neighborhood of each point inpainted that is considered by the algorithm.
  • flags – Inpainting method that could be one of the following:
    • INPAINT_NS Navier-Stokes based method [Navier01]
    • INPAINT_TELEA Method by Alexandru Telea [Telea04].```

第2引数に渡すマスク画像というのは、修復したい部分=除去したい部分を示す画像です。1チャンネル・8ビットのみ受け付けるとのこと。


iOS 向けに次のように実装しました。

+ (UIImage *)inpainting:(UIImage *)srcImage maskImage:(UIImage *)maskImage {

    cv::Mat srcMat;
    cvtColor([OpenCVHelper cvMatFromUIImage:srcImage], srcMat, CV_BGRA2BGR);
    cv::Mat maskMat;
    cvtColor([OpenCVHelper cvMatFromUIImage:maskImage], maskMat, CV_BGR2GRAY);
    cv::Mat dstMat(srcImage.size.height, srcImage.size.width, srcMat.type());
    
    // 入力画像, マスク, 出力画像, 修正時に考慮される近傍範囲を表す半径, 手法
    //     INPAINT_NS    = 0, // Navier-Stokes algorithm
    //     INPAINT_TELEA = 1 // A. Telea algorithm
    cv::inpaint(srcMat, maskMat, dstMat, 3, cv::INPAINT_NS);
    
    return [OpenCVHelper UIImageFromCVMat:dstMat];
}

呼び出し側はこんな感じになります。

UIImage *srcImage  = [UIImage imageNamed:@"src_filename"];
UIImage *maskImage = [UIImage imageNamed:@"mask_filename"];
self.imageView.image = [OpenCVHelper inpainting:srcImage
                                      maskImage:maskImage];

試してみる

もうすぐ WWDC、ということで昨年のWWDC に行ったときの記念写真から、「8」の文字を消してみました。


  • 元画像

f:id:shu223:20150511195234j:image:w400


  • マスク画像

f:id:shu223:20150511195235j:image:w400

※マスク画像は、元画像を下に敷いて、トラックパッドで操作しつつ適当になぞって作成しました。


実行結果

f:id:shu223:20150511195236p:image:w432


なかなかいい感じに修復されているのではないでしょうか。


続・試してみる

Apple感を消すため、後ろの看板のリンゴマークも消してみます。


  • マスク画像その2

f:id:shu223:20150511195237j:image:w400


実行結果

f:id:shu223:20150511195758p:image:w432


こちらは全然ダメでしたね。。こういう細かいパターンが続く箇所の修復は苦手なのかもしれません。


こちらもどうぞ

2015-05-11

iOS / OpenCV 3.0 で画像の特徴点を検出する(AKAZE, SIFT, SURF, ORB)

局所特徴量とは / SIFT, SURF 特徴量

このスライドが超わかりやすかったです。



で、SIFT (Scale-invariant feature transform)、SURF (Speed-Upped Robust Feature) というのは、拡大縮小・回転・照明変化に強いロバストな特徴量、としてよく知られているようです。


SURF の方が軽量で、その代わり認識精度は SIFT の方が良い、とのこと。


特徴量の用途

  • 複数写真からのパノラマ写真合成

f:id:shu223:20150511154137p:image:w425

(上に載せたスライドより)


  • AR のマーカー認識

下記画像はARのマーカー認識とは違いますが、そういう使い方ができそうだ、ということは汲んでいただけるかと。。


f:id:shu223:20150511154236j:image:w425

http://docs.opencv.org/3.0-rc1/d7/dff/tutorial_feature_homography.html より)


  • 物体認識

f:id:shu223:20150511154255p:image:w425

(上に載せたスライドより)


ORB(Oriented-BRIEF)特徴量

SIFT, SURF 同様に回転や拡大縮小にロバストでありながら、高速(SURFの10倍、SIFTの100倍)、かつライセンス的にも使いやすい特徴量。


ちなみに下記記事のコメント欄で教えていただきました。


AKAZE 特徴量

OpenCV 3.0 に新たに組み込まれた特徴点抽出アルゴリズム。


AKAZE は Accelerated KAZE の略で、KAZE という SIFT や SURF の欠点を解決した手法をもとに、さらにそのロバスト性の向上と高速化を図ったもの、とのこと。


上記記事では他手法と比較したベンチマークもとっていて、

画像変化のロバスト性については、スケール・回転・輝度・Blur全てにおいて、AKAZEが最も良い数値を示していることが分かります。実際使ってみた感じでも、かなり正確にトラッキングしている感じがします。

スピードに関しても、SIFTやSURFに比べて同程度、もしくはそれ以上です。

とのことで、かなりおいしいとこ取りな感じです。


さらにいろんなところで言われてることですが、SIFT や SURF はライセンス的に商用利用しづらいものらしく、一方でこの AKAZE はライセンス的にも使いやすくなっているとのこと。*1


AKAZE vs ORB

OpenCV 公式のこちらのページにて、AKAZE と ORB による planar tracking(平面トラッキング)の比較が行われています。



AKAZEの方がより強力にトラッキングしているようです。


iOSにおける実装

というわけでここからが本題ですが、iOS で SURF, SIFT, ORB, AKAZE を用いて特徴点検出を行ってみます。

contrib の利用

SIFT, SURF は(ライセンスの都合上?)contrib という別枠で管理されています。詳しくは下記記事にまとめたのでご参照ください。


コード

こんな感じで実装しました。

typedef NS_ENUM(NSUInteger, CVFeatureDetectorType) {
    CVFeatureDetectorTypeSURF,
    CVFeatureDetectorTypeSIFT,
    CVFeatureDetectorTypeORB,
    CVFeatureDetectorTypeAKAZE,
};
+ (UIImage *)detectKeypoints:(UIImage *)srcImage withFeatureDetector:(CVFeatureDetectorType)detectorType
{
    cv::Mat srcMat = [OpenCVHelper cvMatFromUIImage:srcImage];

    // FeatureDetector 生成
    Ptr<Feature2D> detector;
    switch (detectorType) {
        case CVFeatureDetectorTypeSURF:
        default:
            detector = SURF::create();
            break;
        case CVFeatureDetectorTypeSIFT:
            detector = SIFT::create();
            break;
        case CVFeatureDetectorTypeORB:
            detector = ORB::create();
            break;
        case CVFeatureDetectorTypeAKAZE:
            detector = AKAZE::create();
            break;
    }
    
    // 特徴点抽出
    std::vector<KeyPoint> keypoints;
    detector->detect(srcMat, keypoints);
    
    printf("%lu keypoints are detected.\n", keypoints.size());
    
    // 特徴点を描画
    cv::Mat dstMat;
    
    dstMat = srcMat.clone();
    for(int i = 0; i < keypoints.size(); i++) {
        
        KeyPoint *point = &(keypoints[i]);
        cv::Point center;
        int radius;
        center.x = cvRound(point->pt.x);
        center.y = cvRound(point->pt.y);
        radius = cvRound(point->size*0.25);
        
        cv::circle(dstMat, center, radius, Scalar(255,255,0));
    }
    
    return [OpenCVHelper UIImageFromCVMat:dstMat];
}

呼び出し側はこんな感じ。

- (IBAction)segmentChanged:(UISegmentedControl *)sender {
    
    UIImage *srcImage = [UIImage imageNamed:@"image_filename"];
    
    self.imageView.image = [OpenCVHelper detectKeypoints:srcImage
                                     withFeatureDetector:(CVFeatureDetectorType)sender.selectedSegmentIndex];
}

実行結果

実行時間は iPhone 6 で計測。(特徴点の描画も含む)


SURF

f:id:shu223:20150511154256p:image:w375

  • 特徴点数: 5127
  • 実行時間: 0.610362[s]

SIFT

f:id:shu223:20150511155311p:image:w375

  • 特徴点数: 3175
  • 実行時間: 2.116536[s]

ORB

f:id:shu223:20150511155312p:image:w375

  • 特徴点数: 500
  • 実行時間: 0.120371[s]

AKAZE

f:id:shu223:20150511155313p:image:w375

  • 特徴点数: 5651
  • 実行時間: 1.260507[s]

所感&今後の展望

あれ、AKAZEが意外と遅い。。処理時間の計測範囲には特徴点の描画まで含めたのでその影響かな、とも思いましたがSURFは同じぐらいの特徴点数なのに速いのでそこではないようです。


上でも参照したこちらの記事によると、

AKAZE/KAZEのコードはOpenMPでの並列化に対応しているのですが、このベンチマークではOpenMPを利用していません。Pablo氏からの指摘によれば、AKAZEのロジックは並列化に向いているため、OpenMPの利用により大幅なスピードアップが見込めるとのことです。OpenCVのSIFTとSURFのコードを見たところ、SIFTのコードは並列化していませんが、SURFのコードは並列化しています。上記ベンチマークのSURFの速さは、それも起因していると思われます。

とのこと。SURFの並列化がiOSでも効いているのかどうか、AKAZE の OpenCV の実装がどうなってるのかまでは理解してやっていないので、上記結果はほんの参考程度にどうぞ。。


また、処理速度以前に、検出された特徴点が手法によって全然違う、というのも上の結果から見て取れます。アルゴリズムの中身を理解して、用途に応じて適切なものを選ぶ必要がありそう。今回使用した画像はベクター的なオブジェクトの集合体ですが、写真的な画像に適用したらどうなのか、とか。



次ステップとしては DescriptorExtractor, DescriptorMatcher を使って特徴点のマッチングまでやってみたいと思っています。


おまけ:OpenCV 3.0 で使える FeatureDetector

  • FAST,ORB,BRISK,MSER,GFTT,HARRIS,SimpleBlob,KAZE,AKAZE
  • 要contrib: STAR,SIFT,SURF

(参考:OpenCV3.0.0-alphaの特徴抽出・マッチングまとめ - whoopsidaisies's diary


その他参考ページ

公式

ブログ

*1:自分はまだ個人でお試し利用中なので詳細未確認

2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 |