2006 年 2 月 3 日 23 時 46 分

ぼかしフィルタ・実装


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


さて、昨日のフィルタを実装してみよう。
フィルタは、カーネルを定義するだけで色々できるので、
3x3 の汎用カーネルを適用できるようにしてみた。

use strict;
use warnings;
use bmp_io;
use integer;

# $value を $min 以上 $max 以下の範囲に収めて返す。
sub limit ($$$) {
    my ($value, $min, $max) = @_;
    if ($value < $min) { $value = $min };
    if ($value > $max) { $value = $max };
    $value;
}

# 32 ビット値を RGB 値に分解する。
sub unpack_rgb ($) {
    unpack('xCCC', pack('N', $_[0]));
}

# RGB 値を 32 ビット値に格納する。
sub pack_rgb ($$$) {
    unpack('N', pack('xCCC', $_[0], $_[1], $_[2]));
}

# カーネルの定義。今回は中央が 8 で周りが 1 のフィルタ。

# 1 1 1
# 1 8 1
# 1 1 1

# 配列の添え字を 0, 1, 2 ではなく、-1, 0, 1 で使いたい。
# Perl 5 では、マイナスの添え字は配列の最後から数える。
# なので、$kernel->[2][2] = $kernel->[-1][-1] である。
# そのため、上の行を下に左の行を右に移動して定義すれば、
# -1, 0, 1 の添え字でカーネルにアクセスできる。
my $kernel = [
    [ 8, 1, 1 ],
    [ 1, 1, 1 ],
    [ 1, 1, 1 ],
];

# カーネルの重みを計算しておく。上の例では 16。
my $kernel_weight = 0;
map { $kernel_weight += $_ } @$_ foreach @$kernel;

# BMP 読み込み。
my $old = read_bmp;

# 高さと幅と得る。
my $height = @$old;
my $width = @{$old->[0]};

# 変換後の BMP を格納する行の配列。
my $new = [];

# 行ごとの処理。
for (my $yy = 0; $yy < $height; ++$yy) {

    # 新しい行を一時的に記憶するピクセル配列。
    my $new_line = [];

    # ピクセルごとの処理。
    for (my $xx = 0; $xx < $width; ++$xx) {

        # RGB 要素ごとに累算する。
        my ($red, $green, $blue) = (0, 0, 0);

        # カーネルを合わせる。
        foreach my $ky (-1 .. 1) {
            foreach my $kx (-1 .. 1) {

                # 計算対象のピクセル。(画像の端を考慮)
                my $oy = limit($yy + $ky, 0, $height - 1);
                my $ox = limit($xx + $kx, 0, $width - 1);

                # ピクセル値を取り出す。
                my ($rr, $gg, $bb) = unpack_rgb($old->[$oy][$ox]);

                # 累算する。
                $red += $rr * $kernel->[$ky][$kx];
                $green += $gg * $kernel->[$ky][$kx];
                $blue += $bb * $kernel->[$ky][$kx];

            }
        }

        # 割り算のために一時的に浮動小数計算。
        no integer;

        # 重みで割って、結果を計算する。
        $red   = int($red / $kernel_weight + 0.5);
        $green = int($green / $kernel_weight + 0.5);
        $blue  = int($blue / $kernel_weight + 0.5);

        # 元に戻す。
        use integer;

        # 1 ピクセル完了。
        push @$new_line, pack_rgb($red, $green, $blue);
    }

    # 1 行完了。
    push @$new, $new_line;

}

# 24 ビットで出力する。
write_bmp($new, 24);



Copyright (c) 1994-2007 Project Loafer. All rights reserved.