Imagerでラベリングした領域を整列する

コードが長ったらしくて申し訳ないんだけど、
それでも完成させたいので、見て見ぬふりして頂ければと思います。

全部、載せてしまうけど、こんな感じ。

use v5.14;
use strict;
use warnings;
use Imager;

if ( (not @ARGV) or (not -e $ARGV[0]) ) {
    say "Usage:
    perl $0 file_path";
    exit( 0 );
}

my $img = Imager->new( file => $ARGV[0] )
    or die Imager->errstr();
my @images = ();

my $h = $img->getheight();
my ( $ix, $iy ) = ( 0, 0 );
my $area_no = 1;
while ( $iy < $h ) {
    my $tmp = $img->getsamples( y => $iy, channels => [0] );
    my @pixels = unpack( 'C*', $tmp );

    my $found = 0;
    while ( $ix < scalar(@pixels) ) {
        if ( $pixels[$ix] == 255 ) {
            my $c = Imager::Color->new( $area_no, $area_no, $area_no );
            $img->flood_fill( x => $ix, y => $iy, color => $c );

            my $area = calc_filled_area( $img, $area_no, $ix, $iy );
            print_area_info( $area_no, $area );

            my $cropped_img = $img->crop(
                left   => $area->{xmin},
                top    => $area->{ymin},
                width  => $area->{xmax} - $area->{xmin} + 1,
                height => $area->{ymax} - $area->{ymin} + 1
            );
            $cropped_img = Imager::transform2( {
                channels => 1,
                constants => { area_no => $area_no },
                rpnexpr => 'x y getp1 !pix @pix red area_no eq @pix 0 0 0 rgb ifp'
            }, $cropped_img );

            $cropped_img or die $Imager::ERRSTR;

            my $angle = get_angle( $cropped_img, $area_no );

            # 回転によって出来たアンチエイリアスを任意の色に置き換え
            my $new_img = Imager::transform2( {
                channels => 1,
                rpnexpr => 'x y getp1 !pix @pix red 0 eq @pix 255 255 255 rgb ifp'
            }, $cropped_img->rotate(degrees => $angle, back => 'black') );

            $new_img or die $Imager::ERRSTR;

            push @images, $new_img;

            $area_no++;
            $found = 1;
            last;
        }

        $ix++;
    }

    if ( not $found ) {
        $ix = 0;
        $iy++;
    }

    if ( 255 < $area_no ) {
        die 'area_no = ', $area_no, ' too many areas! sorry.';
    }
}

my $margin = 4;
my ( $dst_width, $dst_height ) = ( 0, 0 );
my @cropped_images = ();
foreach my $img ( @images ) {
    my $area = detect_filled_area( $img, 255 );
    my $w = $area->{xmax} - $area->{xmin} + 1;
    my $h = $area->{ymax} - $area->{ymin} + 1;
    my $cropped_img = $img->crop(
        left => $area->{xmin}, top => $area->{ymin}, width => $w, height => $h );

    printf( "w = %4d, h = %4d, h / w = %4.1f\n", $w, $h, ($h / $w) );
    if ( ($h / $w) < 3.0 ) {
        next;
    }

    push @cropped_images, $cropped_img;
    $dst_width += $cropped_img->getwidth() + ($margin * 2);
    if ( $dst_height < $cropped_img->getheight() ) {
        $dst_height = $cropped_img->getheight();
    }
}

$dst_height += ( $margin * 2 );
my $img_dst = Imager->new(
    xsize => $dst_width, ysize => $dst_height, channels => 4 );
$img_dst->box( color => 'black', filled => 1 );
my $x = $margin;
foreach my $img ( @cropped_images ) {
    my $y = $img_dst->getheight() - $img->getheight() - $margin;
    $img_dst->paste( left => $x, top => $y, src => $img );
    $x += ( $margin + $img->getwidth() + $margin );
}
$img_dst->write( file => $0 . '.png' );

