2020/06/04
  FFTルーチンを変更しました。(僅かだが早いものに変更しました。)
2020/04/14
  実数部、虚数部をデーター数で割るルーチンの変更

2019/03/22 外周処理の追加
 ブレ補正後に現れる、画像内の縞模様を低減するため、外周処理を追加しました。
 完全ではありませんが、かなり低減されています。

手振れ写真の補正

 FFT Deconvolutionを利用して、手振れ画像の補正を検討してみました。

縞模様の発生を少なくする為の、外周の処理追加
 画像の縦横サイズがnになっていない場合出来る余白を、中間色のグレイにし、
画像の外周をグレイから画像色にブレ補正の幅で変化させています。


 通常のDeconvolutionと違うのは、補正データーの作成方法のみです。
FFT Deconvolutionは、レンズのボケ特性にあわせて、ガウス分布でデーターを作成していましたが、手振れの角度を合わせた直線で補正を試みてみました。
速度変動もあるので、それにも合わせた方がよいのですが、速度変動の解析が難しかったので、速度一定としました。

 左図は、垂直にブレた場合の補正の実行例です。
マスクサイズの値は、ブレ量に値を合わせます。
Rangeは、補正値を適用するFFT変換した値の下限値です。
レンズのボケ補正の場合は、Range以下の場合0(ゼロ)を適用していましたが、ブレ補正の場合は、1/Range を適用したほうが良いようです。
一応0(ゼロ)と、1/Rangeの選択が出来る様にしました。
角度はブレの方向です。
例では、垂直方向で9ドット、一定の値となっていますが、本来は速度の遅いところは値を大きくして早いところは小さくするような補正が必要かと思います。
完全には、ブレをとれないようですが、解像度はかなり改善されています。
 ブレ方向のエッジ部のデーターの関係で、画像に縞模様が発生しています、左図例は画像周辺処理のない場合です。



 Rangeの値を調整して、縞模様の発生を少なくしてみました、解像度は若干低下します。
Rangeの値は0.3です。
上図、左がブレ補正前で、中央がブレ補正後です。
一番右が外周をグレイにした後、ブレ補正をした場合で、Rangeの値0.25で、縞模様の発生が少なくなり、解像度も若干、改善されています。

 下図、同じ値で、ブレぼかし画像をつくり、それを元へ戻すと結構よく元へ戻っています。
しかし、外周補正を行うと、エッジのデーターが失われる為、逆に縞模様が発生します。
カメラのブレと、ブレを此処のプログラムの計算で作り出した場合の差のようです。

 とにかく、手振れの画像は、完全に修正することは困難なので、手振れを発生させないことがやはり一番のようです。
現在のカメラの手振れ画像防止機能は素晴らしい機能です。
望遠レンズの場合は、手振れ補正機能と、出来る限り早いシャッター速度を利用する事です。
現在のカメラのセンサーは、可成り高感度になっていて、少しぐらい暗くてもフラッシュ無しでの撮影が可能です。
それでも、ブレが出てしまった場合、僅かなブレなら、本プログラムでも、結構よく補正できるようです。

プログラム

unit FFT_DconvMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtDlgs, Vcl.StdCtrls, Vcl.ExtCtrls, system.Math,
  system.UITypes, Vcl.Grids, system.Types;

