OpenCVのPythonバインディングとC++の速度比較

OpenCVのPythonバインディングはC++で書いた場合に比べてどれくらい遅いのか気になったので適当に調べて見ました。
一番速度差が出そうな要素アクセスをするコードでテストしました。

環境

Mac OS X 10.7.2
OpenCV 2.3.1a
Python 2.7.1
Apple LLVM compiler 3.0

C++

Mat gradation(1000, 1000, CV_8UC3);

for (int y = 0; y < gradation.rows; y++) {
  Vec3b *p = &gradation.at<Vec3b>(y, 0);
  for (int x = 0; x < gradation.cols; x++) {
    (*p)[0] = (int)(255.0 * y / gradation.rows);
    (*p)[1] = (int)(255.0 * x / gradation.cols);
    (*p)[2] = (int)(255.0 * x / gradation.cols);
    
    p++;
  }
}

imwrite("/Users/ryota/opencvtestCpp.tiff", gradation);

Python

import timeit

if __name__ == '__main__':
  t = timeit.Timer('''
  import cv
  import math
  gradation = cv.CreateMat(1000, 1000, cv.CV_8UC3)

  for y in xrange(gradation.rows):
    for x in xrange(gradation.cols):
      gradation[y, x] = (math.floor(255.0 * y / gradation.rows), math.floor(255.0 * x / gradation.rows), math.floor(255.0 * x / gradation.rows))

  cv.SaveImage("/Users/ryota/opencvtextPython.tiff", gradation)
  ''')
  print t.timeit(2)

結果

[sec] Python C++ C++/Python 比
(1)5000×5000 36.58 1.33 27.47
(2)1000×1000 1.59 0.07 23.67
(1)/(2) 比 22.96 19.78

適当な考察

Pythonの場合はC++の場合の25倍程度の時間がかかっています。
これは厳しいですね。
ただ、今回のテストで使用したコードが最適でないかもしれないので…
もし高速なコードがありましたら教えていただけると助かります。

Mixi SDK for iOSを使う際の注意

mixi SDK for iOSを使ってGraph APIを叩く機会があり、ちょっとはまったのでメモ。

環境

iOS 5.0
Xcode 4.2
mixi SDK for iOS ver.1.2

注意点

基本的にはmixiのドキュメント通りに実装すれば問題ありませんが、いくつか注意したほうがいい点が。

mixi公式アプリがインストールされている環境のみで動作する

FacebookのSDKのように未インストールの場合はSafariで認証、といった機能はありません。そのため、シミュレータでは動作しません。

ドキュメントはdocs/index.htmlからアクセス

SDKをダウンロード・解凍してできたディレクトリの中のdocs/index.htmlを開くことで、詳細なドキュメントを読むことができます。

なぜかエラーしていないときにもerror != nilになる

公式ドキュメントにはUIApplicationDelegate#application:openURL:sourceApplication:annotation:に以下のコードを追加するように書いてあります。

NSError *error = nil;
NSString *apiType = [[Mixi sharedMixi] application:application openURL:url sourceApplication:sourceApplication annotation:annotation error:&error];
if (error) {
  // エラーが発生しました
}
else if ([apiType isEqualToString:kMixiAppApiTypeToken]) {
  // 認可処理に成功しました
}
else if ([apiType isEqualToString:kMixiAppApiTypeRevoke]) {
  // 認可解除処理に成功しました
}

しかし、エラーが発生していない場合でもif (error) {}に入ってしまいます。[error description]を見てみても原因はわかりませんでした。とりあえず、私はコメントアウトしておきました。

公式ドキュメントに[[Mixi sharedMixi] restore]は書いてあるのに[mixi store]は書いていない

最初、公式ドキュメント通りに実装したら、restoreしているにもかかわらず、起動するごとに認証を要求されました。トークンなどの情報を永続化するには[mixi store]を呼び出します。以下のようなコードをUIApplicationDelegate#application:openURL:sourceApplication:annotation:に追加しましょう。

