2012年6月16日土曜日

OpenCVでオプティカルフローを計算してみる(まだ途中)

前回の続き

間違えてたっぽい箇所:
velx, velyのcvCreateMatの第一、第二引数が逆(かも)

cvCalcOpticalFlowBMの第一、第二引数はprevImg.getCvImage()みたいに取得すべき(多分)

でも動かない。

で、oF + openCVのコード検索してると
こことか
こことかにいっぱいお名前が出てくる
Takashi Maekawaさんの
を調べてみた。

落とすとcvOpFlowBMExampleというディレクトリが入ってる。
src見るとmain.cpp testApp.cpp testApp.hが入ってて
今までみたoFの作法と一緒でそのままビルドできそう。
できなかった。
testApp.hの中でofCvColorImageとかofCvGrayscaleImageとかの名前のヘッダー読み込んだり同名の型でメンバー宣言してるけど
oFについてきたアドオン、ofxOpenCvが定義してるのは
ofxCvColorImageとかofxCvGrayscaleImageしかない。

アドオン側の名前が変わったのだと信じて、Maekawaさん側のコードにxを足していくと、とりあえずビルドはできた。
が、cvCalcOpticalFlowBMの呼び出しでcv::Exception出て止まるなあ…

http://edotprintstacktrace.blogspot.jp/2009/10/cvcalcopticalflowbm.html
を試してもダメ。

ぎゃー前進してない!

とりあえず、探した限りでは「そのまま動くoF+openCVでのサンプル」は無かった。
動作するコードが無いことには話にならないので、次はoFから一旦離れてopenCVだけのサンプルコードを探してみる。もちろん動くやつ。

メモ
http://opencv.jp/sample/optical_flow.html
http://www40.atwiki.jp/chugoku/pages/114.html

続く

2012年6月11日月曜日

OpenCVでオプティカルフローを計算してみる(途中)

openFrameworks2回目。

まだあまり良くわかってない。

とりあえず普段あまりやらないことやってみたいので、OpenCV経由でオプティカルフローを計算してみる。

1. "OpenCV openFrameworks オプティカルフロー"とかで検索

2. 以下のページが引っかかる。
http://opencv.jp/sample/optical_flow.html

3. ブロックマッチングによるオプティカルフローの計算、これy
でも多分これoF関係なくてOpenCV直だな、と思う。

4. ダメ元でcvCalcOpticalFlowBMって打ってみる。
コードヒント動くので、そのままの名前で使えるっぽい。
じゃああとは引数を適切に渡してみる。

こうなった。


#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup() {
    #ifdef _USE_LIVE_VIDEO
        vidGrabber.setVerbose(true);
        vidGrabber.initGrabber(640, 360);
    #else
        vidPlayer.loadMovie("sushi.mp4");
        vidPlayer.play();
    #endif
    
    blockSize = cvSize(10, 10);
    shiftSize = cvSize(1, 1);
    maxRange = cvSize(50, 50);
    
    velx = cvCreateMat(64, 36, CV_32FC1);
    vely = cvCreateMat(64, 36, CV_32FC1);
    
    currImg.allocate(640, 360);
    prevImg.allocate(640, 360);
}

//--------------------------------------------------------------
void testApp::update() {
    ofBackground(100, 100, 100);
    
    bool bNewFrame = false;
    
    #ifdef _USE_LIVE_VIDEO
        vidGrabber.grabFrame();
        bNewFrame = vidGrabber.isFrameNew();
    #else
        vidPlayer.idleMovie();
        bNewFrame = vidPlayer.isFrameNew();
    #endif
    
    if (bNewFrame) {
        prevImg = currImg;
        
        #ifdef _USE_LIVE_VIDEO
            currImg.setFromPixels(vidGrabber.getPixels(), 640, 360);
        #else
            currImg.setFromPixels(vidPlayer.getPixels(), 640, 360);
        #endif
        
        cvCalcOpticalFlowBM(&prevImg, &currImg, blockSize, shiftSize, maxRange, 0, velx, vely);
    }
}

//--------------------------------------------------------------
void testApp::draw() {
    ofSetHexColor(0xFFFFFF);
    currImg.draw(20, 20);
    prevImg.draw(20, 380);
}


で、実行するとcvCalcOpticalFlowBMで例外出る。

prevImg、currImgは多分合ってる。
元のコードではIplImage *だったけど、ここによると特に変換いらないよとのこと。

blockSize、shiftSize、maxRangeは元のコードと一緒で10, 10と1, 1と50, 50を指定している。意味的にも問題なさそう。

velx, velyはちょっと自信ない。画像のサイズはいま640, 360なので64, 36の行列で初期化しているけど、縦横blockSizeで割るんじゃなくてshiftSizeで割ると動くよという説もあった(いまChromeのタブと履歴見たけどどこにもない。幻覚か)
もちろん試したけど動かない。意味的にもおかしい気がする。

