2012年5月3日木曜日

アラインメントの罠

大型連休っていいですね!!

ということで日がな一日好きなコードを書き続けています。こんばんは。
ちょっと勉強がてら、OpenCVでカメラからキャプチャした画像をOpenGLで描画しようとして、またしてもハマったのでメモ。

やろうとしたこと

冒頭にも書いた通り、今回はOpenCVで読み込んだ画像をOpenGLで出力-CPPのようなことをやろうとしてました。
他にも類似の記事はたくさん引っかかったので、すぐできるだろー、と思いコードを書くことに。

方針としては、cvQueryFrame()を使ってIplImageを取ってきて、それを毎回glDrawPixels()に渡して描画する、という流れを考えてました。

結果

うまくいきませんでした……orz

cvQueryFrame()で取れた画像をglDrawPixels()に直接渡して描画*1しようとした結果が以下です。

どうしてこうなった……(´・ω:;.:...

原因

設定がおかしいのかとも思いましたが、結局、正しいデータを渡せていないことが原因でした。
cvCreateImage()で作ったIplImageにcvQueryFrame()で取れたIplImageのデータを「ちゃんと」コピーすることで、期待通りの動作をするようになります。

具体的には以下のようにコピーすることでうまく動作しました。
    // _pFrameはcvQueryFrame()で取得したIplImage
    // frameはcvCreateImage()で作成したIplImage↓
    // IplImage *frame = cvCreateImage( cvSize( _pFrame->width, _pFrame->height ), IPL_DEPTH_8U, 3);
    for (int y = 0; y < frame->height; y++) {
        for (int x = 0; x < frame->width; x++) {
            frame->imageData[frame->widthStep * y + x * 3] =
                    _pFrame->imageData[_pFrame->widthStep * y + x * 3];
            frame->imageData[frame->widthStep * y + x * 3 + 1] =
                    _pFrame->imageData[_pFrame->widthStep * y + x * 3 + 1];
            frame->imageData[frame->widthStep * y + x * 3 + 2] =
                    _pFrame->imageData[_pFrame->widthStep * y + x * 3 + 2];
                    
        }
    }

実行結果↓

どうやらcvQueryFrame()で取得できるIplImageには、各行ごとにパディング用の領域が確保されていて、その領域をOpenGLが読み飛ばせずにおかしな描画をしていたようです。
実際、cvQueryFrame()で取れたIplImageとcvCreateImage()で作ったIplImageのwidthStepを見ると、それぞれ、

  • cvQueryFrame()のIplImage->widthStep = 2560 (= 640 * 4)
  • cvCreateImage()のIplImage->widthStep = 1920 (= 640 * 3)

(※ともにwidth=640, nChannels=3)
となっており、確かに余計な領域が入っていることが確認できました。

そんなわけで、cvQueryFrame()でキャプチャした画像を直接OpenGLに渡す場合には気をつけましょうね!




……と、ここまで書いて気づきました。



cvCopy()一発でいけるんじゃ……?



ということで、forループを2周してコピーしている部分を以下のように書き変えました。
    cvCopy(_pFrame, frame);

で、結果↓


……まぁすっきりしたからよしとしましょう!!

結論

  • cvQueueFrame()で取得したIplImageをglDrawPixels()に渡す際は、一度cvCopy()でコピーしておくと良い
  • 変なコードを書く前に調べる


  • *1:cvCloneImage()してコピーした画像を渡したりもしてみた

2012年5月1日火曜日

SBJsonでの自作クラスJSON化

最近色々動けていなかった鬱憤を晴らすかのように2本目です。

何の因果か、ここのところiOSの開発もできるようにならないといけなくてお勉強したりしているわけですが、ちょっとライブラリを使うときにハマったので、軽くがっつりメモしておきます。

iOSでJSON文字列を生成する

google先生によると、どうやらiOSでJSONを使う場合、SBJsonというライブラリを使うのが一般的な様子。
昔はJson.frameworkと呼ばれていたようですが、いまはSBJsonとなっているそうです。

で、2012/05/01現在、SBJsonの最新バージョンは3.1になっていました。
トップページのAPI DocumentationにはVersion 3.1(Alpha)と書かれていますが、githubを見てみるとひと月前くらいにアップデートされているみたいですね。
ちなみに3.0と3.1の差異はARC対応しているかどうかだそうです→http://d.hatena.ne.jp/paraches/20120115/1326650073

ひとまずgithubからバージョン3.1のzipを落としてきて展開します。


インストールの方法はいくつかあるらしいのですが、他でも紹介されているようにソース一式をプロジェクトに追加しました。


これで準備は完了したので、後はコードを書いていきます。

必要なところで#import "SBJson.h"すれば、すぐにパーサが使えるようになるようです。
文字列をオブジェクトに変換するクラスはSBJsonParserで、このクラスのobjectWithStringなどを使うといい感じに変換してくれます。

んで、例えば↓
 [
  {
     "name":"google",
     "url":"www.google.com"
   },
   {
     "name":"yahoo",
     "url":"www.yahoo.com"
   }
 ]
のようなJSON文字列をパースしてオブジェクト化する場合のコードが↓↓
    SBJsonParser *parser = [[SBJsonParser alloc] init];
    NSError *error = nil;
    // jsonStrにJSON文字列が入ってます
    NSArray *resultList = [parser objectWithString:jsonStr error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", jsonStr);
    } else {
        NSLog(@"%@", resultList);
    }
です。

これを実行してみると、
 (
    {
        name = google;
        url = "www.google.com";
    },
    {
        name = yahoo;
        url = "www.yahoo.com";
    }
 )
という感じの出力が得られるはずです。

逆に、配列などをJSONにしたい場合はSBJsonWriterを使います。
例えば、
    SBJsonWriter *writer = [[SBJsonWriter alloc] init];
    NSMutableDictionary *dstDict = [NSMutableDictionary dictionaryWithCapacity:100];
    [dstDict setObject:[NSNumber numberWithDouble:123.27] forKey:@"latitude"];
    [dstDict setObject:[NSNumber numberWithDouble:74.027] forKey:@"longitude"];
    [dstDict setObject:[NSNumber numberWithDouble:0.0001] forKey:@"accuracy"];
    error = nil;
    jsonStr = [writer stringWithObject:dstDict error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", dstDict);
    } else {
        NSLog(@"%@", jsonStr);
    }
のようなコードを実行すると、
{"longitude":74.027000000000001,"latitude":123.27,"accuracy":0.0001}
という文字列を返してくれます。

自作クラスをSBJsonWriterに引数として直接渡す

前フリが長かったですが、ここからが本題です。
SBJsonWriterを使ってJSON文字列を作成する場合、stringWithObjectを使うことになります。
このとき、引数として渡すオブジェクトはなんでもいいのですが、基本的にはNSArrayかNSDictionaryしか渡すことができません。
自作クラスのオブジェクトも渡せますが、普通に渡すとエラーが返ってきます。
しかしどうにかして自作クラスを直接渡したい。

ということで軽くソースを眺めてみると、SBJsonStreamWriter.hの41行目以降に書いてありました。
 If you have a custom class that you want to create a JSON representation for you can implement this method in your class. It should return a representation of your object defined in terms of objects that can be translated into JSON. For example, a Person object might implement it like this: 
 @code
 - (id)proxyForJson {
        return [NSDictionary dictionaryWithObjectsAndKeys:
        name, @"name",
        phone, @"phone",
        email, @"email",
        nil];
 }
 @endcode
ということで、proxyForJsonメソッドを実装すれば良いそうです。

で、実際に実装してみた結果が以下。

MyClassSample.h
@interface MyClassSample : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic) uint age;

