Laplacian of Gaussian

  LoG (Laplacian of Gaussian) filterは、 ガウシアンを微分して得られる計算式により、フィルターとしたもので、平滑化と、エッジ検出を同時に行うものです。

平滑化(Gaussian)フィルターの合計値は1になりますが、LoGの合計はゼロになります。

 フィルターイメージ図


値が0~255の画像にフィルターを施すと、値が小さすぎて画像として表示が出来ないので、係数を乗じて画像として見やすい値にします。

 左図は、計算されたLoGフィルター値で処理後53倍した後、127を加算して見やすくしたものです。
σ値は2として計算しています。

 細線化処理として、水平垂直斜め方向の値が一定の値以上かをチックしていすます。
 微分した値なので、画像濃度の変化が少ない部分は0に近い値になり、黒から白になる部分はプラスの大な値となり、白から黒になる部分は、マイナスの大きな値になりますので、この変化する部分を捉えれば、画像のエッジ部分を捉えることが出来ます。
  左図は、指定位置に対して、上下左右、斜め方向位置の値の大きさを調べ指定した値を超えていたら白としています。
白の線の幅は、2ドットになります。
チェックの範囲は3x3になりますが、指定位置中央の値は使用されません。
白の線の幅を1ドットにする場合はチェックの範囲を2x2にして隣同士、斜め方向の値をチェックします。 



 上図は、フィルターの差を比べたものですが、やはりCanyの方がはるかに良い画像となっています。

プログラム

unit Main;

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,
  Vcl.Grids, Math;

type
  TForm1 = class(TForm)
    OpenBtn: TButton;
    SigumaGrid: TStringGrid;
    REdit: TLabeledEdit;
    SEdit: TLabeledEdit;
    FilterBtn: TButton;
    OpenPictureDialog1: TOpenPictureDialog;
    Label1: TLabel;
    KEdit: TLabeledEdit;
    Label2: TLabel;
    ReDrawBtn: TButton;
    crossBtn: TButton;
    OffEdit: TLabeledEdit;
    CheckBox1: TCheckBox;
    ScrollBox1: TScrollBox;
    Image1: TImage;
    procedure FilterBtnClick(Sender: TObject);
    procedure OpenBtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ReDrawBtnClick(Sender: TObject);
    procedure crossBtnClick(Sender: TObject);
    procedure SEditChange(Sender: TObject);
  private
    { Private 宣言 }
    procedure LogMask;
    procedure Masking;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
const
  FileOpenFilter = 'Image File|*.jpg;*.jpeg;*.tif;*.tiff;*.png;*.gif;*.bmp;*.wdp' +
                   '|*.Jpg|*.jpg' +
                   '|*.Tif|*.tif' +
                   '|*.Png|*.png' +
                   '|*.Gif|*.gif' +
                   '|*.Bmp|*.bmp' +
                   '|*.Wdp|*.wdp';

  RK    = 0.298912;                           // グレースケール輝度係数 赤
  GK    = 0.586611;                           // グレースケール輝度係数 緑
  BK    = 0.114478;                           // グレースケール輝度係数 青

var
  SXY : array of array of double;   // フィルター配列
  MatSize : integer;                // フィルターサイズ
  MDIV2   : integer;
  Total   : double;

  Inputbitmap       : Tbitmap;                    // ファイル入力用ビットマップ
  OutPutbitmap      : Tbitmap;                    // ファイル入力用ビットマップ

  GrayMat           : array of array of double;
  MaskedMat         : array of array of Byte;
  FMaskedMat        : array of array of double;

  GHeight, GWidth : Integer;                      // 入力画像サイズ
  RepF            : Boolean;
  K             : double;

//-------------------------------
// LoGフィルターの実行ルーチン
//-------------------------------
procedure TForm1.Masking;
var
  X, Y, WX, WY    : integer;
  Xm, Ym, Xd, Yd  : integer;
  Mx, My          : integer;
  Siguma          : double;
  Isiguma         : integer;
  PD              : PByteArray;
  WD              : integer;