NSError *error = nil;
NSString *apiType = [[Mixi sharedMixi] application:application openURL:url sourceApplication:sourceApplication annotation:annotation error:&error];
if ([apiType isEqualToString:kMixiAppApiTypeToken]) {
    // 認可処理に成功しました
    [[Mixi sharedMixi] store];
}
else if ([apiType isEqualToString:kMixiAppApiTypeRevoke]) {
    // 認可解除処理に成功しました
    [[Mixi sharedMixi] store];
}

通常、- (void)mixi:(Mixi *)mixi didFinishLoading:(NSString *)dataではなく

  • (void)mixi:(Mixi *)mixi didSuccessWithJson:(NSDictionary *)dataを使う

SBJsonが同梱されているので、デコードしたものを渡してくれます。

Google Calendarの表示領域を拡大することができる拡張機能「Minimalist for Google Calendar」(Chromeのみ)

Googleのサービスのデザインが最近(といっても少し経つ気がしますが)変わりました。
余白を使ったデザインで、表示領域は小さくなっています。近年のディスプレイの高解像度化にあわせて、というところでしょうか。
ただ、そうはいってもラップトップPCは解像度が限られていますので、表示領域が小さくなるのは死活問題です。

以前のデザイン

新しいデザイン

ということで、Google Chrome用の拡張機能であるMinimalist for Google Calendarを紹介します。
Minimalist for Google Calendar™ – Chrome ウェブストアからインストールします。様々な設定項目がありますが、私は以下のように設定しています。

以上の設定を適用すると、以下のようになります。

iPhoneでTwitterにOAuthでアクセスする方法(OAuthConsumer使用)

OAuthConsumerを入手

まず、OAuthConsumerを入手します。オリジナルのOAuthConsumerはiPhone向けに使用するには不都合があるので、iPhone向けに改良されたものを使用するのがいいと思います。

jdg/oauthconsumer – GitHub

Githubにあるので、git cloneでダウンロードします。

Consumerの生成

Twitterにアクセスし、アプリケーションの登録を済ませてください。
以下のようにconsumerを生成します。これは以下の工程で続けて使うのでインスタンス変数などに保存しておいてください。

_consumer = [[OAConsumer alloc] initWithKey:kTwitterConsumerKey
                                     secret:kTwitterConsumerSecret];

未認可のリクエストトークンを取得

まず、未認可のリクエストトークンをTwitterから取得します。

NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/oauth/request_token"];
   
OAMutableURLRequest *request =
    [[[OAMutableURLRequest alloc]
     initWithURL:url
     consumer:_consumer
     token:nil
     realm:nil
     signatureProvider:nil] autorelease];

[request setHTTPMethod:@"POST"];

OADataFetcher *fetcher = [[[OADataFetcher alloc] init] autorelease];

[fetcher fetchDataWithRequest:request
                     delegate:self
            didFinishSelector:@selector(requestTokenTicket:didFinishWithData:)
              didFailSelector:@selector(requestTokenTicket:didFailWithError:)];

取得が完了するとデリゲートが呼ばれますので、リクエストトークンを変数で保持します。
以下のようにWebViewで認可ページを表示します。

