アンシャープマスキング
画像のボヤけた輪郭を強調し、輪郭を鮮明にするフィルター処理です。(鮮鋭度をあげる処理)
画像の処理をする時、一旦ボケた画像をつくり、その画像を元に、鮮鋭化する為、アンシャープマスクとなっているようです。
ボケ画像の生成に一般的には単純平均画像を使用するのですが、ガウシアンフィルターを使用してみました。
ここで作成したプログラムでは、アンシャープマスキング用カーネルの作成はせず、平滑化画像、差分画像とそれぞれ手順を追って、アンシャープマスキングを行っています。
計算速度を上げる為には、アンシャープマスキング用カーネルを作成して、マスキング処理をしたほうが良いのは明白です。
デジタルカメラで撮影した、最初からボケて撮影された画像の解像度を上げることは出来ません、あくまでも見た目に画像が鮮鋭に感じるようにするための処理です。
カメラによっては、撮影画像をメモリーに保存する時に、アンシャープマスキング処理をして保存をするものもあります、自分で処理をするなら、保存時に画像の処理をしないよう設定しておいた方が良いでしょう。
ディフォルトとして、3x3、5x5、7x7のガウシアンフィルターを用意しました。
シグマの値を設定して、ガウシアンのフィルターの作成も出来る様にしてあります。
値を千倍して、整数化して利用した方が、計算時整数を使用できるので、処理速度が速くなりますが、ここのプログラムでは、浮動少数点で計算をしています。
浮動小数点の計算は、演算用子プロセッサーが使用されるので、単精度計算よりも倍精度で計算したほうが速くなります。
単精度の場合、倍精度で計算して、単精度に変換しています。
シグマ値を大きくすると、中央の値と、周辺の値の差が小さくなり、単純平均に近づきます。
1より小さくしていくと、中央の値が大きくなり、0.5位で、中央部だけとなり、平均化の意味はなくなります。
トータルは、全体を加算したときの値が1になるようにする為の分母になる値です。
ガウスの計算は上図計算式ですが、1/2πσ^2の部分は定数項なので計算しなくても、本プログラムでは問題ありませんが、最初のガウス配列の計算だけの問題なので、省略はしていません。
1/2πσ^2があると、ガウシアンフィルターの値を合計した場合ほぼ 1 になりますが、シグマ値を大きく設定すると、値が小さく広く分布するため、11x11、7×7、5×5、3x3 の外側に分布が広がり、合計値が1よりかなり小さくなります。
上図は、左から、元画像、ガウシアンフィルター処理画像、差分画像、アンシャープ画像です。
差分データーは、値がマイナスにもなるので、データーの最小値を検索して、最小値が値0になるようにして0~255の範囲に入るようにして表示しています。
アンシャープマスキングを行う時、アンシャープ係数と、スレッショルド値を設定します。
アンシャープ係数は、鮮鋭化をどれぐらい行うかですが、スレッショルド値は、差分データーの値が、スレッショルド値より大きかった時、アンシャープマスキングの値を採用し、小さかったら、元の値を採用します。
例えば、スレッショルド値をゼロにすると、差分がゼロ以下の場所は、元のデーターとなります。-255にすれば、全ての場所にアンシャープマスキングの値が採用され、255にすれば、アンシャープマスキング値は全て無視されることになります。
データー範囲の拡張
ガウシアンフィルターを使用して、ボケ画像を作成する場合、画像の左右、上下の部分はフィルターの計算範囲外の座標が発生するため、データーの拡張を行います。
左図は、7×7のフィルターの場合で、計算位置より内側の3列、3行をミラーコピーして、データーを拡張しています。
3x3、5x5、11x11の場合は、それぞれに応じて、データーを拡張して計算します。
座標外になる場合は、そこの部分を計算に入れないようにする方法もありますが、ここではデーターの拡張を行い、すべての範囲で計算出来る様にしています。
実際の画像では、外周の僅かな部分なので、フィルターの計算はフィルターのサイズに応じて、計算できる範囲だけに絞って計算しても問題はないでしよう。
データーの拡張を行わずに、拡張領域があるようにするには、データーをサンプリングするルーチンを少し工夫すれば、簡単に実行することも出来ます。
function TMainF.GrayByteData(Y, X: integer): byte;
begin
if X < 0 then
X := -X;
if X > Width -
1 then X := Width - X + Width - 2;
if Y < 0 then Y := -Y;
if Y > Height - 1 then Y := Height - Y +
Height - 2;
Result := GrayMat[Y, X];
end;
拡張部分がない場合は、ゼロより小さい配列の位置がしてされたら、符号を反転する事により、ゼロから正方向の位置になります。
配列の最大値を超える場合は、超えた分を、配列の最大値から引くことにより、最大値の位置から負方向の位置になり、拡張領域がある場合と同じ計算が出来ます。
サンプルプログラムには、データー領域の拡張をする方法と、プログラムにより、拡張したのと同じ計算になる方法の両方を添付しました。
最近のデジタルカメラでは、顔認識をしたり、撮影の対象によって、画像処理を施して保存するものが増えています。
撮影時に画像処理をしないカメラを選ぶが、処理をしないように設定をして、後で自分で処理するのを楽しんでも良いでしよう。
アンシャープマスキングプログラム
ここのプログラムは、前記したように、ボケ画像の生成に、ガウシアンフィルターを採用しています。
一般的な平滑フィルターを追加するのは、簡単なので、必要であれば変更をしてみてください。
ファイルを開く前に、マスクのサイズを指定します、マスクのサイズは、3×3、5×5、7×7、11×11 の四種類のサイズに固定しています。
自由なサイズに出来る様にしても良かったのですが、面倒なので四種類にしました。
マスクサイズは、中央の値が必要なので、必ず奇数になります。
イメージファイルの読み書きに、TWICImageを使用しているので、各種のイメージファイルに対応しています。
ファイルを読み込んだら、マスキングのサイズによって、画像データーの外側に、ガウシアンフィルターの計算の為、ミラーコピー画像の追加を行います。
Mat表示ボタンをクリックする事により、拡張された画像の確認が出来ます。
実際に追加されるのは、僅かな量なので、追加された部分の画像の判別は出来ないのかも知れませんが、中の画像が僅かに小さく表示されるので、拡張部分が追加されたのが分かります。
次に、ボカシ画像を作成します。
Blurringボタンをクリックする事で、ガウシアンフィルターが実行され、ボカシ画像が生成されます。
差分データーの作成。
元画像から、ボカシ画像を差し引いて、差分データーを作成しますが、差分計算値は、当然-255から+255の範囲を取る可能性があります。
差分データーを作成した時の、差分の最大値と、最小値をラベルにそれぞれ表示しています。
アンシャープ計算
最後にアンシャープマスキングの計算をします。
UnSharp適応量は、前記アンシャープ計算式のK値です。
Thresholdは、差分値の値によって、差分値を元の値に加算するか、しないかの判別する為の値です。
Thresholdの値より大きかったら、元の値に加算し、小さかったら元の値をそのまま使用します。
ガウス分布指定
ガウスの値として、デフォルトの値を使用するか、シグマ値を指定して、計算した値を使用するか指定が出来ます。
デフォルトガウスを指定すると、固定配列として用意してあるガウシアンフィルターが使用されます。
固定配列の値は一般的に採用されている値です、計算高速化の為、整数化された値が採用されています。
しかし、本プログラムでは、シグマ値を指定して、計算されたフィルターの値をそのまま使用しているので、浮動小数点演算を行っていますが、計算値を千倍、あるいは数千倍以上して、整数化して計算すれば、高速化が可能でしょう。
現在は、浮動小数点演算もかなり高速化されていますが、11x11のマスクサイズを指定するとかなりの時間待たされます。
現在のデジタルカメラは、かなり高解像度なので、数十秒待たされることもあるし、メモリーも1G以上消費します。
本プログラムでは、画像を表示しているサイズが小さいので、最近のデジタルカメラの高解像度の画像だと、何が変化したのか分からないでしょう。
実際、カメラの解像度が、通常のパソコンの画面で表示できる解像度を超えているので、アンシャープ処理をする必要があるかどうか不明です。
携帯電話のカメラの場合、センサーの解像度に対して、レンズの解像度が追いついていない場合が沢山あります、その場合は、多少アンシャープマスキングの効果が期待できるかも知れませんが?。
ファイルの保存は開く時と同じように、TWICImageを使用しています。拡張子も含めてファイル名を指定する事で、各種ファイル形式に対応しています。
XE3、XE4には、TWICImagを使用しての、保存時にメモリーリークが発生すバグがあり、ガンマ補正では、TWICImage クラスの SaveToFile と SaveToStreamを override して、プログラムの修正をしましたが、helper を使用して FixedSaveTofile と、バグを修正した FixedSaveToStream を追加して、メモリーリークに対応しました。
unit WicFileSave;
interface
uses
ActiveX, WinCodec, Classes, Graphics;
type
TWICImageHelper = class helper for TWICImage
public
procedure FixedSaveTofile(const Filename: string);
procedure FixedSaveToStream(Stream: TStream);
end;
implementation
{ TWICImageHelper }
procedure TWICImageHelper.FixedSaveTofile(const Filename: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(Filename, fmCreate);
try
FixedSaveToStream(Stream);
finally
Stream.Free;
end;
end;
procedure TWICImageHelper.FixedSaveToStream(Stream: TStream);
var
FGData: TMemoryStream;
Encoder: IWICBitmapEncoder;
Frame: IWICBitmapFrameEncode;
Props: IPropertyBag2;
LStreamAdapter: IStream;
PixelFormat: TGUID;
LStream: IWICStream;
Palette: IWICPalette;
begin
FGData := TMemoryStream.Create;
try
LStreamAdapter := TStreamAdapter.Create(FGData);
ImagingFactory.CreateStream(LStream);
LStream.InitializeFromIStream(LStreamAdapter);
ImagingFactory.CreateEncoder(EncoderContainerFormat, guid_null, Encoder);
Encoder.Initialize(LStream, WICBitmapEncoderNoCache);
Encoder.CreateNewFrame(Frame, Props);
Frame.Initialize(Props);
Handle.GetPixelFormat(PixelFormat);
Frame.SetPixelFormat(PixelFormat);
Frame.SetSize(Width, Height);
ImagingFactory.CreatePalette(Palette);
Handle.CopyPalette(Palette);
Frame.SetPalette(Palette);
Frame.WriteSource(Handle, nil);
Frame.Commit;
Encoder.Commit;
FGData.Position := 0;
Stream.CopyFrom(FGData, FGData.Size);
finally
FGData.Free;
end;
end;
end.
本プログラムは、プログラムを簡単にするために、浮動小数点の配列を使用しています、その為、沢山のメモリーを消費します。
画像データーによっては、1G 以上のメモリーを消費してしまいます。
しかし、アンシャープマスキングが、どの様な計算をしているのかは、よく分かると思います。
”アンシャープマスキング その2” に、GDIPlus1.1による、アンシャープマスクのプログラム例を作成してあります。
GDIPlus1.1には、ぼかしマスク、アンシャープマスキングが組み込まれていて、簡単にプログラムを組むことが出来ます。
unsharp_masking.zip