begin
  setlength(MaskedMat, GWidth, GHeight);
  setlength(FMaskedMat, GWidth, GHeight);
  OutPutbitmap.SetSize(GWidth, GHeight);
  WX := GWidth  - 1;
  WY := GHeight - 1;
  // LoG フィルターの実施
  for X := 0 to WX do
    for Y := 0 to WY do begin
      siguma := 0;
      for Xm := - MDIV2 to MDIV2 do begin
        Mx := Xm + MDIV2;
        Xd := X + Xm;
        if Xd < 0 then Xd := -Xd;
        if Xd > WX then Xd := WX + WX - Xd;     // Xd := WX - (Xd - WX);
        for Ym := - MDIV2 to MDIV2 do begin
          My := Ym + MDIV2;
          Yd := Y + Ym;
          if Yd < 0 then Yd := -Yd;
          if Yd > WY then Yd := WY + WY - Yd;   // Yd := WY - (Yd - WY);
          siguma := siguma + GrayMat[Xd, Yd] * SXY[Mx, My];
        end;
      end;
      // 浮動小数点値
      FMaskedMat[X, Y] := siguma;               // 交差点判定用浮動小数点のまゝ使用します
      // 整数化
      Isiguma := round(siguma + 127);           // ゼロ点の値を中間点127に設定
      if Isiguma < 0 then Isiguma := 0;
      if Isiguma > 255 then Isiguma := 255;
      // Byteデーター
      MaskedMat[X, Y] := Isiguma;
    end;
  // グレースケール画像の生成
  for Y := 0 to WY do begin
    PD := OutPutbitmap.ScanLine[Y];
    for X := 0 to WX do begin
      WD := X * 3;
      PD[WD    ] := MaskedMat[X, Y];
      PD[WD + 1] := MaskedMat[X, Y];
      PD[WD + 2] := MaskedMat[X, Y];
    end;
  end;
  Image1.Canvas.Draw(0, 0, Outputbitmap);          // 出力枠に出力
end;

//----------------------------------------------------------------
// 交差点の検出
// 中間点 X     3x3 マトリックス    2x2 マトリックス
//               a b c               ab
//               d X f               cd
//               g h i
// 判定 3x3  d>X>f  d<X<f b>X>h  b<X<h  a>X>i  a<X<i  c>X>g  c<X<g
// 判定 2x3  a>X>b  a<X<b a>X>c  a<X<c  a>X>d  a<X<d  b>X>c  b<X<c
//----------------------------------------------------------------
procedure TForm1.crossBtnClick(Sender: TObject);
var
  crossmat  : array[0..2] of array[0..2] of double;   // 交差判定用配列
  X, Y      : integer;
  ThP, Thm  : double;                                 // +- Threshold
  Xm, Ym    : integer;
  PD        : PByteArray;
  WD        : integer;
  Offset    : double;                                 // Threshold用入力値

 function crosscheck(a, b: double): boolean;
 begin
  result := false;
  if (a > ThP) and (b < Thm) then result := true;
  if (a < Thm) and (b > ThP) then result := true;
 end;