- (void)requestTokenTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data {
    if (ticket.didSucceed) {
        NSString *responseBody =
        [[[NSString alloc] initWithData:data
                               encoding:NSUTF8StringEncoding]
         autorelease];
       
        _requestToken = [[OAToken alloc] initWithHTTPResponseBody:responseBody];
       
       
        // WebView
        NSString *urlString =
            [NSString stringWithFormat:
             @"https://api.twitter.com/oauth/authorize?oauth_token=%@",
             _requestToken.key];
       
        NSURLRequest *urlRequest =
            [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
       
        [_webView loadRequest:urlRequest];
    }
}

リクエストトークンを認可済みにする

次に先ほど取得したリクエストトークンをユーザに認可してもらいます。
OAuthではTwitter社のページにリダイレクトさせて行います。

先ほどのコードでWebViewに認可ページが表示されていると思います。
ログイン画面が表示されるので、ここでログインをすると、PINコードという数字が表示されます。このPINコードを次のステップで使用する必要がありますので、Javascriptを利用して取得します。

// WebViewのデリゲートを設定しておいてください
- (void)webViewDidFinishLoad:(UIWebView *)webView {
   
    if (_firstLoad) {
        [webView performSelector:@selector(stringByEvaluatingJavaScriptFromString:)
                         withObject: @"window.scrollBy(0,200)"
                         afterDelay: 0];
        _firstLoad = NO;
    } else {
        NSString *authPin = [self _getAuthPinInWebView:webView];
       
          if (authPin.length) {
            _requestToken.verifier = authPin;
            [self _getAccessToken];
               return;
          }
    }
   
}
// Twitter-OAuth-iPhoneから引用させていただきました
- (NSString *)_getAuthPinInWebView: (UIWebView *) theWebView {
     // Look for either 'oauth-pin' or 'oauth_pin' in the raw HTML
     NSString *js = @"var d = document.getElementById('oauth-pin'); if (d == null) d = document.getElementById('oauth_pin'); if (d) d = d.innerHTML; d;";
     NSString *pin = [[theWebView stringByEvaluatingJavaScriptFromString: js] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
   
     if (pin.length == 7) {
          return pin;
     } else {
          // New version of Twitter PIN page
          js = @"var d = document.getElementById('oauth-pin'); if (d == null) d = document.getElementById('oauth_pin'); " 
          "if (d) { var d2 = d.getElementsByTagName('code'); if (d2.length > 0) d2[0].innerHTML; }";
          pin = [[theWebView stringByEvaluatingJavaScriptFromString: js] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
       
          if (pin.length == 7) {
               return pin;
          }
     }
   
     return nil;
}

アクセストークンを取得

これで認証は最後です。

- (void)_getAccessToken {
   
    NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/oauth/access_token"];
   
    OAMutableURLRequest *request = [[[OAMutableURLRequest alloc]
                                    initWithURL:url
                                    consumer:_consumer
                                    token:_requestToken
                                    realm:nil
                                    signatureProvider:nil] autorelease];

    [request setHTTPMethod:@"POST"];
   
    OADataFetcher *fetcher = [[[OADataFetcher alloc] init] autorelease];
   
    [fetcher fetchDataWithRequest:request
                         delegate:self
                didFinishSelector:@selector(accessTokenTicket:didFinishWithData:)
                  didFailSelector:@selector(accessTokenTicket:didFailWithError:)];
}

- (void)accessTokenTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data {
    if (ticket.didSucceed) {
        NSString *responseBody =
        [[[NSString alloc] initWithData:data
                               encoding:NSUTF8StringEncoding]
         autorelease];
       
        _accessToken =
            [[[OAToken alloc] initWithHTTPResponseBody:responseBody] autorelease];
              
    }
   
}

これでアクセストークンを取得できました。
このトークンでAPIを利用します。
ちなみにトークンを保存・復元するにはstoreInUserDefaultsWithServiceProviderNameとinitWithUserDefaultsUsingServiceProviderNameを使います。詳しくはぐぐってください。

APIを叩く

APIの呼び出しも基本的には以上の流れと同じです。

- (void)updateStatus:(NSString *)status {
    NSString *urlString = @"http://api.twitter.com/1/statuses/update.json";

   
    NSURL *url = [NSURL URLWithString:urlString];
   
    OAMutableURLRequest *request = [[[OAMutableURLRequest alloc]
                                     initWithURL:url
                                     consumer:_consumer
                                     token:_accessToken
                                     realm:nil
                                     signatureProvider:nil] autorelease];
   
    [request setHTTPMethod:@"POST"];

    OARequestParameter *statusParam =
        [[[OARequestParameter alloc] initWithName:@"status"
                                            value:status] autorelease];
   
    NSArray *params = [NSArray arrayWithObjects:statusParam, nil];
    [request setParameters:params];
   
       
    OADataFetcher *fetcher = [[[OADataFetcher alloc] init] autorelease];
    [fetcher fetchDataWithRequest:request
                         delegate:self
                didFinishSelector:@selector(updateStatus:didFinishWithData:)
                  didFailSelector:@selector(updateStatus:didFailWithError:)];

}

Facebook iOS SDKの簡単なラッパーを公開します

Facebookはアプリ連携のために公式にSDKを公開しています。
facebook/facebook-ios-sdk – GitHub

これだけでも非常に簡単に扱えるのですが、ちょっとだけ簡単に使えるようなラッパーを作りましたので一応公開します。
(公開するほどのものでもないのですが)

Githubで公開しています。
ryotarai/FacebookManager – GitHub

主な機能は以下です

  • graphPath(”me”, “me/friends”など)、デリゲート、セレクタを指定するだけで、データを取ってくることができます
  • Facebook iOS SDK単体だと、OAuth未認可の場合の処理を書かなければならず、(ちょっとだけ)面倒です
  • 複数のコントローラからのFacebookへのアクセスを一元化できます
  • フィードに投稿など(ダイアログ関連)は現時点では実装していません(実装予定はあります)

使用方法はREADMEを参照してください。

ASIFormDataRequestのPOSTデータを書き換える方法

ASIHTTPRequestという有名なObjective-C用のライブラリがあります。これのPOST, Multipart対応したものがASIFormDataRequestなのですが、1度実行したリクエストのPOSTデータの書き換えに少々苦戦しました。メモしておきます。

まず、POSTデータを読みだすには以下のようにします。

NSArray *array = [request postData];
for (NSDictionary *dict in array) {
     NSString *key = [dict objectForKey:@"key"];
     NSString *value = [dict objectForKey:@"value"];
}

このようにkeyとvalueを取得することができます。
一つ注意点があります。このpostDataは無名カテゴリ内でプロパティとして宣言されていますので、通常ユーザが操作することは想定していないと考えられます。
読み出しのみにするのが安全でしょう。
また、Xcodeがwarningを表示するかと思いますが、実装は存在しますので問題なく実行できます。

変更する際は通常通りsetPostValue:forKey:またはaddPostValue:forKey:で。

ここまででPOSTデータの変更ができましたが、このまま再度実行しても前のPOSTデータのままです。
実は実際に送信されるデータはpostDataではなくpostBodyです。postDataからpostBodyに変換する必要があります。

request.postBody = [NSMutableData data];
request.haveBuiltPostBody = NO;
[request buildPostBody];

この3行で完了です。
buildPostBodyはpostBodyに追記するため、まず空のMutableDataを用意します。
haveBuiltPostBodyがYESだと、buildPostBodyは動かない(returnされてしまう)ので、手動でNOに設定します。
その後、buildPostBodyでPOSTデータの生成を行います。

ASIHTTPRequestはドキュメントに載っていない情報があります。
(私が見落としているだけ??)
読みやすいコードなので、何か困ったらコードを覗いてみるのが良さそうです。

Interface Builderを使わない最も基本的なiPhoneアプリ開発

iPhoneアプリを作りたい!と思ってMacとiPhoneと入門書を買った人は多いはず。
でも、Objective-Cという言語、iOS SDK(UIView? UIViewController??)という壁により、なかなか理解が進まない人が多いのではないでしょうか。

理解を阻害している原因の一つに、Interface Builderの存在があります。
XcodeにはInterface Builder(以下IB)というUIデザイン専用のアプリケーションが付属しています。
わけも分からずIBOutlet, IBActionを使ってる人も多いと思います。

確かにIBは便利なツールなのですが、入門→初級に上がるためにはIB抜きでのプログラミングを経験することが近道です。
今回はIB抜きで簡単なiPhoneアプリを作ってみることにします。

Xcodeのプロジェクトを新規作成する

プロジェクトの種類は”Window-based Application”にしましょう。
これは余計なものがついていない、最もシンプルなテンプレートです。
プロダクト名は”WithoutIB”としました。

IBとおさらばする

IBといえば、.xibファイル。
ということで削除しましょう。参照だけでなく本体も削除してしまっていいです。

次にプロジェクト設定を変更します。
以下の画面はXcode4のものなのでXcode3を使っている方は少し異なるかもしれませんが、基本的には同じです。

main.mを開きます。
(Xcode4の場合、Supporting Filesグループ内にあります)

// Before
int retVal = UIApplicationMain(argc, argv, nil, nil);
// After
int retVal = UIApplicationMain(argc, argv, nil, @"WithoutIBAppDelegate");

そろそろIBとおさらばできます。
このままだとIBOutletを使っている箇所がありますので、変更します。
WithoutIBAppDelegate.hを開き、以下のように変更します。

// Before
@property (nonatomic, retain) IBOutlet UIWindow *window;
// After
@property (nonatomic, retain) UIWindow *window;

アプリの土台をつくる

iPhoneアプリはここから始まります。
それはWithoutIBAppDelegate.mの

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    [self.window makeKeyAndVisible];
    return YES;
}

の部分です。ここにコードを書き加えます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    [self.window makeKeyAndVisible];
    return YES;
}

1行だけ書き加えました。UIWindowというものを生成しましたが、これはすべてのUI(View)の土台になるもので、ここに貼りつけていくイメージをもつと良いです。
一つ注意すべきなのは、alloc->initで生成しているので自分でreleaseをしなければいけません。今回の場合はdeallocメソッドに解放が最初から書かれています。

[_window release];

土台にのせてみる

上で作ったUIWindowにUIを載せてみます。
UIWindowに直接UITextViewやUIImageViewを載せることはできませんしません。
UIWindowの上にはUIView(Controller)を載せます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   
    // ここから
    UIViewController *viewController = [[UIViewController alloc] init];
    [self.window addSubview:viewController.view];
    [viewController release];
    // ここまで追加

    [self.window makeKeyAndVisible];
    return YES;
}

