2009年12月16日水曜日

FreeBSDでPerlMagick(その5)さらに進んだコマンド

この投稿はFreeBSDでPerlMagick(その4)図形の描画と画像の加工からの続きです。
また,FreeBSDでPerlMagick(その1)はじめにに目次があります。

(21) Fx (その2)

Fx について少し前の項目で書いたが,もう少し書いておこう。
 FxRGB の三原色を表すrgb の3つのチャネルと,透明度を制御するalpha チャネルの4つのチャネルの強度を別々に演算することができるもので,Image Magick の中でも特別なコマンドとなっている。そのため,Image Magick サイトのThe Fx Special Effects Image Operatorに専用のページがある。
 このブログではFx の使い方として,すでに前回の記事で
  [a] 白黒画像への変換((23) Fx:三原色の色チャネルの演算
  [b] 画像の半透明化((20) 図の一部を透明にする:TransparentとFx
の二つの使い方を紹介した。ここではさらに進んだ使い方について書こう。

ビットごとの演算(いずれも白いキャンバスを用意してから演算した。キャンバスの種類にはよらないはずだが)
例1:$img = $img->Fx(expression=>'i/w');
例2:$img = $img->Fx(expression=>'(w-i)/w');
例3:$img = $img->Fx(expression=>'j/h');
例4:$img = $img->Fx(expression=>'i/w',channel=>'red');
これらの例はビットごとの演算の一つである。下記に上記のコマンドの結果を示す。
・ 例1は横方向へのグラデーション画像である。ここでw はピクセル単位の画像の横幅であり,i はcolumn offsetと呼ばれているが,図の中の各点を表すピクセル単位の横座標である。例1では,点ii/w という値を入れろ,と書いてある。対象となるチャネルはRGB の全てのチャネルである。つまり,左端の点(i=0 ) にはゼロを,右端の点(i=w) には1を入れる。その間は点の座標に比例した値を入れる。すると結果として,左側が黒,右端が白となるグラデーション模様になる。ここでは強度は0~1で表される
・ 例2ではi/w の代わりに(w-i)/w を入れている。そのため右端が黒,左端が白のグラデーションになっている。
・ 例3では縦方向のグラデーションを描いている。h がピクセル単位で表した画像の縦幅であり,j が縦軸の座標を表している。
 j の原点は上端なので,グラデーションは上端が黒,下端が白となっている。
・ 例4は例1と同じように横方向のグラデーションなのだが,red チャネルに対してのみ演算を行っている。
 そのため赤の補色である水色が白に変わるグラデーションとなっている。
例1:点 i に i/w を代入例2:(w-i)/w を代入例3:縦グラデーション例4:赤チャネルへの
グラデーション処理
 さらに以下のようなことも可能となる。
例1:$img = $img->Fx(expression=>'(i/w)^4');
例2:$img = $img->Fx(expression=>'sin(pi/2*i/w)');
例3:$img = $img->Fx(expression=>'(exp(i/w)-1)/(e-1)');
例4:$img = $img->Fx(expression=>'abs(cos(2*pi*i/w))');
ここではべき乗やsinexp などの関数を用いている。どんな関数が使えるかはThe Fx Special Effects Image Operatorを見て欲しい。
例1:(i/w)の4乗例2:sinを用いた例3:expを用いた例4:absとcos

 以下の例では2次元の処理を行っている。
例1:$img = $img->Fx(expression=>'xx=i/w-.5; yy=j/h-.5; rr=xx*xx+yy*yy; 1-rr*4');
例2:$img = $img->Fx(expression=>'rr=hypot(i/w-.5, j/h-.5); 1-rr*1.42');
例3:$img = $img->Fx(expression=>'(1-(2*i/w-1)^4)*(1-(2*j/h-1)^4)');
例4:$img = $img->Fx(expression=>'.5 - atan2(j-h/2,w/2-i)/pi/2');
ここでrr は中心からの距離である。hypot 関数は2乗の和の平均を取る関数らしい。例3では四角っぽいグラデーションが作れる。
例1例2例3例4

複数の画像を読込んでビットごとの演算Image Magick ではひとつの変数に複数枚の画像を読み込むことができる。それをFx で扱うこと考えよう。ひとつの変数に複数枚を読み込むには単に同じ変数に複数回画像を読み込めばいいみたい。注意点は,最初の読込みは参照する際に0番となる点
$img->Set(size=>'160x120');
$img->ReadImage('xc:none'); # Fxでは,u[0] と参照される
$img->ReadImage('xc:red'); # Fxでは,u[1] と参照される
$img->ReadImage('xc:yellow'); # Fxでは,u[2] と参照される
$img->ReadImage('xc:lime'); # Fxでは,u[3] と参照される
$img->ReadImage('xc:blue'); # Fxでは,u[4] と参照される
  これらを使って以下のような演算をさせると,いろんな色を含んだ画像ができる(全部Image MagickUsage のページに書いてあるんだけどね)
例1:$img = $img->Fx(expression=>'ar=hypot( i/w-.8, j/h-.3 )*4;
br=hypot( i/w-.3, j/h-.7 )*4;
u[1]*br/(ar+br) + u[2]*ar/(ar+br)');

例2:$img = $img->Fx(expression=>'ar=1/max(1,  (i-50)*(i-50)+(j-10)*(j-10)  );
br=1/max(1,  (i-10)*(i-10)+(j-70)*(j-70)  );
cr=1/max(1,  (i-90)*(i-90)+(j-90)*(j-90)  );
( u[1]*ar + u[2]*br + u[3]*cr )/( ar+br+cr )');

例3:$img = $img->Fx(expression=>'ar=1/max(1,  (i-50)*(i-50)+(j-10)*(j-10)  );
br=1/max(1,  (i-10)*(i-10)+(j-70)*(j-70)  );
cr=1/max(1,  (i-90)*(i-90)+(j-90)*(j-90)  );
dr=1/max(1,  (i-90)*(i-90)+(j-10)*(j-10)  );
( u[1]*ar + u[2]*br + u[3]*cr + u[4]*dr)/( ar+br+cr+dr )');
ここでu[1] が1枚目の画像の要素,u[2] が2枚目の画像の要素,…,を表している。例ではわかりやすいように(ほんとに?)u[0] を使っていない。そのため0番として透明な画像を読み込ませている。5枚読み込んだ時に,u[5] とすると最初に戻りu[0] が参照されるらしい。u[-1] は最後の読込みを表すらしい。つまり引数は循環して適用されるってことやね。他にu[t] とするとcurrentイメージを参照するらしい。currentイメージってどうやって指定するんやろ?
例1:例2:例3:

(22) LevelやGamma値の調整

ここでは画像の強度LevelやGamma値の補正について書く。と言っても,そんなに理解できてないので,自動調整と,Threshold を使ったものだけを書こう。
例1:$img->AutoLevel(channel=>'All');
例2:$img->AutoGamma(channel=>'All');
例3:$img->BlackThreshold(threshold=>'50%',channel=>'All');
例4:$img->WhiteThreshold(threshold=>'50%');
ここでは,例1から順に,AutoLevelAutoGammaBlackThreshold 50%WhiteThreshold 50% の例を示している。AutoGammaを施すとなんとなく画像が赤っぽくなっているような感じがする。BlackThreshold は指定された強度(ここでは50%) 以下の強度の点を黒にする。WhiteThreshold は指定された強度(ここでは50%) 以上の強度の点を白にする。
例1:AutoLevel例2:AutoGamma:ちょっと赤っぽい例3:BlackThreshold 50%例4:WhiteThreshold 50%
追記)gamma 値の補正についての補足。上記のAutoGamma(gamma 値の自動補正)を行うとどうも赤っぽい感じ見えてしまう。オリジナルと比べるとその違いは顕著である。そこで,gamma 値の補正に挑戦してみた。
例:$img->Gamma(gamma=>0.8, channel=>'Cyan');
ここでは,上記の例2のAutoGammaを施したものに,さらに Cyan を 0.8 に減らす,という gamma 補正(言葉あってる?)を施している。結果はオリジナルにある程度近くなったと思う。しかし,いつもこの補正値でよいかは不明なので,いろいろと試しみてほしい。詳しいことはImage Magick サイトのColor Modificationsを読んでみてほしい。
AutoGamma 後に
Gamma(0.8, 'Cyan') を施したもの

(23) Negate:ネガを作る

ネガを作るにはNegate というコマンドを使う。
$img->Negate(channel=>'All');
PerlMagick の説明を読むといろいろオプションがあるようだが,とりあえずネガを作るには上記のようにすればよい。
オリジナル (160x120)Negate

(24) Posterize:色の種類を減らす

Posterize というコマンドを使うと,色レベルの種類(数)を減らして,ポスター調の画像を作る。コマンドは以下の通り。
例1:$img->Posterize(levels=>4, dither=>'True');
例2:$img->Posterize(levels=>4, dither=>'False');
オプションのlevels についてはよくわからないが,10より大きな数字にすると,何をしてるのかわからなくなる。解像度にもよるかもしれないが,小さい整数がいいみたい。
オリジナル (160x120)例1:Posterize levels=>4
dither=>False
例2:Posterize levels=>4
dither=>True

(25) OrderedDither:ザラザラにするコマンド?

OrderedDither というコマンドを使うと,画像をザラザラにすることができる。ちゃんと意味を理解してないので,具体例を見てください。
例1:$img->OrderedDither(threshold=>'h4x4a', channel=>'All');
例2:$img->OrderedDither(threshold=>'h4x4o', channel=>'All');
例3:$img->OrderedDither(threshold=>'h4x4a', channel=>'Green');
オプションのthreshold の意味はよくわからない。誰か教えて~。きっとImage MagickUsage のページを見れば書いてあると思うけど…最後の例は,対象をGreen チャネルだけにしてみた。
オリジナル (160x120)例1:Threshold=>'h4x4a'
channel=>'All'
例2:Threshold=>'h4x4o'
channel=>'All'
例3:Threshold=>'h4x4a'
channel=>'Green'

(26) 明るさの調整 : SigmoidalContrast

明るさの調整には,SigmoidalContrastというコマンドを用いる。
$img->SigmoidalContrast(contrast=>'5', mid-point=>'0%');
ここでもやっぱり細かいパラメータの値のことをまだ理解していないが,contrastはどの程度画像を明るくするかを表し,0だと何もせず,数値が大きくなると画像がより明るくなる。3が普通で,20だとやりすぎらしい。mid-pointの値はよくわからない。0%でも100%でもいまいち差が分からない。他にもsharpenというパラメーターもあり,コントラストを減らすには,このsharpenをFalseにするといいらしい。
オリジナル (120x160)SigmoidalContrast contrast=>5

2009年12月13日日曜日

FreeBSDでPerlMagick(その4)図形の描画と画像の加工

この投稿はFreeBSDでPerlMagick(その3)画像の加工からの続きです。
また,FreeBSDでPerlMagick(その1)はじめにに目次があります。

(15) Draw:図形の描画(その2)

 PerlMagick での図の描画の例をいつくか示そう。まずはline, rectangle, roundRectangle について書こう。それぞれの書式は以下のようになる。ちなみに,いずれの場合も 100 x 100 ピクセルのキャンバスの上に描画している。描画するための画像ファイルの読込み,あるいは新しいキャンバスを用意については,このブログのFreeBSDでPerlMagick(その1)はじめにFreeBSDでPerlMagick(その2)図と文字を描く を見て欲しい。
$img1->Draw(primitive=>'line', points=>'x0,y0 x1,y1', stroke=>$color, strokewidth=>$width);
$img1->Draw(primitive=>'rectangle', points=>'x0,y0 x1,y1', fill=>'green', 
stroke=>$color, strokewidth=>$width);
$img1->Draw(primitive=>'roundRectangle', points=>'x0,y0 x1,y1 wc,hc', fill=>'green', 
stroke=>$color, strokewidth=>$width);
line は始点と終点を,rectangle は対角線上の2点を, roundRectangle は対角線上の2点と角の丸みを指定する。点と点の間はスペースだが,コンマで区切っても大丈夫だった。stroke は線を表し,rectangleなら縁の線を表す。指定しなければ枠なしの図形となる。fill は塗りつぶしの色を表し,line で指定しても無視される。以下に具体例を示す。例4はスリット付きの角の丸い四角である。
例1:$img1->Draw(primitive=>'line', points=>'0,50 100,100', stroke=>'blue', strokewidth=>2);
例2:$img1->Draw(primitive=>'rectangle', points=>'0,50 100,100', fill=>'green');
例3:$img1->Draw(primitive=>'roundRectangle', points=>'0,50 100,100 10,10', fill=>'green');
例4:$img1->Draw(primitive=>'roundRectangle', points=>'0,25 100,75 10,10', fill=>'green');
   $img1->Draw(primitive=>'rectangle', points=>'45,0 55,100', stroke=>'none',  fill=>'white');

例1:line例2:rectangle例3:roundRectagle例4:roundRectagle
with slit

 次にarc, ellipse, circle について書く。それぞれの書式は以下のようになる。
$img1->Draw(primitive=>'arc', points=>'x0,y0 x1,y1 a0,a1', stroke=>$color, strokewidth=>$width, fill=>'green');
$img1->Draw(primitive=>'ellipse', points=>'x0,y0 rx,ry a0,a1', stroke=>$color, strokewidth=>$width, fill=>'green');
$img1->Draw(primitive=>'circle', points=>'x0,y0 x1,y1', fill=>'green', stroke=>$color, strokewidth=>$width);
 arc では,x0,y0 x1,y1 で四角い描画領域を指定し,その四角の中に辺に接するように角度a0からa1までの円弧を描く。角度は右横が0度で,時計回り(下向き)に角度を測る。下が90度方向,左が180度方向,上が270度方向になっている。gravity オプションが使えれば,角度の指定方法は変わると思われる(gravityが使えるかどうかもわからないので,あくまで推論) ellipse は中心座標と横方向の半径,縦方向の半径と開始角度,終了角度を指定する。circlex0,y0 で中心座標を指定し,中心座標から点x1,y1 までを半径とする円を描く。stroke 等はlinerectangleなどと同じである。
例1:$img1->Draw(primitive=>'arc', points=>'0,0 100,50 0,225', 
stroke=>'blue', strokewidth=>2, fill=>'green');
例2:$img1->Draw(primitive=>'arc', points=>'0,0 100,50 225,0', stroke=>'blue', strokewidth=>2, fill=>'green');
例3:$img1->Draw(primitive=>'ellipse', points=>'50,50 50,30 0,225', stroke=>'blue', strokewidth=>2, fill=>'green');
例4:$img1->Draw(primitive=>'circle', points=>'50,50 50,5', stroke=>'blue', strokewidth=>2, fill=>'green');

例1:arc例2:arc2例3:ellipse例4:circle

(16) Composite:画像の合成

 図の合成はComposite コマンドを使う。詳しいことはCompositing Imagesを見て欲しいが,以下に例を示しておこう。
# Original画像の読み込み
my $img = Image::Magick->new;
$img->Read($target_pict);

# タイトル用の透明キャンバスの用意
my $img1 = Image::Magick->new;
$img1->Set(size=>'300x50');
$img1->ReadImage('xc:none');

# タイトルの作成
$text='紀伊半島・二木島';
$text = jcode($text)->utf8;
$fontdir='./HGRSMP.TTF';
$color='yellow';
$pointsize=32;
$img1->Annotate(text=>$text,geometry=>'+0+0',font=>$fontdir,fill=>$color,
gravity=>'Center',pointsize=>$pointsize,strokewidth=>1,stroke=>$color);

# 合成
$img->Composite(image=>$img1,compose=>'over',geometry=>'+0+80',gravity=>'Center');

# 出力とメモリの開放
$img->Write($newname);
undef $img1;
undef $img;
ここで,$img という画像に,$img1 というタイトル文字列を合成している。今の場合は,単純に上に重ねればいいのでcomposeover にしている。また,場所の指定は,Center からの相対的な座標で表している。

OriginalTitle (背景が透明なのでpng で出力)TitleをCompositeしたもの

(註):この例は合成を示したいがためにこんなことをしているが,普通は画像(写真)に直接Anotate で文字を描く方が早い。
(註2):gravity で合成する際の座標原点を指定できるが,左下を指定した場合(SouthWest)にオフセットの値をプラスで記述するとうまくいかなかった。そんな時はマイナスの数値にしてみてほしい。

(17) Quantizeを用いた白黒画像への変換

白黒にする方法としてQuantize を使う方法を紹介する。他にもFx を使って演算する方法があるが,それは次の項目にしよう。QuantizeFx で結果は微妙に違うが,大体は同じような白黒画像が得られる。
$img->Quantize(colorspace=>'gray');
Quantize は本来,色の種類を減らすために用いるみたいだが,筆者はまだちゃんと理解できてない。Quantize に関しては,Image Magick の使い方のサイトのColor Quantization and Ditheringを見て欲しい。
オリジナル (160x120)Quantizeで白黒へ

(18) Fx:三原色の色チャネルの演算

白黒変換のところでFx を使って演算する方法があると書いたが,ここではそのFx について書いてみよう。
 まずImage Magick での色についてだが,色を指定するのに幾つかの手法があって,よく知られているのがRGB による指定である。他にもCMYKHSBHSLなど幾つもあるらしい。筆者はRGB しか理解できてないので,RGB で話をしよう。RGB では,赤(r, red),青(b, blue),緑(g, green)の3色の和として色を表している。Image Magick では,色を指定するのに#ccddff のように16進数を使って表すこともできる。ここで,最初の2桁の#cc# は以降が16進数であることを示している) はred 成分の濃さを,次の#ddgreen 成分の濃さを,最後の#ffblue 成分の濃さを表している。16進数で2桁なので,#00~#ff までの値を取りうる。Fx を用いると,この3つの色のチャネルに対して演算を行うことができる。例えば以下のように書くと,greenblue の色の平均をred チャネルに代入できる。
$img = $img->Fx(expression=>'(g+b)/2',channel=>'red');
ここで数式の中のgb がそれぞれgreenチャネル,blueチャネルを表している。redチャネルは数式の中ではrと表される。数式はsincos などのような一般の関数が使えるらしい。次ように書くと,3色の平均を取り,それを全てのチャネルに代入する。結果として白黒画像が得られる。
$img = $img->Fx(expression=>'(r+g+b)/3');
Fx に関しては,Image Magick サイトのThe Fx Special Effects Image Operatorを見て欲しい。
オリジナル (160x120)Fx(expression=>'(g+b)/2',
channel=>'red')
Fxで'(r+g+b)/3'とした

(19) Colorize:色をつける

Colorize を使うと,RGB の3色に別々に色をつけることができる。以下ではFx で白黒画像にしたものにColorize で色をつけている。
$img = $img->Fx(expression=>'(r+g+b)/3');
$color='#fad759';
$opacity='50%';
$img->Colorize(fill=>$color,opacity=>$opacity);
ここでopacity は不透明度を表し,opacity が大きいほど画像は薄くなる(?あれ?逆か?)。
オリジナル (160x120)Fxで'(r+g+b)/3'の後
色'#fad759'とopacity 50%で
Colorizeしたもの
Fxで'(r+g+b)/3'の後
色'#ccddff'とopacity 50%で
Colorizeしたもの

(20) 図の一部を透明にする:TransparentとFx

Image Magick で図を別の図に重ねる時に,図の一部や背景を透明にしたいことがある。Image Magick では,透明にした図を表すために,RGB の3つのチャネルに,alpha チャネル(あるいはmatte チャネル,ないしはopacity チャネル) と呼ばれるチャネルを1個追加して画像を扱っている。つまり色を表すのに#ccdd88ff のように16進数8桁で表している。6桁の場合はalpha チャネルの部分は#ff(透明ではない) とされる。従って,画像を透明にするには16進数8桁で表される色の最後の2桁を#00 にすると完全に透明になる。しかし,画像を処理する際には特定の色のみ透明にしたいことがある。例えば背景を白にしておいて,白い部分だけを透明にしたい,という場合。その場合はTransparent を使うと特定の色を透明にできる。
$img->Transparent(color => 'White');
この方法を使えば,以下のように新しい白いキャンバスを用意しておいて,白の部分を透明にすることで透明なキャンバスを用意することもできる。
use Image::Magick;
my $img1 = Image::Magick->new;
$img1->Set(size=>'300x300');
# ----------------------------------
$img1->ReadImage('xc:white'); # white canvas
$img1->Transparent(color=>'White');
# ----------------------------------
$img1->Write($newname);
undef $img1;
また,図を描いた上に背景色で図を上書きして,背景色を透明にすると図を切り取ることもできる。
 全体を一様に半透明にするにはFx を用いる。Fx の使い方のところで書いたが,Fx では3原色をredgreenblue の3つのチャネルとして演算していたが,透明度のためにalpha チャネル(a チャネル) に対して演算すると透明度が変化する。
$img1 = $img1->Fx(expression=>'a/2',channel=>'alpha');
このようにすると,透明度が50%に下がる。下記の例では(15) Draw:図形の描画(その2)の例4の画像の背景を透明にしたものと,さらに全体を半透明にしたものを写真に合成している。念のために,今回用いた合成のコマンドを書いておく。
$img->Composite(image=>$img1,compose=>'over',geometry=>'+0+0',gravity=>'Center');
透明化に関しては,Image Magick の使い方のサイトのMasking and Background Removalに (2011/4 訂正) Channels, Masks and Transparency詳しく書いてある。
オリジナル (160x120)白を透明にして合成全体を半透明にして合成

(註):色のついた背景(xc:white などで準備したもの)に対しては上記の処理はうまくいかないことがあった。背景を半透明にするには,canvas の準備で透明なキャンバスを選び,全面に好みの色(白でもsilverでもgrayでもいい)をベタ塗りする。その上で上記のように alpha チャンネルの値を減らすとよい。

FreeBSDでPerlMagick(その5)さらに進んだコマンドに続く