kaeruspoon

サムネイル画像高速作成ツールspeedpetalの開発 - その3 バイキュービック法を試してみた

今度はバイキュービック法を試してみました。バイキュービック法は元画像のポイントの周囲16ピクセルも調べて、それぞれのピクセルの色に距離による重みを加えた上で新しい画像のポイントの色にするというやり方です。当然、ニアレストネイバー法よりもコストがかかります。
一般的にバイキュービックは以下のような式で重みを導き出します。

t : ターゲットの座標と対象のピクセルとの距離
0<= |t| < 1 なら
  (a + 2)|t|^3 - (a + 3)|t|^2 + 1
1<= |t| < 2 なら
  a|t|^3 - 5a|t|^2 + 8a|t| - 4a
2 <= |t| なら
  0

aは定数で、-1としている人が多いようです。ただ、ぼくが試したときは画像によってノイズが出るときがありました。-0.5くらいがいいのかも。また、処理速度もかなり遅くなります。その上、ぼくが見るかぎりはニアレストネイバー法と大きく差違を感じませんでした。高速にサムネイルを作成するという目的から、ニアレストネイバー法を採用しようと思います。
ちなみにバイキュービック法によるサムネイルの作成のコードは以下のような感じです。

// 重み計算
double color_weight(double origin, int target) {
    double distance = fabs(origin - target);
    double weight = 0.0;

    if (distance < 1.0) {
        weight = (CONST_W + 2) * pow(distance, 3.0) - (CONST_W + 3) * pow(distance, 2.0) + 1;
    } else if(distance < 2.0) {
        weight = CONST_W * pow(distance, 3.0) - 5 * CONST_W * pow(distance, 2.0) + 8 * CONST_W * distance - 4 * CONST_W;
    }
    return weight;
}

// バイキュービック法による計算
void bicubic(JSAMPARRAY in_buffer, JSAMPARRAY out_buffer, int origin_width, int origin_height, int x, int y, double origin_x, double origin_y, int components) {
    int nearest_x = (int)floor(origin_x);
    int nearest_y = (int)floor(origin_y);

    double red = 0.0;
    double blue = 0.0;
    double green = 0.0;
    for(int round_x = nearest_x -1; round_x <= nearest_x + 2; round_x++){
        for(int round_y = nearest_y - 1; round_y <= nearest_y + 2; round_y++){
            if(round_x >= 0 && round_x < origin_width && round_y >= 0 && round_y < origin_height) {
                double weight_x = color_weight(origin_x, round_x);
                double weight_y = color_weight(origin_y, round_y);
                red += in_buffer[round_y][round_x * components] * weight_x * weight_y;
                green += in_buffer[round_y][round_x * components + 1] * weight_x * weight_y;
                blue += in_buffer[round_y][round_x * components + 2] * weight_x * weight_y;
            }
        }
    }
    int n_red = (int)floor(red + 0.5);
    int n_green = (int)floor(green + 0.5);
    int n_blue = (int)floor(blue + 0.5);
    n_red = n_red > 255 ? 255 : n_red;
    n_green = n_green > 255 ? 255 : n_green;
    n_blue = n_blue > 255 ? 255 : n_blue;
    out_buffer[y][x * components] = n_red;
    out_buffer[y][x * components + 1] = n_green;
    out_buffer[y][x * components + 2] = n_blue;
}

// サムネイル生成
void create_thumbnail(JSAMPARRAY in_buffer, JSAMPARRAY out_buffer, int origin_width, int origin_height, int width, int height, int components, int algorithm) {
    double width_factor = (double)origin_width / width;
    double height_factor = (double)origin_height / height;
    double width_pos[width];

    for(int x = 0; x < width; x++) {
        width_pos[x] = x * width_factor;
    }

    for(int y = 0; y < height; y++) {
        double height_pos = y * height_factor;
        for(int x = 0; x < width; x++) {
            bicubic(in_buffer, out_buffer, origin_width, origin_height, x, y, width_pos[x], height_pos, components);
        }
    }
}

Speedpetalがとりあえず完成

今まで作ってきたspeedpetalRubyの拡張ライブラリSpeedpetalとして作ってみた。

require 'speedpetal'
Speedpetal::resize(100, "in.jpg", "out.jpg")
Speedpetal::resize_square(100, "in.jpg", "out_square.jpg")

という感じでモジュールメソッドで作りました。まだコードが汚いし、速度のチューニングをしていない素の状態なので、そのあたりが一段落したらオープンソースで出そうかと思います。本当は今日中に出すつもりだったのですが、ユルさんのためにカレーを作るという大切な仕事を優先したのでした。

とりあえず現時点でのベンチマークです。
RMagickの場合

             user     system      total        real
RMagick  4.340000   2.410000   6.750000 (  6.779993)
avg      0.043400   0.024100   0.067500 (  0.067800)

Speedpetalの場合

             user     system      total        real
speedpetal  0.400000   0.040000   0.440000 (  0.469999)
avg      0.004000   0.000400   0.004400 (  0.004700)