UIWindowの上に載せるにはaddSubview:を使います。
(addSubviewをしたあとすぐにreleaseしているのは、addSubviewした時点でretainされているためです。メモリ管理については別記事にしてまとめたいと思います)

UIViewControllerというのが正しく機能しているのか確認するために、背景色を変えてみます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   
    UIViewController *viewController = [[UIViewController alloc] init];
    viewController.view.backgroundColor = [UIColor redColor]; // この行を追加
    [self.window addSubview:viewController.view];
    [viewController release];

    [self.window makeKeyAndVisible];
    return YES;
}

実行してみましょう。赤い画面が表示されたでしょうか。
つまらないアプリですが、これがどんなiPhoneアプリでも基本となっています。
次は少し複雑にしたプログラムを紹介してみましょう。

UITabBarで特定のタブをタッチしたときにモーダルでビューを表示する方法

※Interface Builderは使っていません

UITabBarは基本的にはタブを切り替えるUIですが、ボタンのように使うこともあります。
(Foursquareのチェックインボタンなど)

少々強引ですが、特定のタブをタッチしたときにモーダルでビューを表示する方法を紹介します。

以下、すべてAppDelegate.[h|m]のお話です。

プロパティを追加します。

// interface
@property (nonatomic, retain) UITabBarController *tabBarController;
@property (nonatomic, retain) UIViewController *controller1;