- (id)proxyForJson;  // SBJsonを使用して任意のオブジェクトをjsonにする場合,このメソッドを実装する必要がある
@end
MyClassSample.m
#import "MyClassSample.h"

@implementation MyClassSample

@synthesize name;
@synthesize age;

- (id) proxyForJson {
    return [NSDictionary dictionaryWithObjectsAndKeys:
            name, @"name",
            [NSNumber numberWithUnsignedInt:age], @"age", 
            nil];
}

@end
呼び出し側
    MyClassSample *obj1 = [[MyClassSample alloc] init];
    obj1.name = @"name1";
    obj1.age = 21;
    
    error = nil;
    jsonStr = [writer stringWithObject:obj1 error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", obj1);
    } else {
        NSLog(@"%@", jsonStr);
    }
実行結果
{"age":21,"name":"name1"}

ということで、無事に自作クラスをJSON化できました。
ちなみに、自作クラスをNSDictionaryに追加してもちゃんと動作して、
呼び出し側
    MyClassSample *obj1 = [[MyClassSample alloc] init];
    MyClassSample *obj2 = [[MyClassSample alloc] init];
    obj1.name = @"name1";
    obj1.age = 21;
    obj2.name = @"name2";
    obj2.age = 42;
    NSDictionary *userMap = [NSDictionary dictionaryWithObjectsAndKeys:
                             obj1, @"user1", 
                             obj2, @"user2", 
                             nil];
    
    error = nil;
    jsonStr = [writer stringWithObject:userMap error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", userMap);
    } else {
        NSLog(@"%@", jsonStr);
    }
実行結果
{"user2":{"age":42,"name":"name2"},"user1":{"age":21,"name":"name1"}}

という感じになり、こちらも無事にJSON文字列を取ってくることができました。

GLMetaseqとHandyARを使ってみる その0

3日坊主にだけはするまいと思っていたにも関わらず、約一月ぶりの更新です。
こんばんは。
まだまだ仕事のコントロールが出来ない若造でございます。

さて、前回までにHandyARで手のひらを検出するところまで行ってましたが、ちまちま作業をしていた結果、ひとまずミクさんが手のひらに乗ってくれるところまで到達↓

※3Dモデルは――innoce――さんのLat式ミクさんをお借りしました。

タイトルにもありますが、3Dモデルの読み込みにGLMetaseq(元記事)というライブラリを使用し、読み込んだデータをHandyARで検出した座標系を使って表示、という流れになっています。
どう見ても二番煎じです。本当に(ry

で、このあとどうするかが問題で、

  1. アニメーションを適用する
  2. 手の検出精度を上げる
  3. ぜんぜん違うことやる

と3つくらい考えてます。
個人的にはアニメーションが結構ややこしそうなので、手の検出精度を上げるか、ぜんぜん違うことやり始めようかなーと思ってたりします。
が、あんまり決めてなかったりするのでどうなるかは不明です。

ちなみに実装でハマったところとかそのへんは近日中に書くと思います。
GLMetaseq付属のサンプルは画面描画のときにメモリリークしてる気がしたりとか…ね…