begin
  val(OffEdit.Text, Offset, WD);
  if WD <> 0 then begin
    application.MessageBox('Threshold 入力に間違いがあります。', 'Threshold', 0);
    exit;
  end;
  // 判定値の上下設定
  ThP := (Total * K + Offset);
  ThM := (Total * K - Offset);
  if CheckBox1.Checked = True then begin  // 2x2 交差点検出
    // 交差点判別
    for X := 1 to GWidth - 1 do
      for Y := 1 to GHeight - 1 do begin
        MaskedMat[X, Y] := 0;
        if crosscheck(FMaskedMat[X-1, Y-1], FMaskedMat[X  , Y-1]) then MaskedMat[X, Y] := 255; // 左右
        if crosscheck(FMaskedMat[X-1, Y-1], FMaskedMat[X-1, Y  ]) then MaskedMat[X, Y] := 255; // 上下
        if crosscheck(FMaskedMat[X-1, Y-1], FMaskedMat[X  , Y  ]) then MaskedMat[X, Y] := 255; // 左上右下
        if crosscheck(FMaskedMat[X  , Y-1], FMaskedMat[X-1, Y  ]) then MaskedMat[X, Y] := 255; // 右上左下
     end;  
  end
  else begin                              // 3x3 交差点検出
    // 交差点判別
    for X := 1 to GWidth - 2 do
      for Y := 1 to GHeight - 2 do begin
        MaskedMat[X, Y] := 0;
        if crosscheck(FMaskedMat[X-1, Y  ], FMaskedMat[X+1, Y  ]) then MaskedMat[X, Y] := 255; // 左右
        if crosscheck(FMaskedMat[X  , Y-1], FMaskedMat[X  , Y+1]) then MaskedMat[X, Y] := 255; // 上下
        if crosscheck(FMaskedMat[X-1, Y-1], FMaskedMat[X+1, Y+1]) then MaskedMat[X, Y] := 255; // 左上右下
        if crosscheck(FMaskedMat[X+1, Y-1], FMaskedMat[X-1, Y+1]) then MaskedMat[X, Y] := 255; // 右上左下
      end;
  end;
  // モノクロ2値画像生成
  for Y := 0 to GHeight - 1 do begin
    PD := OutPutbitmap.ScanLine[Y];
    for X := 0 to Gwidth - 1 do begin
      WD := X * 3;
      PD[WD    ] := MaskedMat[X, Y];
      PD[WD + 1] := MaskedMat[X, Y];
      PD[WD + 2] := MaskedMat[X, Y];
    end;
  end;
  Image1.Canvas.Draw(0, 0 , Outputbitmap);          // 出力枠に出力
end;

//---------------------
// Logフィルターの実施
//---------------------
procedure TForm1.FilterBtnClick(Sender: TObject);
begin
  LogMask;                    // LoGフィルター生成
  Masking;                    // LoGフィルターの実施
  ReDrawBtn.Enabled := True;
  RepF := True;
  crossbtn.Enabled := True;
end;

//------------------------
// LoGフィルターの作成
//------------------------
procedure TForm1.LogMask;
var
  sigma, LogXY  : double;
  RS            : integer;
  Ch            : integer;
  X, Y          : integer;
begin
  val(REdit.Text, RS, Ch);
  if Ch <> 0 then begin
    application.MessageBox('R に間違いがあります。', '半径R', 0);
    exit;
  end;
  val(SEdit.Text, sigma, Ch);
  if Ch <> 0 then begin
    application.MessageBox('σ に間違いがあります。', 'シグマ σ', 0);
    exit;
  end;
  val(KEdit.Text, K, Ch);
  if Ch <> 0 then begin
    application.MessageBox('ゲイン G に間違いがあります。', 'ゲイン G', 0);
    exit;
  end;
  //フィルター配列の確保
  MatSize := RS * 2 + 1;
  SetLength(SXY, MatSize, MatSize);
  // ストリンググリッドの設定
  SigumaGrid.ColCount := MatSize div 2 + 2;
  SigumaGrid.RowCount := MatSize div 2 + 2;
  SigumaGrid.ClientWidth  := (SigumaGrid.DefaultColWidth  + 1) * (MatSize div 2 + 2);
  SigumaGrid.ClientHeight := (SigumaGrid.DefaultRowHeight + 1) * (MatSize div 2 + 2);
  // 固定セルにナンバーリング
  MDIV2 := MatSize div 2;
  for Y := MDIV2 downto 0 do SigumaGrid.Cells[0 , MDIV2 - Y + 1] := intTostr(Y);
  for X := MDIV2 downto 0 do SigumaGrid.Cells[X + 1,  0] := intTostr(X);
  // フィルター値計算と表示
  Total := 0;
  for Y := - MDIV2 to MDIV2 do
    for X := - MDIV2 to MDIV2 do begin
      LogXY := (X * X + Y * Y - 2 * sigma * sigma) / 2 / pi / power(sigma, 6) * exp(- (X * X + Y * Y) / 2 / sigma / sigma);
      LogXY := LogXY * K;
      SXY[X + MDIV2, Y + MDIV2] := LogXY;
      Total := Total + LogXY;
      // グリッドに表示
      if (X <= 0) and (Y <= 0) then // 第一象限のみ表示
        SigumaGrid.Cells[1 - X , Y + MDIV2 + 1] := FloatTostrF(LogXY, ffFixed, 10, 6);
    end;
  Label1.Caption := 'Total= ' + FloatTostr(Total);
