極悪です。
Image::Magic を使ったスクリプトの例を一発。
魚眼レンズで撮った、黒バックに丸い視野の画像の歪みを補正しま
す。レンズの画角は 180°決め打ちで、その中心から 140°の範囲
を切り出して歪み(等距離射影方式を仮定)を補正し、aout.png
というファイル名で出力します。輝度も補正しないといけない気が
しますが、市販ソフトでも補正してるという記述がないので誤差の
範囲なのかな。
ちなみに市販ソフトでは画像の中心の調整(光学系の光軸ずれや撮
影後の画像処理等で中心がずれるらしい)とか、画角(220°とか
対角線で 180°とか)、各種射影方式(シグマの等立体角射影とか
Nikon の正射影とか)に対応したもの、ソフトであれば動画ファイ
ルを処理するもの、ハードであればカメラ映像をリアルタイムに補
正するものなどがあります。カメラメーカでも、自社のデジカメ+
魚眼レンズで撮ったものを超広角相当の画像に変換するソフトを売
ってたりします。
使い方:
D:% perl fisheye.pl Deco2.jpg
file : Deco2.jpg
width : 256[dot]
height : 256[dot]
depth : 8[bit]
size : 17558[byte]
radius : 128[dot]
process : 100[%]
D:%
→ これで、Deco2.jpg を補正した aout.jpg というファイルがで
きます。インターネットに転がっている魚眼レンズの画像(写真)
を適当に変換してみてください。画像のサンプルとしては
http://www.worldserver.com/turk/quicktimevr/FishExamples.html
などが使えます。
use strict;
use POSIX qw(floor);
use Image::Magick;
use Math::Trig;
use constant PI => 4 * atan(1);
use constant ANGLE => 140;
use constant KRADIUS => 200/256;
# スクリプト引数は入力画像ファイル名
my($file) = @ARGV or die qq/usage: $0 filename\n/;
# 入力画像を開く
my $image = Image::Magick->new;
my $error = $image->Read($file);
if($error){ die $error,"\n"; }
my($width,$height,$depth,$size)
= $image->Get('width','height','depth','filesize');
my $radius = round( get_r($image) );
print "file\t: ", $file, "\n";
print "width\t: ", $width, "[dot]\n";
print "height\t: ", $height, "[dot]\n";
print "depth\t: ", $depth, "[bit]\n";
print "size\t: ", $size, "[byte]\n";
print "radius\t: ", $radius, "[dot]\n";
# 補正画像の作成(半径 R の位置が半画角 T に相当)
my $T = deg2rad(ANGLE / 2);
my $R = round($radius * KRADIUS);
my $image_out = Image::Magick->new;
$image_out->Set(size=>"@{[2*$R]} x @{[2*$R]}");
$image_out->ReadImage('xc:black');
my $a = $R / $T;
my $b = tan($T) / $R;
++$|; printf "\rprocess\t: %03d[%%]",0;
for(my $y = 0; $y <= $R; ++$y){
printf "\rprocess\t: %03d[%%]",round( 100*($y+$R)/(2*$R) );
for(my $x = 0; $x <= $R; ++$x){
my $r = sqrt($x*$x+$y*$y);
my($x0,$y0) = ($x,$y);
if($r){
my $k = ($a / $r) * atan($b * $r);
$x0 *= $k;
$y0 *= $k;
}
copy_rgb([$image,+$x0,+$y0],[$image_out,+$x,+$y]);
copy_rgb([$image,+$x0,-$y0],[$image_out,+$x,-$y]);
copy_rgb([$image,-$x0,+$y0],[$image_out,-$x,+$y]);
copy_rgb([$image,-$x0,-$y0],[$image_out,-$x,-$y]);
}
}
$image_out->Write('aout.jpg');
exit 0;
# 四捨五入
sub round{ floor($_[0] + 0.5) }
# 入力画像内の円形写野の半径を求める
sub get_r{
my $image = shift;
my($width,$height) = $image->Get('width','height');
my($w_max,$h_max);
for(my $j = 0; $j < $height; ++$j){
my $w = 0;
for(my $i = 0; $i < $width; ++$i){
local $_ = $image->Get("pixel[$i,$j]");
my($r,$g,$b) = m/(\d+)/g;
if($r or $g or $b){ ++$w }
}
if($w_max < $w){ $w_max = $w }
else{ next }
}
for(my $i = 0; $i < $width; ++$i){
my $h = 0;
for(my $j = 0; $j < $height; ++$j){
local $_ = $image->Get("pixel[$i,$j]");
my($r,$g,$b) = m/(\d+)/g;
if($r or $g or $b){ ++$h }
}
if($h_max < $h){ $h_max = $h }
else{ next }
}
my $r = ($w_max + $h_max) / 4;
return $r;
}
# 位置 (x,y) の色を求める
sub get_rgb{
my($image,$x,$y) = @_;
my($width,$height) = $image->Get('width','height');
my $i = round($x + $width/2);
my $j = round($height/2 - $y);
my($r,$g,$b);
if($i < 0 || $width <= $i || $j < 0 || $height <= $j){
($r,$g,$b) = (00,00,00); # 範囲外
}else{
local $_ = $image->Get("pixel[$i,$j]");
($r,$g,$b) = m/(\d+)/g;
}
return($r,$g,$b);
}
# 位置 (x,y) に色 (r,g,b) をプロット
sub set_rgb{
my($image,$x,$y,$r,$g,$b) = @_;
my($width,$height) = $image->Get('width','height');
my $i = round($x + $width/2);
my $j = round($height/2 - $y);
if($i < 0 || $width <= $i || $j < 0 || $height <= $j){
($r,$g,$b) = (00,00,00); # 範囲外
}else{
$image->Set("pixel[$i,$j]","$r,$g,$b");
}
return($r,$g,$b);
}
# 位置 from の色を位置 to にコピーする
sub copy_rgb{
my($from,$to) = @_;
set_rgb(@{$to},get_rgb(@{$from}));
}
__END__
--
FZH01112 at nifty.com
http://hpcgi1.nifty.com/dune/gwiki.pl?