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' );
結果的に、今まで書いてきたコードのコピペになっちゃったから、
そろそろ、モジュールとかにしないと、ちょっとアレなんだけど。
例えば、こういう画像を入力すると、
こんな感じになる。
ちゃんと、重なっている部分がはじかれている。
あと、ソートするのも簡単だけど、
そこはGIFアニメーションにしたいので、ずっと先のお話。
おしまい。
Leave a Comment