@property (nonatomic, retain) UIViewController *controller2;
@property (nonatomic, retain) UIViewController *controller3;

// implement
@synthesize tabBarController = _tabBarController;
@synthesize controller1 = _controller1;
@synthesize controller2 = _controller2;
@synthesize controller3 = _controller3;

その後、起動直後に実行されるコードを追加します。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    _tabBarController = [[UITabBarController alloc] init];
   
   _controller1 = [[UIViewController alloc] init];
    self.controller1.tabBarItem.title = @"タブ1";
   

   _controller2 = [[UIViewController alloc] init];
    self.controller1.tabBarItem.title = @"タブ2";
   
   _controller3 = [[UIViewController alloc] init];
    self.controller1.tabBarItem.title = @"タブ3";

    self.tabBarController.viewControllers =
        [NSArray arrayWithObjects:
         self.controller1,
         self.controller2,
         self.controller3,
         nil];
   
    // delegateを指定することが重要
    self.tabBarController.delegate = self;

    [self.window addSubview:self.tabBarController.view];
    [self.window makeKeyAndVisible];
    return YES;
}

以上のコードで通常のタブバーの表示ができます。
通常のタブバーの使い方ではtabBarControllerのデリゲートを指定する必要はありませんが、この場合は必須です。
ここではタブ2をタッチしたときにモーダルビューが表示されるようにしたいと思います。