続く

2012年6月7日木曜日

いまさらoF触ってみた

Twitterを1ヶ月以上放置してしまった。
ブログについても、いっぺん離れると戻るのが大変なので
内容はともかく書き続けるというのをやってみようと思いますー

で、突然だけどoF始めてみた。

始めてみた?今までやってなかったの?
やだ吉川さん意外〜。

はいそうです。
まだの人、一緒にやりましょう。


2011年9月24日土曜日

GAEでIf-Modified-Sinceに応答

だいぶ前になってしまいましたが、GAEの課金体系の変更が話題になりました。

一番大きな変更を大雑把にまとめると
  • CPU時間ごとの課金($0.10/CPU hour、毎日6.5時間まで無料)が無くなる
  • インスタンス時間ごとの課金($0.08 / hour、毎日28時間まで無料)が導入される
という感じ。

CPU時間というのは、CPUが計算を行っている時間。そのまま。
対してインスタンス時間というのは何か。

GAEでは、アプリケーションがGoogleのサーバで動いてて、ユーザからのリクエストに応答します。これがインスタンス。
リクエストが増えて応答に時間がかかるようになると、GAEのスケジューラは新しいインスタンスを起動してリクエストの処理に当たらせます。なのでインスタンス時間とは、全インスタンスが起動している「のべ」の時間。

で、それぞれ具体的にどんな数字が入るかというと、例えばdrawlrのケースでは

CPU時間: 0.1~0.6時間/日 ぐらい
インスタンス時間: 100時間/日 ぐらい

なので、改定前だと毎日無料、改定後だと毎日$5.76かかる計算に。やってられん!

ということで、GAEユーザの間で、どうするんだみたいな状況になっているようです。経緯説明終わり。

で、どうするんだ

まあ僕もGAEで公開しているサイトがいくつかあるのですが、既にチューニングしきってて大量のアクセスをギリギリ捌いているならともかく全然そんなレベルではないので、改定後の無料割り当てに収まるように地道に工夫してこうかと。

まずはキャッシュをちゃんと使ってもらう

こっから本題です。言うまでもなくHTTPにはCachingという仕組みがあります。

GAEでは、staticなリソースに対しては最初からキャッシュが効くよう動いてくれますが、ハンドラを使った動的な応答では、自前でキャッシュが効くようにレスポンスヘッダを設定する必要があります。

例えばdrawlrのトップページには20枚のサムネイルが表示されていますが、これらはデータストアに保存されているものをハンドラが動的に返しています。特に何もしていない実装だと、ビジターがページを開くたびに20枚のサムネイルがリクエストされ、画像がデータストアから取得され、ネットワークをデータが流れ、その全てのステップで僕の口座からお金が減ります。こいつはいけない!

何もしていない状態のハンドラ
class ImageHandler(webapp.RequestHandler):
  """ 画像出力 """
  def get(self, drawing_id):
    drawing = Drawing.get_by_key_name(drawing_id)
    self.response.headers['Content-Type'] = 'image/png'
    self.response.out.write(drawing.image)

実際はもう少し複雑ですが、大体こういうことをしています。

では、ブラウザにキャッシュしてもらうためには何をすれば良いか。
  • Expiresレスポンスヘッダを設定する
  • Cache-Controlレスポンスヘッダを設定する
  • Last-Modifiedレスポンスヘッダを設定し、次からのIf-Modified-Sinceリクエストヘッダを正しく処理する
これら全てを行います。
(本当はLast-Modifiedより利点のあるETagというのもあるのですが、また今度。)

Expiresは、レスポンスの有効期限を指定するHTTP/1.0のヘッダです。
ここで指定した期日までレスポンスは有効、言い換えればサーバへの確認なしにキャッシュを信じて良い期間を指定しています。例えば以下の例では、キャッシュされたレスポンスが10日間はサーバへの問い合わせを行わずに再利用されることが期待できます。
expires = datetime.datetime.now() + datetime.timedelta(days=10)
self.response.headers['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')

Cache-Controlは、キャッシュをどのように行わせるかを詳細に指示するHTTP/1.1のヘッダです。
以下のように指定することで、やはり10日間の有効期限を指定しています。
publicの指示は、複数ユーザが使う共有キャッシュに入れても良いことを示します。このレスポンスが今回のクライアントの為だけに用意されたものであれば、代わりにprivateを指示します。
self.response.headers['Cache-Control'] = 'public, max-age=864000'

Last-Modifiedは、今回のエンティティ(この場合はdrawing)が最後に更新された時間を指定するヘッダです。drawlrの場合は、drawing.updated_at というプロパティがあるのでそれを使っています。
self.response.headers['Last-Modified'] = drawing.updated_at.strftime('%a, %d %b %Y %H:%M:%S GMT')
上記で指定した有効期限が切れた場合などで"キャッシュの正当性"を検証する必要がある場合、ブラウザは前回のLast-Modifiedの値をIf-Modified-Sinceリクエストヘッダに入れてリクエストを行います。
この際、サーバがステータスコード304(Not Modified)を返せば、キャッシュは引き続き正当となり、画像自体がネットワークを流れる分の帯域と時間の節約になります。

