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