14.4倍ほど、RMagickより高速です。まだまだいけそう。

ツールやライブラリは個人的に作った方がいい

仕事で作ってもなかなか表に出せなかったりします。個人的に使いたかったりするのですが。これからは仕事でツールやライブラリを作るのはやめておきましょう。

speedpetalRubyのライブラリとして公開したあとは、apacheのモジュールにしてみようかと思います。もともとは会社の同僚の人のアイデアなのです。

webサービスなどよりも、ちょっとしたライブラリとかを作るほうがぼくには合っている気がします。C言語はやっぱり楽しいね。もっとCでRubyのライブラリを作りたいな。

勉強が止まってますが、Erlangもちゃんとやりましょう。

今日から早起き

今日から5時に起きて仕事に行くことにしました。会社に着くのが7時過ぎです。早く行って早く帰りたいお年頃なのだ。いいことがたくさんあるので、最初からやっておけばよかったです。

都内は雨だったのに帰ってきたらめっちゃ雪が降っていて積もったりしていました。しろさんは大丈夫でしょうか。

会社で風邪をうつされました。speedpetalの続きをやろうと思ったけど頭が働かなくなっていたのでやめました。カピバラを見て英気を養います。

プログラミング禁止令

帰ってきたらユルさんからプログラミング禁止令が発令されてしまいしまた。暴君による戒厳令みたい。しょうがないのでニコニコでも見てすごします。

ライブラリやツールは個人的に作ったほうがよい、などと言っておきながら、舌の根も乾かぬうちに仕事でRailsプラグインを作ったりしています。こういうものって、やっぱり仕事だからこそ欲しい機能に気づかされるという面があって、そうすると家に帰って作るのなんて待ってられないから仕事中に作ってしまうのでした。

作ったプラグインはコントローラ中でアクションメソッドと例外処理を分離して記述できるという単純なもの。rescue_fromみたいに例外単位に補足するようなものではなく、アクション単位で分けて書けるというだけです。開発していて、例外処理がアクションメソッドのお尻についているのがどうも汚く思えて我慢ならなくなってしまったのでした。作ってから、絶対どこかで誰かが作ってそうだなと思い至りましたが、おもしろかったのでよしとしましょう。

Rails2.3もそろそろリリースでしょうか。kaeruspoonRailsを継続してバージョンアップしてきましたが、2.3が出たら新規に作り直そうかと思います。

高速にサムネイル画像を生成するRubyライブラリ、Speedpetalを公開しました。

Speedpetalは高速にサムネイル画像を生成できるRubyのライブラリです。現在のところ、jpegのみに対応しています。

1.インストール

sudo gem install tsukasaoishi-speedpetal

gemのソースにGitHubを加えていないときは以下を先に実行してください。

gem sources -a http://gems.github.com


2.使い方
以前にも書きましたが、以下のとおりです。

require 'rubygems'
require 'speedpetal'
Speedpetal::resize(100, "in.jpg", "out.jpg")
Speedpetal::resize_square(100, "in.jpg", "out_square.jpg")

簡単。

Rubyforgeに絶望してGitHubで公開しました。GitHubはシンプルで触り心地がいいですね。ぼくのソースは全部GitHubに集約しようっと。

THERMOS ステンレスランチジャー クールグレー JLS-1601F CGY: ホーム&キッチン

THERMOS ステンレスランチジャー クールグレー JLS-1601F CGY

高校生の頃、みんながこれを持っていました。ぼくは小食なのでこんなに大きな弁当は食べられないのですが、ちょっとうらやましくも思っていたりしました。

アメリカで象印の保温弁当箱「Mr. Bento」が大好評【米amazon顧客レビュー】

Rails2.3 RC2をインストール

正式リリースを待てなかったので、RC2をインストールしました。最近、Railsの新機能などを追いかけていなかったのでここら辺で一気に取り返そうと思います。

sudo gem install rails --source http://gems.rubyonrails.org

Rails2.3からRackを使うようになったので、当然Rackも必要です。ぼくはすでにRackを持っているのですが、持っていない人は

sudo gem install rack

でインストールしてください。

風邪が治らない

どうも風邪がなおらなくてずっとダルい今日この頃です。ぼくは風邪をひくと熱いお風呂に入って汗をかいて毛布いっぱいくるまって汗をかいてとにかく水を大量に飲んで汗をかいてなおしていたりしたのですが、最近は年のせいかそういうヘヴィなことができなくなってきています。なので薬を飲んでごまかしつつなおしているのです。

過去に書きためた小説をまとめて文庫本でも作ろうかなと思っていたりします。

会社の冷蔵庫に納品されるアイスが本当にふざけています。ぷんぷん。パナップをどうしていれないのか。かといって、グリコのお姉さんに「パナップをいれてください」とお願いするのも年齢が年齢だけに恥ずかしくてできない今日この頃です。