type
  D2array = array of array of Double;
  B2array = array of array of Byte;
  TDarray = array[0..0] of Double;
  PDarray = ^TDarray;

  TForm1 = class(TForm)
    FileOpenBtn: TButton;
    OpenPictureDialog1: TOpenPictureDialog;
    MaskingBtn: TButton;
    RadioGroup1: TRadioGroup;
    FileSaveBtn: TButton;
    SavePictureDialog1: TSavePictureDialog;
    SigumaGrid: TStringGrid;
    sourceBtn: TButton;
    RangeEdit: TLabeledEdit;
    Timer1: TTimer;
    RepaintBtn: TButton;
    ScrollBox1: TScrollBox;
    Image1: TImage;
    magnificationEdit: TLabeledEdit;
    TrimmingBtn: TButton;
    Memo1: TMemo;
    MaskSizeEdit: TLabeledEdit;
    AngleEdit: TLabeledEdit;
    FrameBtn: TButton;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    procedure FileOpenBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure MaskingBtnClick(Sender: TObject);
    procedure FileSaveBtnClick(Sender: TObject);
    procedure sourceBtnClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
    procedure RangeEditChange(Sender: TObject);
    procedure RepaintBtnClick(Sender: TObject);
    procedure TrimmingBtnClick(Sender: TObject);
    procedure Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure MaskSizeEditChange(Sender: TObject);
    procedure AngleEditChange(Sender: TObject);
    procedure FrameBtnClick(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure CheckBox2Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure FFT2(var a_rl, a_im: D2array; inv, Y_EXP, X_EXP: Integer);
    procedure cstb(length: Integer; inv: integer; var sin_tbl, cos_tbl: array of double);
    procedure rvmtx(var a, b: D2array; xsize, ysize: Integer);
    procedure Imageout(Image: TBitmap);
    procedure Trimming;
    procedure TrimmingImage;
    procedure trimming_imageout(Image: TBitmap);
    procedure TestRoutine;
    function LineDataset: boolean;
    procedure Edge_processing;
    procedure fft(a_rl, a_im: PDarray; length: integer; inv: integer;
                  sin_tbl, cos_tbl: PDarray; m: PMarray);
  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;                           // 表示枠サイズ


  Line_blur     = 0;
  Deconvolution = 1;

var
  InputDBitmap  : TBitmap;                    // 入力データー表示用ビットマップ
  OutputBitmap  : TBitmap;                    // 画像表示用ビットマップ
  SHeight       : Integer;                    // 入力データー画像高さ
  SWidth        : Integer;                    // 入力データー画像幅
  GHeight       : Integer;                    // 計算データー画像高さ
  GWidth        : Integer;                    // 計算データー画像幅
  THeight       : Integer;                    // トリミングデーター画像高さ
  TWidth        : Integer;                    // トリミングデーター画像幅

  VRect         : Trect;                      // 表示サイズ設定用
  InFilename    : string;                     // 入力ファイル名

  threshold     : Double;                     // 範囲指定
  gaus          : integer;                    // ガウシアン配列サイズ

  Gaussian : array of array of double;
  X_EXP, Y_EXP  : Integer;          // 2^n 累乗
  TrimmingF     : boolean;          // トリミングフラグ
  TNX, TNY      : integer;          // トリミング幅,高さ 2^n 累乗
  trimTop       : integer;          // トリム上
  trimLeft      : integer;          // トリム左
  trimHeight    : integer;          // トリム高さ
  trimWidth     : integer;          // トリム幅
  TrimScale     : double;           // トリムスケール
  Sctext        : string;

const
  kr  = 0.299;                     // R 輝度変換係数
  kg  = 0.587;                     // G 輝度変換係数
  kb  = 0.114;                     // B 輝度変換係数

//------------------------
// 変倍出力
//------------------------
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(Image.Width * magnification);
  MH := Round(Image.Height * 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;

//--- rvmtx --- 2次元データの転置 -------------------------------------------
// a: 2次元入力データ1
// b: 2次元出力データ1
// c; 2次元入力データ2
// d; 2次元出力データ2
// xysize: 水平,垂直データ個数
// yxsize: 垂直,水平データ個数
//-----------------------------------------------------------------------------
procedure TForm1.rvmtx(var a, b, c, d: D2array; xysize, yxsize: Integer);
var
  i, j: integer;
begin
  for i := 0 to yxsize - 1 do
    for j := 0 to xysize - 1 do begin
      b[j, i] := a[i, j];
      d[j, i] := c[i, j];
    end;
end;

// ------------------------新 FFT計算 ------------------------------
//   a_rl:   データ実数部(入出力兼用)
//   a_im:   データ虚数部(入出力兼用)
//   length: データー個数
//   inv:     変換フラグ F = -1 変換 F = 1 逆変換
//   sin_tbl, cos_tbl sin, cos テーブル
//    m       回転子バッファ
// -----------------------------------------------------------------
procedure TForm1.fft(a_rl, a_im: PDarray; length: integer; inv: integer;
                     sin_tbl, cos_tbl: PDarray; m: PMarray);
var
  I, J, nd, ns, L, arg, it, ib: integer;
  a, b, t : double;
begin
  nd := length div 2;           // データ数の1/2
  ns := nd;                     // 2進数の最上位値
  while (ns > 0) do begin       // 2進数の最上位の値が0になるまで繰り返し
    L := 0;                     // L=0
    while L < length do begin   // Lの値をlengthまで、
      arg := m[L] div 2;        // 回転子=前の回転子の値/2 m[0]は常に0
      for it := L to L + ns - 1 do begin    // itの値をLの値からL+2進数の最上位の値まで繰り返し
        ib := it + ns;                      // ib=it + 2進数の最上位の値
        a := a_rl[ib] * cos_tbl[arg] - a_im[ib] * sin_tbl[arg];
        b := a_im[ib] * cos_tbl[arg] + a_rl[ib] * sin_tbl[arg];
        a_rl[ib] := a_rl[it] - a;
        a_im[ib] := a_im[it] - b;
        a_rl[it] := a_rl[it] + a;
        a_im[it] := a_im[it] + b;
        m[it] := arg;           // m[it]に回転子を保存
        m[ib] := arg + nd;      // m[ib]に回転子 + データ数の1/2 を保存
      end;
      L := L + ns + ns;         // Lの値を0からnまで、2×最上位の値のstepで繰り返し
    end;
    ns := ns div 2;             // 2進数の最上位の値を繰り下げ
  end;
  if inv = 1 then begin         // inv 1 周波数変換 -1 逆変換 2 フィルター
    t := 1 / length;
    for I := 0 to length - 1 do begin
      a_rl[I] := a_rl[I] * t;
      a_im[I] := a_im[I] * t;
    end;
  end;
  for I := 0 to length - 1 do begin     // データーの並べ替えビット反転処理
    if I < m[I] then begin
      J := m[I];
      t := a_rl[I];
      a_rl[I] := a_rl[J];
      a_rl[J] := t;
      t := a_im[I];
      a_im[I] := a_im[J];
      a_im[J] := t;
    end;
  end;
end;

//--- cstb --- SIN,COSテーブル作成 --------------------------------------------
//    length:     データ個数
//    inv:        1: DFT, -1: 逆DFT
//    sin_tbl:    SIN計算用テーブル
//    cos_tbl:    COS計算用テーブル
//-----------------------------------------------------------------------------
procedure TForm1.cstb(length: Integer; inv: Integer; var sin_tbl, cos_tbl: array of double);
var
  i : Integer;
  xx, arg : Double;
begin
  xx := - PI * 2.0 / length;
  if inv < 0 then xx := - xx;
  for i := 0 to length - 1 do begin
    arg := i * xx;
    sin_tbl[i] := sin(arg);
    cos_tbl[i] := cos(arg);
  end;
end;

//--- fft2 --- 2次元FFTの実行 ---------------------------------------------
//   (GWidth,GHeightが2のべき乗の場合に限ります)
//    a_rl:    データ実数部(入出力兼用)
//    a_im:    データ虚数部(入出力兼用)
//    inv:    1: DFT,-1: 逆DFT
//  fftのルーチンに値を渡すのにポインター @ を使用していますが
//  二次元配列の一行だけを渡す為に使用しています。
//  一次元の配列を渡すのに ポインター @ を使用する必要性はありません。
//  配列の値を渡すのを統一するのに使用しています。
//-----------------------------------------------------------------------------
procedure TForm1.FFT2(var a_rl, a_im: D2array; inv, Y_EXP, X_EXP: Integer);
var
  b_rl      : D2array;              // データ転置作業用配列(実数部)
  b_im      : D2array;              // データ転置作業用配列(虚数部)
  hsin_tbl  : array of Double;      // 水平用SIN計算用テーブル
  hcos_tbl  : array of Double;      // 水平用COS計算用テーブル
  vsin_tbl  : array of Double;      // 垂直用SIN計算用テーブル
  vcos_tbl  : array of Double;      // 垂直用COS計算用テーブル
  m_x       : M2array;               // データーの並べ替えビット反転処理with方向
  m_y       : M2array;               // データーの並べ替えビット反転処理Height方向
  i         : Integer;
begin
  // 配列の確保
  SetLength(b_rl, GWidth, GHeight);
  SetLength(b_im, GWidth, GHeight);
  SetLength(hsin_tbl, GWidth);
  SetLength(hcos_tbl, GWidth);
  SetLength(vsin_tbl, GHeight);
  SetLength(vcos_tbl, GHeight);
  SetLength(buf_x, GWidth);
  SetLength(buf_y, GHeight);

  cstb(GWidth,  inv, hsin_tbl, hcos_tbl);     // 水平用SIN,COSテーブル作成
  cstb(GHeight, inv, vsin_tbl, vcos_tbl);     // 垂直用SIN,COSテーブル作成
  // 水平方向のFFT
  for i := 0 to GHeight - 1 do
    fft(@a_rl[i][0], @a_im[i][0], GWidth, inv, @hsin_tbl[0], @hcos_tbl[0], @m_x[0]);
  // 2次元データの転置 X方向とY方向の値を入れ替えます
  // fft1coreのscanがX方向にしか出来ない為に行います
  rvmtx(a_rl, b_rl, a_im, b_im, GWidth, Gheight);
  // 垂直方向のFFT
  for i := 0 to GWidth - 1 do
    fft(@b_rl[i][0], @b_im[i][0], GHeight, inv, @vsin_tbl[0], @vcos_tbl[0], @m_y[0]);
  // 2次元データの転置 X方向とY方向の値を入れ替えます
  // X方向とY方向を入れ替えているので元へ戻します
  rvmtx(b_rl, a_rl, b_im, a_im, GHeight, GWidth);
end;

//---------- ブレ補正データーの作成 -----------
// 角度と長さの設定
//---------------------------------------------
function TForm1.LineDataset: boolean;
var
  X, Y, CH   : integer;
  KS, Angle  : Double;
  Total   : Double;
  MDIV2, MDIV : integer;
  MatSize : integer;
  rad, ypos, xpos : Double;
begin
  result := False;
  val(MaskSizeEdit.Text, gaus, CH);
  if CH <> 0 then begin
    application.MessageBox('MaskSize 入力に間違いがあります。', 'MaskSize', 0);
    exit;
  end;
  if gaus < 3 then begin
    application.MessageBox('MaskSize の値が小さすぎます。', 'MaskSize', 0);
    exit;
  end;
  if (gaus > GWidth div 5) or (gaus > GHeight div 5) then begin
    application.MessageBox('MaskSize の値が大き過ぎます。', 'MaskSize', 0);
    exit;
  end;
  if gaus mod 2 = 0 then begin
    application.MessageBox('MaskSizeを奇数にしてください。', 'MaskSize', 0);
//    exit;
  end;
  gaus := gaus + 2;
  val(AngleEdit.Text, Angle, CH);
  if CH <> 0 then begin
    application.MessageBox('角度に間違いがあります。', '角度', 0);
    exit;
  end;
  repeat
    if Angle < 0 then Angle := Angle + 180;
    if Angle >= 180 then Angle := Angle - 180;
  until (Angle >= 0) and (Angle < 180);
  rad := Angle / 180 * pi;
  if rad > 0 then rad := -rad;
  setlength(Gaussian, gaus, gaus);
  MatSize := gaus;
  // ストリンググリッドの設定
  SigumaGrid.ClientWidth  := (SigumaGrid.DefaultColWidth + 1)  * (MatSize + 1) - 1;
  SigumaGrid.ColCount := MatSize + 1;
  SigumaGrid.ClientHeight := (SigumaGrid.DefaultRowHeight + 1) * (MatSize + 1) - 1;
  SigumaGrid.RowCount := MatSize + 1;
  // 固定セルにナンバーリング
  MDIV2 := MatSize div 2;
  MDIV := MDIV2;
  if MatSize mod 2 = 0 then MDIV := MDIV2 - 1;
  for Y := - MDIV to MDIV2 do SigumaGrid.Cells[0 , Y + MDIV + 1] := intTostr(Y);
  for X := - MDIV to MDIV2 do SigumaGrid.Cells[X + MDIV + 1,  0] := intTostr(X);

  // マスクデーター計算と表示
  Total := gaus - 2;
  for Y := - MDIV to MDIV2 do
    for X := - MDIV to MDIV2 do begin
      Gaussian[X + MDIV, Y + MDIV] := 0;
    end;

  for X := - MDIV2 +1 to MDIV -1 do begin
    ypos := sin(rad) * X;
    xpos := cos(rad) * X;
    Y := round(ypos);
    CH := round(xpos);
    Gaussian[Y + MDIV, CH + MDIV] := 1
  end;

  // フィルターの値変換 合計値が1になるように変換します
  for Y := - MDIV to MDIV2 do
    for X := - MDIV to MDIV2 do begin
      KS := Gaussian[X + MDIV, Y + MDIV] / Total;
      Gaussian[X + MDIV, Y + MDIV] := KS;
      // グリッドに表示
      SigumaGrid.Cells[Y  + MDIV + 1, X + MDIV + 1] := FloatTostrF(KS, ffFixed, 6,5);
    end;
  result := true;
end;



//-------------------------------------------------------------------------------------------------
type
  TPrgbarry = array[0..0] of Trgbtriple;  // 24ビットカラーレコード 32ビット用はTRGBQuadArray
  Prgbarray = ^TPrgbarry;                 // ポインター
                                          // 配列のポインターが必要なだけなので、長さは1 [0..0]で問題ありません。
var
  conv_real   : D2array;             // フィルター用実数部
  conv_image  : D2array;             // フィルター用虚数部
  half_cw     : integer;
  half_ch     : integer;
  arBlu       : D2array;             // データ実数部(入出力兼用)
  aiBlu       : D2array;             // データ虚数部(入出力兼用)
  r, i        : Double;
  PBA         : Prgbarray;
  DI          : Integer;
  rdr         : Double;
  rdi         : Double;

//------------------------------
// フィルター計算選択
//------------------------------
procedure TForm1.MaskingBtnClick(Sender: TObject);
begin
  MaskingBtn.Enabled := False;
  FileOpenBtn.Enabled := False;
  FileSaveBtn.Enabled := False;
  RepaintBtn.Enabled := False;
  sourceBtn.Enabled := False;
  TrimmingBtn.Enabled := False;
  if TrimmingF then TrimmingImage;
  Memo1.Clear;
  Memo1.Lines.Add('計算時間');
  Memo1.Lines.Add(' Start ' + DateTimeToStr(Now));
  Timer1.Enabled := True;
end;

//----------------------------
// 再計算許可
//----------------------------
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  MaskingBtn.Enabled := True;
end;

procedure TForm1.RangeEditChange(Sender: TObject);
begin
  MaskingBtn.Enabled := True;
end;

procedure TForm1.MaskSizeEditChange(Sender: TObject);
begin
  MaskingBtn.Enabled := True;
end;

procedure TForm1.AngleEditChange(Sender: TObject);
begin
  MaskingBtn.Enabled := True;
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  MaskingBtn.Enabled := True;
end;

procedure TForm1.CheckBox2Click(Sender: TObject);
begin
  MaskingBtn.Enabled := True;
end;

//--------------------------------
// ボタン表示更新待ち合わせ
//--------------------------------
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  if (RadioGroup1.ItemIndex = Line_blur) or (RadioGroup1.ItemIndex = Deconvolution) then TestRoutine;

  FileSaveBtn.Enabled := True;
  FileOpenBtn.Enabled := True;
  RepaintBtn.Enabled := True;
  sourceBtn.Enabled := True;
  Memo1.Lines.Add(' End   ' + DateTimeToStr(Now));
end;

//-------------------------------------
// Edge processing
// 画像の縁をGray~原色に変化させます
//-------------------------------------
procedure TForm1.Edge_processing;
var
  x, y : integer;
  mdata : array of double;
  gdata : array of byte;
begin
  setlength(mdata, gaus);
  setlength(gdata, gaus);
  for y := 0 to gaus - 1 do begin
    mdata[y] := y / gaus;
    gdata[gaus - 1 - y] := round((128 / gaus) * y);
  end;
  for y := 0 to gaus - 1 do
    for x := y to SWidth - y - 1 do
      begin
        arBlu[y, x] := round(arBlu[y, x] * mdata[y]  + gdata[y]);
        arBlu[Sheight - y - 1, x] := round(arBlu[Sheight - y - 1, x] * mdata[y] + gdata[y]);
      end;
  for x := 0 to gaus - 1 do
    for y := x + 1 to SHeight - x - 2 do
      begin
        arBlu[y, x] := round(arBlu[y, x] * mdata[x] + gdata[x]);
        arBlu[y, Swidth - x - 1] := round(arBlu[y, Swidth - x - 1] * mdata[x] + gdata[x]);
      end;
end;

//--------------------------------------
// ボカシ処理  デコンボリューション処理
//--------------------------------------
procedure TForm1.TestRoutine;
var
  nx, ny, x, y: integer;
  Step        : integer;
  CH          : integer;
  Color       : byte;
begin
  if not LineDataset then exit;
  application.ProcessMessages;
  val(RangeEdit.Text, threshold, CH);
  if CH <> 0 then begin
    application.MessageBox('Deconv Range 入力に間違いがあります。', 'Range', 0);
    exit;
  end;
  if threshold <= 0 then begin
    application.MessageBox('Deconv Range ゼロ及び負数ではいけません。', 'Range', 0);
    exit;
  end;
  if threshold > 1 then begin
    application.MessageBox('Deconv Range 1を超えた場合画像はなくなります。', 'Range', 0);
    exit;
  end;

  // 配列の確保
  // 水平方向1ライン毎の先頭ポインターを使用する為 [V Lines, H Lines] になります
  setlength(conv_real,  GHeight, GWidth);
  setlength(conv_image, GHeight, GWidth);
  setlength(arBlu, GHeight, GWidth);
  setlength(aiBlu, GHeight, GWidth);

  // サイズ 2のn乗に書き込み サイズの合わない部分は黒色又は灰色になります
  if checkbox2.Checked then Color := 128
                       else Color := 0;
  if (SWidth <> GWidth) or (GHeight <> SHeight) then begin
    for y := 0 to Gheight - 1 do begin
      PBA := InputDBitmap.ScanLine[Y];
      if y < Sheight then begin
        for x := SWidth to GWidth - 1 do begin
          PBA[x].rgbtBlue  := Color;
          PBA[x].rgbtGreen := Color;
          PBA[x].rgbtRed   := Color;
        end;
       end
       else begin
        for x := 0 to GWidth - 1 do begin
          PBA[x].rgbtBlue  := Color;
          PBA[x].rgbtGreen := Color;
          PBA[x].rgbtRed   := Color;
        end;
      end;
    end;
  end;

  // フィルター配列のクリア
  for y := 0 to GHeight - 1 do
    for x := 0 to GWidth - 1 do begin
      conv_real[y, x] := 0.0;         // 実数部
      conv_image[y, x] := 0.0;        // 虚数部
    end;
  // 拡張フィルターの設定   実数部値セット
  half_cw := gaus div 2;
  half_ch := gaus div 2;
  if gaus mod 2 = 0 then begin
    half_cw := half_cw - 1;
    half_ch := half_ch - 1;
  end;
  for y := 0 to gaus - 1 do
    for x := 0 to gaus -1 do  begin
      nx := GWidth - half_cw + x;
      ny := Gheight - half_ch + y;
      if nx >= GWidth   then nx := nx - GWidth;
      if ny >= Gheight  then ny := ny - GHeight;
      conv_real[ny][nx] := Gaussian[y][x];
    end;

  // 拡張したフィルターをフーリエ変換します RGB三色共通です
  // 注 フィルターの場合、変換時、データー数による除算処理をしないようにします
  FFT2(conv_real, conv_image, 2,  Y_EXP, X_EXP);

  // RGB 1色ごとにFFTの計算をします。
  for Step := 0 to 2 do begin
    // 原画像を読み込み,2次元FFTの入力データに変換します
    for y := 0 to Gheight - 1 do begin
      PBA := InputDBitmap.ScanLine[Y];
      for x := 0 to GWidth - 1 do begin
      // カラー用三色分解        実数部
        case Step of
          0 :  arBlu[y, x] := PBA[x].rgbtBlue;
          1 :  arBlu[y, x] := PBA[x].rgbtGreen;
          2 :  arBlu[y, x] := PBA[x].rgbtRed;
        end;
        aiBlu[y, x] := 0.0;   // 虚数部
      end;
    end;

    if checkbox2.Checked then Edge_processing;
    // 2次元FFTを実行し 原画像を周波数成分に変換します
    FFT2(arBlu, aiBlu, 1,  Y_EXP, X_EXP);

    // ボカシ処理 ---------------------------
    if RadioGroup1.ItemIndex = Line_blur then begin
      for x := 0 to GWidth - 1 do
        for y := 0 to GHeight - 1 do begin
          // フィルターの実施
          r := arBlu[y][x] * conv_real[y][x]  - aiBlu[y][x] * conv_image[y][x];
          i := arBlu[y][x] * conv_image[y][x] + aiBlu[y][x] * conv_real[y][x];
          arBlu[y][x] := r;
          aiBlu[y][x] := i;
        end;
    end;
    //---------------------------------------------------

    // デコンボリューション処理 -------------------------
    if RadioGroup1.ItemIndex = Deconvolution then begin
      for x := 0 to GWidth - 1 do
        for y := 0 to GHeight - 1 do begin
          // デコンボリューション係数
          rdr := abs(conv_real[y][x]);
          rdi := abs(conv_image[y][x]);
          // ボカシ処理とは逆なので、逆数にします
          // thresholdは値が小さすぎた場合逆数の値が大きくなりすぎるので制限する値です
          // thresholdはゼロでの除算の防止もします
          if rdr > threshold then rdr := 1 / conv_real[y][x]
                             else begin
                               if checkbox1.Checked then rdr := 0
                                                    else rdr := 1 / threshold;
                             end;
          if rdi > threshold then rdi := 1 / conv_image[y][x]
                             else begin
                               if checkbox1.Checked then rdi := 0
                                                    else rdi := 1 / threshold;
                             end;
          // デコンボリューションの生成
          r := arBlu[y][x] * rdr - aiBlu[y][x] * rdi;
          i := arBlu[y][x] * rdi + aiBlu[y][x] * rdr;

          arBlu[y][x] := r;
          aiBlu[y][x] := i;
      end;
    end;
    //---------------------------------------------------


    // 逆FFTを実行して,フィルタ処理された周波数成分を画像に戻します
    FFT2(arBlu, aiBlu, -1, Y_EXP, X_EXP);

    // 結果を画像データに変換
    for Y := 0 to Sheight - 1 do begin
      PBA := OutputBitmap.ScanLine[Y];
      for X := 0 to SWidth - 1 do begin
        DI := Round(arBlu[y][x]);
        if DI > 255 then DI := 255;
        if DI <   0 then DI := 0;
        case Step of
          0: PBA[x].rgbtBlue    := DI;
          1: PBA[x].rgbtGreen   := DI;
          2: PBA[x].rgbtRed     := DI;
        end;
      end;
    end;
  end;
  Imageout(OutputBitmap);               // 出力枠に変倍出力
end;

//--------------
// 元画像の表示
//--------------
procedure TForm1.sourceBtnClick(Sender: TObject);
begin
  Imageout(InputDBitmap);               // 出力枠に変倍出力
end;

//----------------------------
// 計算結果の再表示
//----------------------------
procedure TForm1.RepaintBtnClick(Sender: TObject);
begin
  Imageout(OutputBitmap);               // 出力枠に変倍出力
end;

//-----------------------
// 枠内表示倍率設定
//-----------------------
procedure TForm1.FrameBtnClick(Sender: TObject);
begin
  magnificationEdit.Text := Sctext;
end;

//--------------------------------------------------
// 開いたファイルの画像表示
//--------------------------------------------------
procedure TForm1.trimming_imageout(Image: TBitmap);
begin
  if Image.Width >= Image.Height then begin
    Image1.Width := ImageHWC - 6;
    image1.Height := round((ImageHWC - 6) * image.Height / Image.Width);
  end
  else begin
    Image1.Height := ImageHWC - 6;
    image1.Width := round((ImageHWC - 6) * image.Width / Image.Height);
  end;
  Image1.Picture.Bitmap.SetSize(Image1.Width, image1.Height);
  Image1.Canvas.StretchDraw(Rect(0, 0, Image1.Width, image1.Height), Image);    // 出力枠に変倍出力
  magnificationEdit.Text := floatTostrF(Image1.Width / Image.Width, ffGeneral, 4, 2);
  Sctext := magnificationEdit.Text;
end;

//---------------------------------------------------------------
// 処理画像サイズ縦横を2^nとする時、原画像の縦横を超えない何位で
// 処理画像サイズ縦横を設定し、原画像から取り出します。
// 取り出した部分しか処理されません。
//---------------------------------------------------------------
procedure TForm1.TrimmingBtnClick(Sender: TObject);
begin
  Trimming;
end;

var
  Drawing       : Boolean;                    // マウス選択枠表示フラグ
  Origin        : TPoint;                     // マウススタート位置
  EndPoint      : TPoint;                     // マウスアップ位置
  NewMove       : Boolean;                    // マウス新移動

//-------------------------------------------------------
// 表示された画像に、2^n サイズの取り出し枠を表示します
//-------------------------------------------------------
procedure TForm1.Trimming;
var
  nx : integer;
begin
  if TrimmingF then begin
    if Drawing then begin
      Image1.Canvas.Pen.Mode := pmNotXor;                                   // ペンモード Not Xor
      if not NewMove then begin
        Image1.Canvas.Rectangle(trimLeft, trimtop, trimWidth + trimLeft, trimHeight + trimTop); // 前の線消去
        Image1.Canvas.Pen.Mode := pmCopy;                   // ペンモード通常に
      end;
    end;
    TrimmingF := False;
    Drawing := False;
    exit;
  end;
  if not TrimmingF then TrimmingF := true;
  nx := 1;
  TNX := 0;
  TNY := 0;
  // データ幅の2のべき乗を計算
  // 元画像と等しいか小さい2^nの画像サイズ取得
  repeat
    nx := nx * 2;
    if (nx <= SWidth) then  begin
      TWidth := nx;                        // トリミング幅
      inc(TNX);
    end;
    if (nx <= SHeight) then begin
      THeight := nx;                       // トリミング高さ
      inc(TNY);
    end;
  until (nx >= SWidth) and (nx >= SHeight);
  trimTop  := 0;          // トリム上
  trimLeft := 0;          // トリム左
  TrimScale   := Image1.Width / SWidth;
  trimHeight  := round(THeight * TrimScale);          // トリム高さ
  trimWidth   := round(TWidth  * TrimScale);          // トリム幅
  Image1.Canvas.Brush.Style := bsClear;
  Image1.Canvas.Pen.Style := psSolid;
  Image1.Canvas.Pen.Color := clred;
  Image1.Canvas.Pen.Mode := pmNotXor;                 // ペンモード Not Xor
  Image1.Canvas.Rectangle (trimLeft, trimtop, trimWidth, trimHeight);
  Image1.Canvas.Pen.Mode := pmCopy;                   // ペンモード通常に
  NewMove := False;
  Drawing := True;
end;

//----------------------------------------------------------
// 取り出し枠で表示された部分から画像を取り出し
// InputDBitmapにセットします。
//----------------------------------------------------------
procedure TForm1.TrimmingImage;
var
  X, Y : integer;
  T, L : integer;
  PBB  : Prgbarray;
begin
  // 2^nの画像サイズに設定
  InputDBitmap.Width := TWidth;
  InputDBitmap.Height := THeight;
  T := round(trimTop / TrimScale);
  L := round(trimLeft / TrimScale);
  if SWidth - (L + TWidth) < 0 then L := L - (SWidth - (L + TWidth));
  if SHeight - (T + THeight) < 0 then T := T - (SHeight - (T + THeight));
  for Y := T to T + THeight - 1 do begin
    PBA := OutputBitmap.ScanLine[Y];
    PBB :=  InputDBitmap.ScanLine[Y - T];
    for X := L to L + TWidth - 1 do begin
      PBB[X - L].rgbtBlue := PBA[X].rgbtBlue;
      PBB[X - L].rgbtGreen := PBA[X].rgbtGreen;
      PBB[X - L].rgbtRed := PBA[X].rgbtRed;
    end;
  end;
  GWidth := TWidth;
  GHeight := THeight;
  SWidth  := TWidth;
  SHeight := THeight;
  X_EXP := TNX;
  Y_EXP := TNY;
  OutputBitmap.Width := GWidth;
  OutputBitmap.Height:= GHeight;
  // トリミング終了したのでフラグ解除
  TrimmingF := False;
  // トリミング後の表示サイズ設定
  if GWidth >= GHeight then begin
    Image1.Width := ImageHWC - 6;
    image1.Height := round((ImageHWC - 6) * GHeight / GWidth);
  end
  else begin
    Image1.Height := ImageHWC - 6;
    image1.Width := round((ImageHWC - 6) * GWidth / GHeight);
  end;
  Image1.Picture.Bitmap.SetSize(Image1.Width, image1.Height);
  magnificationEdit.Text := floatTostrF(Image1.Width / GWidth, ffGeneral, 4, 2);
  Sctext := magnificationEdit.Text;
end;

//---------------------------------------------------------
// 画像取り出し枠移動の為のマウスダウンによる設定
//---------------------------------------------------------
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if not TrimmingF then exit;
  Origin := Point(X, Y);                                                  // 座標の保存
  NewMove := False;
  Drawing := True;
end;

//---------------------------------------------------------
// マウスの移動による取り出し枠移動
//---------------------------------------------------------
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  defX, defy : integer;
begin
  if Drawing and (Shift = [ssLeft]) then begin
    Image1.Canvas.Pen.Mode := pmNotXor;                                   // ペンモード Not Xor
    if not NewMove then begin
      Image1.Canvas.Rectangle(trimLeft, trimtop, trimWidth + trimLeft, trimHeight + trimTop); // 前の線消去
    end;
    EndPoint := point(X, Y);                                              // 新しい座標を保存
    defX := EndPoint.X - Origin.X;
    defY := EndPoint.Y - Origin.Y;
    if (trimLeft + defX >= 0) and (trimWidth + trimLeft + defX < image1.Width) then
      trimLeft := trimLeft + defX;
    if (trimtop + defY >= 0) and (trimHeight + trimTop + defY < image1.Height) then
      trimtop  := trimtop  + defY;
    Origin := Point(X, Y);
    Image1.Canvas.Rectangle(trimLeft, trimtop, trimWidth + trimLeft, trimHeight + trimTop);
    NewMove := False;
    Image1.Canvas.Pen.Mode := pmCopy;                                     // ペンモード通常に
  end;
end;

//------------------------------------------------
// ファイルのオープン
//------------------------------------------------
procedure TForm1.FileOpenBtnClick(Sender: TObject);
var
  WIC         : TWICImage;
  XF, YF      : Boolean;
  nx2         : 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);                         // 画像の読み込み
        SHeight := WIC.Height;                                // 画像高さ取得
        SWidth  := WIC.Width;                                 // 画像幅

        // OutputBitmapに原画像一時保存
        OutputBitmap.Width  := SWidth;
        OutputBitmap.Height := SHeight;
        OutputBitmap.Canvas.Draw(0, 0, WIC);     // ビットマップに描画フォーマット変換書き込み

        //---------------------------------------------------------------
        // 原画像サイズより大きい 画像のサイズ 2のn乗に設定
        // InputDBitmap に原画像左上基準にコピー
        XF := False;
        YF := False;
        nx2 := 1;
        X_EXP := 0;
        Y_EXP := 0;
        // データ幅の2のべき乗を計算
        // 元画像と等しいか大きい2^nの画像サイズ取得
        repeat
          nx2 := nx2 * 2;
          if not XF then inc(X_EXP);
          if (nx2 >= SWidth) and (XF = False) then begin
            XF := True;
            GWidth := nx2;
          end;
          if not YF then inc(Y_EXP);
          if (nx2 >= SHeight) and (YF = False) then begin
            YF := True;
            GHeight := nx2;
          end;
        until (nx2 >= SWidth) and (nx2 >= SHeight);

        // 2^nの画像サイズに設定
        InputDBitmap.Width := GWidth;
        InputDBitmap.Height := GHeight;
        // 全面黒色設定  Canvas 画像消去
        VRect := Rect(0, 0, GWidth, GHeight);
        InputDBitmap.Canvas.Brush.Style := bsSolid;
        InputDBitmap.Canvas.Brush.Color := clblack;
        InputDBitmap.Canvas.FillRect(VRect);
        // 左上基準にWIC画像を書き込み
        InputDBitmap.Canvas.Draw(0, 0, WIC);
        //--------------------------------------------------------------
      finally
        WIC.Free;                                   // TWICImage 解放
      end;
    end
    else exit;
  trimming_imageout(OutputBitmap);                  // 出力枠合わせてに変倍出力

  MaskingBtn.Enabled := True;
//  sourceBtn.Enabled := True;
  radiogroup1.Enabled := True;
  TrimmingBtn.Enabled := True;
  TrimmingF := False;
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;
  radiogroup1.Items.Add('Line blur');
  radiogroup1.Items.Add('Deconvolution');

  radiogroup1.ItemIndex := 1;
  radiogroup1.Enabled := False;
  MaskingBtn.Enabled := False;
  FileSaveBtn.Enabled := False;
  sourceBtn.Enabled := False;
  RepaintBtn.Enabled := False;
  TrimmingBtn.Enabled := False;
  Sctext := '1';
end;

// 終了処理
procedure TForm1.FormDestroy(Sender: TObject);
begin
  InputDBitmap.Free;
  OutputBitmap.Free;
end;

end.

   download FFT_Deconvolution.zip

画像処理一覧へ戻る

      最初に戻る