Perlで窓関数を描く

FIRフィルタとか使う時に必要なので、描いてみた。

use v5.14;
use strict;
use warnings;

use Imager;
use Math::Trig qw/pi/;

use constant STEP_X => 4;
use constant SCALE_Y => 256.0;
use constant N => 101;

my ( $width, $height ) = ( 601, 400 );
my ( $x0, $y0 ) = ( int($width / 2), ($height - 50) );

{
    my $img = Imager->new(
        xsize => $width, ysize => $height, channels => 4 );
    draw_graduation( $img, Imager::Color->new(64,64,64) );

    draw_window( $img, kaiser_window(N, 0.000), 'red' );
    draw_window( $img, kaiser_window(N, 3.395), 'green' );
    draw_window( $img, kaiser_window(N, 5.653), 'orange' );
    draw_window( $img, kaiser_window(N, 7.857), 'yellow' );

    $img->write( file => sprintf('kaiser_%02d.png', N) ) or die $img->errstr;
}

{
    my $img = Imager->new(
        xsize => $width, ysize => $height, channels => 4 );
    draw_graduation( $img, Imager::Color->new(64,64,64) );

    draw_window( $img, hamming_window(N), 'red' );
    draw_window( $img, hann_window(N), 'green' );
    draw_window( $img, kaiser_window(N, 7.857), 'yellow' );

    $img->write( file => sprintf('window_%02d.png', N) ) or die $img->errstr;
}

sub kaiser_window {
    my ( $n, $alpha ) = @_;

    my @ret = map {
        bessel( $alpha * sqrt(1.0 - ($_ ** 2.0)) ) / bessel( $alpha );
    } map {
        ($_ - int($n / 2)) / int($n / 2);
    } 0..($n - 1);

    return \@ret;
}

sub hann_window {
    my $n = shift;

    my @ret = map {
        my $x = 2.0 * pi() * $_;
        0.5 + (0.5 * cos($x));
    } map {
        ($_ - int($n / 2)) / ($n - 1);
    } 0..($n - 1);

    return \@ret;
}

sub hamming_window {
    my $n = shift;

    my @ret = map {
        my $x = 2.0 * pi() * $_;
        0.54 + (0.46 * cos($x));
    } map {
        ($_ - int($n / 2)) / ($n - 1);
    } 0..($n - 1);

    return \@ret;
}

sub bessel {
    my $x = shift;

    my $total = 1;
    my $factorial = 1;
    foreach my $n ( 1..20 ) {
       $factorial *= $n;
       my $tmp = (($x / 2) ** $n) / $factorial;
       $total += ($tmp * $tmp);
    }

    return $total;
}

sub draw_window {
    my ( $img, $data, $color ) = @_;

    my $img_tmp = Imager->new(
        xsize => $img->getwidth(), ysize => $img->getheight(), channels => 4 );

    my $n = scalar( @{$data} );
    for (my $i=0; $i<$n; $i++) {
        my $dx = $i - int($n / 2);
        my $dy = int($data->[$i] * SCALE_Y);

        $img_tmp->line(
            x1 => ($x0 + ($dx * STEP_X)), y1 => $y0,
            x2 => ($x0 + ($dx * STEP_X)), y2 => ($y0 - $dy),
            color => $color );
    }

    $img->compose( src => $img_tmp, opacity => 0.25, combine => 'add' );

    for (my $i=0; $i<$n; $i++) {
        my $dx = $i - int($n / 2);
        my $dy = int($data->[$i] * SCALE_Y);

        my ( $x, $y ) = ( $x0 + ($dx * STEP_X), $y0 - $dy );
        $img->box(
            xmin => $x - 1, ymin => $y - 1, xmax => $x + 1, ymax => $y + 1,
            color => $color, filled => 0 );
    }
}

sub draw_graduation {
    my ( $img, $color ) = @_;

    $img->box( filled => 1, color => 'black' );

    $img->line(
        x1 => $x0, y1 => $y0,
        x2 => $x0, y2 => 0,
        color => $color );
    $img->line(
        x1 => 0, y1 => $y0,
        x2 => ($width - 2), y2 => $y0,
        color => $color );
}

カイザー窓の元ネタはこの本だけど、ハン窓とハミング窓は調べて追加した。

カイザー窓はこんな感じ。
20160430-1

ハミング窓(赤)、ハン窓(緑)、カイザー窓(黄)はこんな感じ。
20160430-2

で、さっきの本だとFIRフィルタの係数を算出できなくて困ったので、
家にある適当な本を使って算出した。
たぶん、sinc関数を使うべきところで、sinc関数に触れてなかったので、
最初はソースコードが載っている入門本が良いと思う。

追記 2016/06/04
ソースコードの59行目と72行目の分母が間違ってたので修正しました。
なので、2枚目のグラフも差し替えました。
— ここまで追記 —

おしまい。

Leave a Comment