これらを実装して、こんな感じになりました。
class ImageHandler(webapp.RequestHandler):
  """ 画像出力 """
  def get(self, drawing_id):
    expires = datetime.datetime.now() + datetime.timedelta(days=10)
    self.response.headers['Last-Modified'] = drawing.updated_at.strftime('%a, %d %b %Y %H:%M:%S GMT')
    self.response.headers['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
    self.response.headers['Cache-Control'] = 'public, max-age=864000'
    if 'If-Modified-Since' in self.request.headers:
      cached = datetime.datetime.strptime(self.request.headers['If-Modified-Since'], '%a, %d %b %Y %H:%M:%S GMT')
      current = drawing.updated_at - datetime.timedelta(seconds=1)
      if cached > current:
        self.response.set_status(304)
        return
    drawing = Drawing.get_by_key_name(drawing_id)
    self.response.headers['Content-Type'] = 'image/png'
    self.response.out.write(drawing.image)
※drawlrの場合は描き足しがあるので、10日ではなくかなり短い時間にしていますが、描き足しがない場合は365日とかで良いはず。


動いているか確認

キャッシュされてない状態で訪問。画像の応答に約1.5秒、ステータスは200。

キャッシュされた状態で再訪問。画像のリクエストは行われない。(Sizeの欄にfrom cacheと出る)

キャッシュされた状態でリロード。画像の応答に約0.2秒、ステータスは304。

実際にページを再訪問(リロードせずにTopのリンクを開くなど)すると効果が体験できます。
応答が早い時間で終わるほど、一つのインスタンスが多くのリクエストを捌けるので、こういった工夫を積み重ねればまだまだGAEを使えるんじゃないかな!

次回はこれを応用して、JavaScriptコードをごにょごにょする記事を書きます。

2011年8月30日火曜日

drawlrのデータをWebGLで描画(Google Chromeのみ)

先週の8/21、グリッチワークショップのアフターパーティにて。


音で絵を動かしたい欲が高まり、なにか作りだす。

現場でできたものを調整したのがこれ。

http://www.drawlr.com/assets/experiments/webgl-gc.html

※WebGLの動くマシンじゃないと見れないです。
※Web Audio APIを使ってるので、設定を変えたGoogle Chromeじゃないと動かないです。

Web Audio APIの設定のしかた
  1. アドレスバーに about:flags と打ち込む
  2. 「ウェブ オーディオ」の項目を有効にする

もうちょっと整理して、ひとつの絵をgl.drawArrays一発で描くようにしたのがこちらなので、ソース見るならこちらでお願いします。でも表現はまだ微妙に前のほうが良い。

http://www.drawlr.com/assets/experiments/webgl-gc2.html

それと、drawlrに描いていただいた絵を使用しましたが、もし使われたくない!という方がいましたらTwitterにてお知らせください。

(将来的には、drawlrの機能として二次利用の可否を表明できるようにしたいです)

2011年7月14日木曜日

drawlrオープンしました。

前回の投稿と前後してしまいましたが、そういうわけで先週の7/8に、drawlrというサイトをオープンしました。

まずは突然これを作った経緯について説明しますと
というのが発端です。そのときのURLはこれで、「描いた絵に途中から描き足せる」「描いた手順を全て記録・再生できる」という特徴はこの段階でついていました。

当初はこの段階でさらっと紹介して終わるところだったのですが、絵を描いてくれた見富君に予想外に気に入ってもらえたので(?)機能を整え、デザインを@sskhybridさんにお願いして、サイトとして公開することになり。

ちょこちょこ機能を相談しながら作っていったら2ヶ月も経ってしまいましたが、その分「こういう機能あったらいいよね」を盛り込めたし、今後も増やしていける形にできました。

技術的には、HTML5 Canvas、JavaScript、Google App Engine/Pythonあたりで作っています。ので、重さを気にしなければiPhoneからでも再生・投稿できます。(まだiPhoneに対し最適化していないので、もっとスムーズに使えるようにはしたいところ)

いきなり多くの人に見てもらえたので、公開後1時間経たずにGoogle App Engineの無料割り当て分を突破してしまいました(ちなみに初日のサーバ代は$1.17でした)。ありがとうございます。

今後も隙を見て機能増やしていきますので、欲しい機能ありましたら僕 @psyark まで気軽にご意見ください。

2011年7月10日日曜日

ベクターデータ

説明が前後してしまいますが。



いまベクターデータを取得するプログラムを書いたので、そのうち何らかの形で機能にしたい。

drawlr自体の説明は次のエントリーで!