sub calc_filled_area {
    my ( $img, $area_no, $filled_x, $filled_y ) = @_;

    my ( $xmin, $xmax ) = ( $filled_x, $filled_x );
    my ( $ymin, $ymax ) = ( $filled_y, $filled_y );

    my $h = $img->getheight();
    my $iy = $ymin;
    while ( $iy < $h ) {
        my $tmp = $img->getsamples( y => $iy, channels => [0] );
        my @pixels = unpack( 'C*', $tmp );

        my $found = grep { $_ == $area_no } @pixels;
        if ( $found ) {
            my $st = 0;
            $st++ while $pixels[$st] != $area_no;

            my $en = scalar(@pixels) - 1;
            $en-- while $pixels[$en] != $area_no;

            $xmin = $st if $st < $xmin;
            $xmax = $en if $xmax < $en;
        }

        if ( not $found ) {
            last;
        }
        else {
            $ymax = $iy;
            $iy++;
        }
    }

    return +{
        xmin => $xmin,
        ymin => $ymin,
        xmax => $xmax,
        ymax => $ymax
    };
}

sub detect_filled_area {
    my ( $img, $area_no ) = @_;

    my ( $w, $h ) = ( $img->getwidth(), $img->getheight() );
    for (my $iy=0; $iy<$h; $iy++) {
        my $tmp = $img->getsamples( y => $iy, channels => [0] );
        my @pixels = unpack( 'C*', $tmp );

        for (my $ix=0; $ix<$w; $ix++) {
            if ( $pixels[$ix] == $area_no ) {
                return calc_filled_area( $img, $area_no, $ix, $iy );
            }
        }
    }

    die 'filled area not found.';
}

sub print_area_info {
    my ( $area_no, $area ) = @_;
    printf( "area_no = %3d, (x, y) = (%4d, %4d), w = %4d, h = %4d",
        $area_no,
        $area->{xmin},
        $area->{ymin},
        $area->{xmax} - $area->{xmin} + 1,
        $area->{ymax} - $area->{ymin} + 1 );
    print "\n";
}

sub get_angle {
    my ( $img, $area_no ) = @_;

    my $cur_angle = .0;
    my $cur_value = calc_evaluation_value( $img, $cur_angle, $area_no );

    my $step = 32.0;
    while ( 0.1 < abs($step) ) {
        my $new_angle = $cur_angle + $step;
        my $new_value = calc_evaluation_value( $img, $new_angle, $area_no );
       # printf( "Current %5.1f: %3d, New %5.1f: %3d\n",
       #     $cur_angle, $cur_value,
       #     $new_angle, $new_value );

        if ( $new_value < $cur_value ) {
            $cur_value = $new_value;
            $cur_angle = $new_angle;
        }
        else {
            $step *= -0.5;
        }
    }

    return $cur_angle;
}

sub calc_evaluation_value {
    my ( $img, $rot_angle, $area_no ) = @_;

    my $img_tmp = $img->rotate(
        degrees => $rot_angle, back => 'black' );

    my $xmin = int( $img_tmp->getwidth() / 2 );
    my $xmax = int( $img_tmp->getwidth() / 2 );
    my $iy = $img_tmp->getheight();
    while ( 0 < $iy-- ) {
        my $tmp = $img_tmp->getsamples( y => $iy, channels => [0] );
        my @pixels = unpack( 'C*', $tmp );

        if ( grep { $_ == $area_no } @pixels ) {
            my $st = 0;
            $st++ while $pixels[$st] != $area_no;

            my $en = scalar(@pixels) - 1;
            $en-- while $pixels[$en] != $area_no;

            $xmin = $st if $st < $xmin;
            $xmax = $en if $xmax < $en;
        }
    }

    return $xmax - $xmin + 1;
}

$img_dst->write( file => $0 . '.png' );

結果的に、今まで書いてきたコードのコピペになっちゃったから、
そろそろ、モジュールとかにしないと、ちょっとアレなんだけど。

例えば、こういう画像を入力すると、

20140727-2

こんな感じになる。

20140826-1

ちゃんと、重なっている部分がはじかれている。
あと、ソートするのも簡単だけど、
そこはGIFアニメーションにしたいので、ずっと先のお話。

おしまい。

Leave a Comment