シャープネス
画像のシャープさを改善するのにアンシャープマスクによる方法、デコンボリューションによる方法がありますが、デコンボリューションはFFTを使用しても、演算回数が膨大でデジタルカメラの写真画像の場合、非常に時間がかかり実用的でないので、演算回数の少ない、シャープネスを検討してみました。
シャープネスマスク
一番左は整数演算用のフィルター値ですが、シャープ化の制御をする為に、係数Kを適用したのが左から二番目です。
三番目は、フィルター値をどの様に設定すれば良いかを表しています。
微分をするために、マスク中央値以外をマイナスのフィルター値に設定
マスクの中央値を、周囲の合計値の符号を反転した値に1を加算した値にすればシャープ化マスクのフィルター値となります。
ここで紹介しているプログラムでは、先に中央値を決め、1を引いた値を反転して周囲に配分する方式をとっています。
シャープネスの場合、微分値に元の画像を加算した形になります。
一回のマスク計算(カラーの場合三回)で済むので、高速演算が可能となります。
計算結果は、アンシャープと同じです。
アンシャープ
の場合、ボケ画像を作成、元画像との差分に元画像を加算していますが、ここでのシャープネスの計算は、フィルター値をボケ画像差分+元画像の値にして、一回のマスク計算で済もせているだけです。
整数で演算した方が早いのですが、現在は浮動小数点演算を使用しても十分に早いので、ストレスを感じることはないでしょう。
上記のマスクの値は、中央値を決め、中央値から1を引いて、残った値を中央値の周囲にマイナスとして配分しています。
中央からの距離の自乗に反比例するようにしています。
マスクの値の合計値は1になります。
3×3、5×5、7×7のマスクを作成してみましたが、マスクを大きくしても、効果は上がりません。
3×3のシャープネスマスクだけで良いようです。
中央値に1以下を設定するめと、平均化され、ボケ画像となります。
3×3のシャープネスマスクの実行例です。
実際に解像度が上がっているわけではないのですが、見た目には、かなり鮮明な画像になっています。
プログラム例
unit Main; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.Extdlgs, Vcl.grids, System.UITypes, System.Math; type B2array = array of array of Byte; D2array = array of array of Double; TForm1 = class(TForm) FileOpenBtn: TButton; ScrollBox1: TScrollBox; Image1: TImage; OpenPictureDialog1: TOpenPictureDialog; magnificationEdit: TLabeledEdit; SharpnessGrid: TStringGrid; SharpnessEdit: TLabeledEdit; Timer1: TTimer; RepaintBtn: TButton; MaskingBtn: TButton; sourceBtn: TButton; FileSaveBtn: TButton; SavePictureDialog1: TSavePictureDialog; RadioGroup1: TRadioGroup; procedure FileOpenBtnClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure RepaintBtnClick(Sender: TObject); procedure MaskingBtnClick(Sender: TObject); procedure FileSaveBtnClick(Sender: TObject); procedure sourceBtnClick(Sender: TObject); procedure SharpnessEditChange(Sender: TObject); procedure RadioGroup1Click(Sender: TObject); private { Private 宣言 } procedure Imageout(Image: TBitmap); procedure SharpnessMask; procedure Masking; procedure ScanArray; public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} const OpenFileFilter = '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.ico;*.wdp'+ '|*.png|*.png' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.bmp|*.bmp' + '|*.tif|*.tif' + '|*.ico|*.ico' + '|*.wdp|*.wdp'; SaveFileFilter = '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.wdp' + '|*.png|*.png' + '|*.jpg|*.jpg' + '|*.gif|*.gif' + '|*.bmp|*.bmp' + '|*.tif|*.tif' + '|*.wdp|*.wdp'; ImageHWC = 440; // 表示枠サイズ var InputDBitmap : TBitmap; // 入力データー表示用ビットマップ OutputBitmap : TBitmap; // 回転画像表示用ビットマップ GHeight : Integer; // 入力データー画像高さ GWidth : Integer; // 入力データー画像幅 MGHeight : Integer; // 入力データー画像高さ MGWidth : Integer; // 入力データー画像幅 VRect : Trect; // 表示サイズ設定用 InFilename : string; // 入力ファイル名 RedMat : B2array; GrnMat : B2array; BluMat : B2array; sharparray : D2array; arBlu : D2array; // データ実数部(入出力兼用) aiBlu : D2array; // データ虚数部(入出力兼用) TimerF : Byte; sharp : integer; scand : array of integer; // 配列スキャン用配列の作成 procedure TForm1.ScanArray; var MDIV2, x: integer; begin case RadioGroup1.ItemIndex of 0: sharp := 3; 1: sharp := 5; 2: sharp := 7; end; setlength(scand, sharp); MDIV2 := sharp div 2; // -----スキャン配列の作成------- for x := - MDIV2 to MDIV2 do scand[x + MDIV2] := x; end; //------------------------------------------- // シャープネスフィルター作成 // フィルターの値の合計が1になるようにします // a の値は負数に設定をします。 // a a a // a c a // a a a // a = - (c - 1) / 8 //------------------------------------------- procedure TForm1.SharpnessMask; var x, y : integer; MDIV2 : integer; ad : double; C, at : Double; check : integer; Tsharp : Double; begin setlength(sharparray, sharp, sharp); val(SharpnessEdit.Text, C, check); if check <> 0 then begin application.MessageBox('Sharpness 中心値入力に間違いがあります。','注意',0); exit; end; if C < 0 then begin application.MessageBox('Sharpness 中心値 0以下ではいけません。','注意',0); exit; end; // ----シャープネス配列の作成---- MDIV2 := sharp div 2; Tsharp := 0; for y := - MDIV2 to MDIV2 do for x := - MDIV2 to MDIV2 do begin // 距離の自乗分の一計算 配列の中央値は0 if (x = 0) and (y = 0) then ad := 0 else ad := 1 / (y * y + x * x); // 配列に保存 sharparray[y + MDIV2 , X + MDIV2] := ad; // 合計値計算 Tsharp := Tsharp + ad; end; // 合計値が-(C-1)になるように補正 at := - (C - 1) / Tsharp; for y := 0 to sharp - 1 do for x := 0 to sharp - 1 do sharparray[x, y] := sharparray[x, y] * at; // 配列の中央値 C をセット sharparray[MDIV2 , MDIV2] := C; // ------------------------------ // ストリンググリッドの設定 SharpnessGrid.ClientWidth := (SharpnessGrid.DefaultColWidth + 1) * (sharp + 1) - 1; SharpnessGrid.ColCount := sharp + 1; SharpnessGrid.ClientHeight := (SharpnessGrid.DefaultRowHeight + 1) * (sharp + 1) - 1; SharpnessGrid.RowCount := sharp + 1; // 固定セルにナンバーリング for y := - MDIV2 to MDIV2 do SharpnessGrid.Cells[0 , Y + MDIV2 + 1] := intTostr(Y); for x := - MDIV2 to MDIV2 do SharpnessGrid.Cells[X + MDIV2 + 1, 0] := intTostr(X); // シャープネスフィルター値の表示 for x := 0 to sharp - 1 do for y := 0 to sharp - 1 do begin SharpnessGrid.Cells[x + 1, y + 1] := FloatTostrF(sharparray[x , y], ffFixed, 5,4); end; application.ProcessMessages; end; //------------------------ // 変倍出力 //------------------------ procedure TForm1.Imageout(Image: TBitmap); var Rect0 : Trect; magnification : Double; Check : Integer; MW, MH : Integer; begin Val(magnificationEdit.Text,magnification,Check); if Check <> 0 then begin application.MessageBox('表示倍率入力に間違いがあります。','注意',0); exit; end; MW := Round(GWidth * magnification); MH := Round(GHeight * magnification); Rect0 := Rect(0, 0, MW, MH); Image1.Width := MW; Image1.Height := MH; Image1.Picture.Bitmap.SetSize(MW, MH); Image1.Canvas.StretchDraw(Rect0, Image); // 出力枠に変倍出力 end; //---------------------------------- // シャープネス処理 //---------------------------------- procedure TForm1.Masking; var x, y : integer; mx, my : integer; dx, dy : integer; Step : integer; data : Double; PBA : PByteArray; DI : Byte; WP : integer; begin ScanArray; SharpnessMask; // シャープネスフィルターの生成 for Step := 0 to 2 do begin for y := 0 to MGHeight do for x := 0 to MGWidth do begin case Step of 0 : arBlu[y, x] := BluMAT[y][x]; 1 : arBlu[y, x] := GrnMat[y][x]; 2 : arBlu[y, x] := RedMat[y][x]; end; aiBlu[y, x] := 0.0; end; // ----- シャープネス計算 ------- for y := 0 to MGHeight do for x := 0 to MGWidth do begin for my := 0 to sharp - 1 do begin dy := y + scand[my]; if dy < 0 then dy := - dy; if dy > MGHeight then dy := MGheight - scand[my]; for mx := 0 to sharp - 1 do begin dx := x + scand[mx]; if dx < 0 then dx := - dx; if dx > MGWidth then dx := MGWidth - scand[mx]; aiBlu[y, x] := aiBlu[y, x] + arBlu[dy, dx] * sharparray[my, mx]; end; end; end; // ------------------------------ for Y := 0 to MGheight do begin PBA := OutputBitmap.ScanLine[Y]; for X := 0 to MGWidth do begin WP := X * 3; data := aiBlu[y, x]; if data > 255 then data := 255; if data < 0 then data := 0; DI := Round(data); case Step of 0 : PBA[WP] := DI; 1 : PBA[WP + 1] := DI; 2 : PBA[WP + 2] := DI; end; end; end; end; Imageout(OutputBitmap); // 出力枠に変倍出力 FileSaveBtn.Enabled := True; RepaintBtn.Enabled := True; sourceBtn.Enabled := True; MaskingBtn.Enabled := False; end; //------------------------------------------------------------------------------- // シャープネス実行 // ボタンのデスエブルアニメション実行の待ち合わせの為タイマーで演算開始遅延します //------------------------------------------------------------------------------- procedure TForm1.MaskingBtnClick(Sender: TObject); begin FileSaveBtn.Enabled := False; RepaintBtn.Enabled := False; MaskingBtn.Enabled := False; sourceBtn.Enabled := False; Timer1.Enabled := True; end; //---------------------------- // 計算結果の再表示 //---------------------------- procedure TForm1.RepaintBtnClick(Sender: TObject); begin Imageout(OutputBitmap); // 出力枠に変倍出力 end; //---------------------------------- // ソース画像の表示 //---------------------------------- procedure TForm1.sourceBtnClick(Sender: TObject); begin Imageout(InputDBitmap); // 出力枠に変倍出力 end; //----------------------------------- // 最計算の為 マスクボタンイネーブル //----------------------------------- procedure TForm1.SharpnessEditChange(Sender: TObject); begin MaskingBtn.Enabled := True; end; procedure TForm1.RadioGroup1Click(Sender: TObject); begin MaskingBtn.Enabled := True; end; //------------------------------- // 表示計算タイミングの制御 //------------------------------- procedure TForm1.Timer1Timer(Sender: TObject); begin Timer1.Enabled := False; case TimerF of 0 : begin ScanArray; SharpnessMask; TimerF := 1; end; 1: Masking; end; end; //--------------------------------------------------- // ファイルのオープン カラーデーターの生成 //--------------------------------------------------- procedure TForm1.FileOpenBtnClick(Sender: TObject); var WIC : TWICImage; X, Y : Integer; PBA : PBytearray; WP : Integer; begin VRect := Rect(0, 0, Image1.Width, Image1.Height); Image1.Canvas.Brush.Style := bsSolid; Image1.Canvas.Brush.Color := clBtnface; Image1.Canvas.FillRect(VRect); // Canvas 画像消去 OpenPictureDialog1.Filter := OpenFileFilter; // ファイルオープンフィルターの設定 if OpenPictureDialog1.Execute = true then // ファイルが指定されたら begin WIC := TWICImage.Create; // TWICImageの生成 try InFilename := OpenPictureDialog1.FileName; // ファイル名の取得 WIC.LoadFromFile(InFilename); // 画像の読み込み GHeight := WIC.Height; // 画像高さ取得 GWidth := WIC.Width; // 画像幅 Image1.Width := GWidth; Image1.Height:= GHeight; Image1.Picture.Bitmap.SetSize(GWidth, GHeight); InputDBitmap.Width := GWidth; InputDBitmap.Height := GHeight; InputDBitmap.Canvas.Draw(0, 0, WIC); // ビットマップに描画フォーマット変換 // InputDBitmap.Assign(WIC); // Tbitmap に TWICImageをコピー finally WIC.Free; // TWICImage 解放 end; end else exit; MGHeight := GHeight - 1; // 入力データー画像高さ MGWidth := GWidth - 1; // 入力データー画像幅 OutputBitmap.Width := GWidth; OutputBitmap.Height := GHeight; // 作業用配列の確保 setLength(RedMat, GHeight, GWidth); setLength(GrnMat, GHeight, GWidth); setLength(BluMat, GHeight, GWidth); setlength(arBlu, GHeight, GWidth); setlength(aiBlu, GHeight, GWidth); // カラー分解 for Y := 0 to MGheight do begin PBA := InputDBitmap.ScanLine[Y]; for X := 0 to MGWidth do begin WP := X * 3; // ラプラシアン用グレイ画像生成 // カラー用三色分解 BluMAT[Y, X] := PBA[WP]; GrnMAT[Y, X] := PBA[WP + 1]; RedMAT[Y, X] := PBA[WP + 2]; end; end; Imageout(InputDBitmap); // 出力枠に変倍出力 MaskingBtn.Enabled := True; sourceBtn.Enabled := True; RadioGroup1.Enabled := True; end; //------------------------------ // 画像のファイルへの保存 //------------------------------ procedure TForm1.FileSaveBtnClick(Sender: TObject); var WIC : TWicImage; WICF : TWicImageFormat; Fname : String; ExeStr : String; FnameTop: String; Findex : integer; function WFormatSet: Boolean; // 拡張子によるファイルフォーマット設定 begin Result := false; ExeStr := LowerCase(ExeStr); if ExeStr = '.jpg' then begin WICF := Wifjpeg; Result := True; end; if ExeStr = '.jpeg' then begin WICF := Wifjpeg; Result := True; end; if ExeStr = '.tif' then begin WICF := Wiftiff; Result := True; end; if ExeStr = '.tiff' then begin WICF := Wiftiff; Result := True; end; if ExeStr = '.png' then begin WICF := Wifpng; Result := True; end; if ExeStr = '.gif' then begin WICF := Wifgif; Result := True; end; if ExeStr = '.bmp' then begin WICF := Wifbmp; Result := True; end; if ExeStr = '.wdp' then begin WICF := WifWMPhoto; Result := True; end; if ExeStr = '.hdp' then begin WICF := WifWMPhoto; Result := True; end; end; begin SavePictureDialog1.Filter := SaveFileFilter; // SavePictureDialog1.DefaultExt := GraphicExtension(TWicImage); if not SavePictureDialog1.Execute then exit; ExeStr := ExtractFileExt(SavePictureDialog1.FileName); if ExeStr = '' then begin // 拡張子がなかったら Findex := SavePictureDialog1.FilterIndex; // FilterIndexによる拡張子の設定 case Findex of 1, 3 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.jpg'); // 拡張子の設定 2 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.png'); // 拡張子の設定 4 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.gif'); // 拡張子の設定 5 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.bmp'); // 拡張子の設定 6 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.tif'); // 拡張子の設定 7 : Fname := ChangeFileExt(SavePictureDialog1.FileName,'.wdp'); // 拡張子の設定 end; end else Fname := SavePictureDialog1.FileName; ExeStr := ExtractFileExt(Fname); // 拡張子だけ取り出し if not WFormatSet then begin // 拡張子によるファイルフォーマット設定と確認 application.MessageBox('ファイルの拡張子が間違っています。','注意', 0); exit; end; FnameTop := ExtractFileName(Fname); // ファイル名だけ取り出し if Length(FnameTop) = Length(ExeStr) then begin // ファイル名の長さ確認 application.MessageBox('ファイル名がありません。','注意', 0); exit; end; if FileExists(Fname) then // ファイル名によるファイル検索 if MessageDlg('既に同じ名前のファイルがあります上書きしますか ' + ExtractFileName(Fname) + '?', mtConfirmation, [mbYes, mbNo], 0, mbNo) = IDNo then exit; WIC := TWicImage.Create; // TWicImage生成 try WIC.Assign(OutputBitmap); // TWicImageにビットマップデーター割り付け WIC.ImageFormat := WICF; // 保存フォーマットセット WIC.SaveTofile(Fname); // ファイルの書き出し finally WIC.Free; // TWicImage解放 end; end; //---------------- // 初期設定 //---------------- procedure TForm1.FormCreate(Sender: TObject); begin Timer1.Enabled := False; Image1.Width := ImageHWC div 2; Image1.Height := ImageHWC div 2; ScrollBox1.Height := ImageHWC; ScrollBox1.Width := ImageHWC; Image1.Top := 0; Image1.Left := 0; InputDBitmap := TBitmap.Create; OutputBitmap := TBitmap.Create; InputDBitmap.PixelFormat := pf24bit; OutputBitmap.PixelFormat := pf24bit; MaskingBtn.Enabled := False; FileSaveBtn.Enabled := False; RepaintBtn.Enabled := False; sourceBtn.Enabled := False; RadioGroup1.Enabled := False; TimerF := 0; Timer1.Enabled := True; end; //---------------- // 終了処理 //---------------- procedure TForm1.FormDestroy(Sender: TObject); begin InputDBitmap.Free; OutputBitmap.Free; end; end.
Sharpness.zip ファイルの中には、FFTを使用した シャープネスもあります。
FFTのプログラムは、アンシャープマスク、デコンボリューションのプログラムにシャープネスを追加したもので、各プロセスによる比較が出来ます。