デリゲートに指定したためプロトコルを指定します。

@interface FrecoAppDelegate : NSObject
<UIApplicationDelegate,
UITabBarControllerDelegate> {
…
}

そして実装ファイルに以下のコードを加えます。

#pragma mark UITabBarControllerDelegate Methods
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
    if (viewController == self.controller2) {
        UIViewController *controller = [[UIViewController alloc] init];
        [self.controller2 presentModalViewController:controller animated:YES];
        return NO;
    }
   
    return YES;
}

タブをタッチしたときにこのメソッドが呼ばれます。
戻り値はBOOLで、NOを返すことにより、タブ切り替えをキャンセルすることができます。

この場合、self.controller2はほぼダミーになっていますので、ただのUIViewControllerで十分です。実際にはself.controller1,3はUIViewControllerの派生クラスをつかうことになると思います。

もっとスマートな方法をご存じの方がいらっしゃいましたら、コメントで教えていただけるとありがたいです。

Interface Builderを使わずにTab Bar Application

Interface Builderを使わずにTab Bar Applicationを作成する方法です。
意外に忘れやすいので、メモをしておきます。
(環境はXcode4です)

  1. Window-based Applicationでプロジェクト作成
  2. MainWindow.xibを削除
  3. プロジェクトの設定->InfoからMain nib file base nameを削除
  4. main.mを以下のように変更
int retVal = UIApplicationMain
  (argc, argv, nil, @"TabWithoutIBAppDelegate");
  1. TabWithoutIBAppDelegate.h
@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) UITabBarController *tabBarController;
  1. TabWithoutIBAppDelegate.m
@synthesize window = _window;
@synthesize tabBarController = _tabBarController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // 階層的に最も下に位置するwindowを生成
    _window = [[UIWindowalloc] initWithFrame:[[UIScreenmainScreen] bounds]];
    _tabBarController = [[UITabBarControlleralloc] init];

    // TabBarControllerに加える3つのViewController
    ViewController1 *controller1 = [[ViewController1 alloc] init];
    UIViewController *controller2 = [[UIViewController alloc] init];
    UIViewController *controller3 = [[UIViewController alloc] init];
    
    self.tabBarController.viewControllers = 
        [NSArrayarrayWithObjects:controller1, controller2, controller3, nil];
    
    [controller1 release];
    [controller2 release];
    [controller3 release];
    
    [self.windowaddSubview:self.tabBarController.view];
    [self.windowmakeKeyAndVisible];
    returnYES;
}

- (void)dealloc
{
    [_tabBarControllerrelease];
    [_windowrelease];
    [superdealloc];
}
  1. UIViewControllerを追加する
    実際の開発ではUIViewControllerを自作する必要があります。今回はIBを使わないのでloadViewでデザインをします。
- (void)loadView
{
    [superloadView];
    
    self.view.backgroundColor = [UIColorwhiteColor];
    
    UITextView *text = [[UITextView alloc] 
        initWithFrame:CGRectMake(0, 0, 100, 100)];
    [self.view addSubview:text];
    1;
}