今年の夏くらいにサーバを一新したいなと考えています。静音・省電力・省スペースで作りましょう。そういえば、昔つかっていたVAIO typeFというノートPCが余っているですが、微妙に使い道がなくて困っています。分散処理でもさせようかな。

毎朝、MXTVのTOKYOモーニングサプリを観るのが楽しみです。

ActiveRecordのようにTokyoTyrantを使う、MiyazakiResistanceを作りました

TokyoTyrantをActiveRecordのように気軽に使える、MiyazakiResistanceを作りました。Cabinet(内閣)、Tyrant(傀儡)という意味を見て、地方から傀儡政権に抵抗するというイメージが勝手にわいてきたのでした。
MiyazakiResistanceは、TokyoTyrantのテーブルデータベースを対象にしています。データスキーマのないTokyoTyrantに、あえてスキーマの制約を与えることで管理をしやすくしています。TokyoTyrantでは異なる種類のデータをひとつのデータベースに置くことはあまりないと想定してのことです(キー値がかぶるし)。
MiyazakiResistanceは、レプリケーションにも対応しています。マスターと複数のスレーブ構成でも、デュアルマスタ構成でも大丈夫です。デュアルマスタはアクティブ-スタンバイで運用することを想定していて、アクティブ側がタイムアウトしたときはスタンバイ側にフェイルオーバーします。
まだ開発中なのでバグはあちらこちらに残っているのですが、適宜解消していく予定です。
github上で公開しています。http://github.com/tsukasaoishi/miyazakiresistance/tree/master

1.インストール(gemcutterで公開しています)

sudo gem install miyazakiresistance


2.モデルクラスの定義

require 'rubygems'
require 'miyzakiresistance'
class Example < MiyazakiResistance::Base
  set_server "localhost", 1975, :write
  set_timeout 60
  set_column :name, :string
  set_column :age, :string
  set_column :created_at, :datetime
end

MiyazakiResistance::Baseを継承して定義します。

set_server でTokyoTyrantのサーバーを指定します。第一引数がホスト名、第二引数がポート番号です。第三引数はオプションで指定しなければ:readonlyと解釈されます。:writeは読み書き両用のサーバーです。
マスターと複数のスレーブで構成するときは、

  set_server "master", 1975, :write
  set_server "slave1", 1975
  set_server "slave2", 1975

と宣言します。読みのときにどのサーバーが選択されるかはランダムに決定されます。
デュアルマスタ構成のときは、

  set_server "active", 1975, :write
  set_server "standby", 1975, :standby

:standbyで指定されたサーバには、読み込みのみアクセスします。さらにフェイルオーバーの対象に指定されます。

set_timeout でタイムアウト値を宣言します。単位は秒です。タイムアウト値を超えると、対象のサーバーはコネクションプールから排除されます。対象のサーバーがマスタの場合、デュアルマスタでスタンバイが存在しているときはフェイルオーバーします。スタンバイが存在しなければ例外を吐きます。

set_columnでデータ構造を宣言します。キー値はself.idとして規約で定義されているので宣言の必要はありません。第一引数にカラム名、第二引数にデータの型を指定します。データの型は、:string(文字列)、:integer(数値)、:date(日付)、:datetime(日時時刻)の4種類です。:integerは実際には実数でも問題ありません。:dateはDateクラス、:datetimeはTimeクラスに対応しています。

実際のCRUD操作はActiveRecordとほぼ同様です。conditionsの書き方だけが少し違っていて、条件同士は必ずAND結合されるので、条件の間にAND, ORなどを書く必要はありません。

work = Example.new(:name => "tsukasa")
work.age = 34
work.save
Example.count
Example.find(:all, :conditions => ["name = ? age = ?", "tsukasa", 34], :order => "created_at DESC", :limit => 10)

TokyoTyrantのオプティマイザは最初に型が一致したカラムのインデックスを使用します。

Example.find(:all, :conditions => ["created_at >= ? created_at <= ?", yesterday, today])

だと、最初の created_at > ? にはインデックスが適用されますが、二つ目の条件 created_at < ?にはインデックスが適用されません。この場合は、

Example.find(:all, :conditions => ["created_at between ?", [yesterday, today]])

とすればいいでしょう。

Tokyoモーニングサプリが夜の時間帯に移動

090325golden.pdf (application/pdf オブジェクト)

もう観られないと思ってショボーンとしてたら、こんなピックニュースが。あのお別れ会みたいな雰囲気はなんだったんだろう。朝だと出勤の時間もあって途中までしか観られなかったんだけど、夜ならずっと観られるね。すばらしい。楽しみです。

レプリケーションでバックアップ

kaeruspoonのDBのバックアップはmysqldumpで定期的に行っていたのだけど、開発環境のcoLinux上のMySQLをスレーブにして、レプリケーションでバックアップを取ることにしました。
MySQLのレプリケーションに最近ほとんど触っていなかったので、すっかりいろいろ忘れていました。定期的に触ってないといけませんね。
ついでに、リンク元の集計に使っているTokyoTyrantも、スレーブをcoLinux上に置いてバックアップをとることにしました。