end;

//------------------------------------------
// ファイルのオープンとGray配列作成
//------------------------------------------
procedure TForm1.OpenBtnClick(Sender: TObject);
var
  XX, YY            : Integer;                            // for loop  GG ポインター計算用
  WIC               : TWICImage;
  PD                : PByteArray;
  WD                : Integer;
begin
  OpenPictureDialog1.Filter := FileOpenFilter;
  if OpenPictureDialog1.Execute then                      // ファイルが指定されたら
    begin
      WIC := TWICImage.Create;
      try
        WIC.LoadFromFile(OpenPictureDialog1.FileName);
        GHeight := WIC.Height;
        GWidth  := WIC.Width;
        Inputbitmap.SetSize(GWidth, GHeight);
        Inputbitmap.Canvas.Draw(0, 0, WIC);               // フォーマット変換 24ビットフォーマットにします
      finally
        WIC.Free;
      end;
    end
    else exit;                                            // キャンセルだったら終了
  // Gray配列に入力画像セット
  SetLength(GrayMat, GWidth, GHeight);
  for YY := 0 to GHeight - 1 do begin
    PD := Inputbitmap.ScanLine[YY];
    for XX := 0 to GWidth - 1 do begin
      WD := XX * 3;
      GrayMat[XX, YY] := PD[WD    ] * BK + PD[WD + 1] * GK + PD[WD + 2] * RK;
    end;
  end;
  // 入力画像表示
  Image1.Height := GHeight;
  Image1.Width  := GWidth;
  Image1.Picture.Bitmap.SetSize(GWidth, GHeight);
  Image1.Canvas.Draw(0, 0, Inputbitmap);          // 出力枠に出力
  FilterBtn.Enabled := true;
  ReDrawBtn.Enabled := False;
  crossBtn.Enabled := False;
end;

//----------------------------------
// 表示画像の切り替え
//----------------------------------
procedure TForm1.ReDrawBtnClick(Sender: TObject);
begin
  if RepF then begin
    Image1.Canvas.Draw(0, 0, Inputbitmap);        // 出力枠に出力
    RepF := False;
  end
  else begin
    Image1.Canvas.Draw(0, 0, Outputbitmap);       // 出力枠に出力
    RepF := True;
  end;
end;

//-----------------------------
// σ値により半径とゲイン調整
//-----------------------------
procedure TForm1.SEditChange(Sender: TObject);
var
  Inr : double;
  ch  : integer;
begin
  val(SEdit.Text, Inr, Ch);
  if ch <> 0 then exit;
  REdit.Text := floatTostr(round((Inr + 0.1) * 4));
  KEdit.Text := floatTostr(round(power(Inr + 0.7, 4)));
end;

//----------------------------
// 初期設定
//----------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
  Inputbitmap :=  Tbitmap.Create;
  Inputbitmap.PixelFormat := pf24bit;                 // 24ビットフォーマット
  OutPutbitmap := Tbitmap.Create;
  OutPutbitmap.PixelFormat := pf24bit;                // 24ビットフォーマット
  FilterBtn.Enabled := False;
  ReDrawBtn.Enabled := False;
  crossbtn.Enabled := False;
end;

//----------------------
// 終了処理
//----------------------
procedure TForm1.FormDestroy(Sender: TObject);
begin
  OutPutbitmap.Free;
  Inputbitmap.Free;
end;

end.

    download Log_filter.zip

画像処理一覧へ戻る

      最初に戻る