トップへ(mam-mam.net/)

Image Filters in Delphi (VCL) — Unsharp Mask, Gaussian Blur, Smoothing, and Salt‑and‑Pepper Noise Removal

Japanese

Image Filters in Delphi (VCL) — Unsharp Mask, Gaussian Blur, Smoothing, and Salt‑and‑Pepper Noise Removal

Preparing the File for Image Filters

Create a new unit file named "UMamBasicFilter.pas" by copying the source code provided at the bottom of this page, and place the file inside your project folder.

Writing the Source Code

Create a new VCL Application project and place two TImage components and five TButton components on Form1.
Load the image you want to apply filters to into the Picture property of Image1.
For this example, we intentionally use an image that contains salt‑and‑pepper noise (spike noise).

Double‑click Button1, Button2, Button3, Button4, and Button5, and write the following source code.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.Imaging.jpeg, VCL.Imaging.pngimage;

type
  TForm1 = class(TForm)
    Image1: TImage;
    Image2: TImage;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses UMamBasicFilter;

procedure TForm1.Button1Click(Sender: TObject);
var bmp:TBitmap;
begin
  // Convert Image1 to grayscale and display it in Image2
  bmp := TBitmap.Create;
  try
    bmp.Assign(Image1.Picture.Graphic);
    MamGrayScale(bmp, Image2.Picture.Bitmap);
  finally
    bmp.Free;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var bmp:TBitmap;
begin
  // Apply Unsharp Mask (sharpening) to Image1 and display it in Image2
  bmp := TBitmap.Create;
  try
    bmp.Assign(Image1.Picture.Graphic);
    MamUnSharp(bmp, Image2.Picture.Bitmap, 2);
  finally
    bmp.Free;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var bmp:TBitmap;
begin
  // Apply Gaussian blur (noise‑reduction blur) to Image1 and display it in Image2
  bmp := TBitmap.Create;
  try
    bmp.Assign(Image1.Picture.Graphic);
    MamGaussian(bmp, Image2.Picture.Bitmap, TMamGaussian.Gaussian5x5);
  finally
    bmp.Free;
  end;
end;

procedure TForm1.Button4Click(Sender: TObject);
var bmp:TBitmap;
begin
  // Apply smoothing/average blur to Image1 and display it in Image2
  bmp := TBitmap.Create;
  try
    bmp.Assign(Image1.Picture.Graphic);
    MamBlur(bmp, Image2.Picture.Bitmap, 2);
  finally
    bmp.Free;
  end;
end;

procedure TForm1.Button5Click(Sender: TObject);
var bmp:TBitmap;
begin
  // Apply median filter (effective for removing salt‑and‑pepper/spike noise)
  // to Image1 and display it in Image2
  bmp := TBitmap.Create;
  try
    bmp.Assign(Image1.Picture.Graphic);
    MamMedian(bmp, Image2.Picture.Bitmap, 1);
  finally
    bmp.Free;
  end;
end;

end.

Running the Application

Click the Run button to execute the application (Debug Run is also fine).

When you click Button1, the image in Image1 is converted to a grayscale image and displayed in Image2.


When you click Button2, an Unsharp Mask (sharpening) filter is applied to the image in Image1 and the result is displayed in Image2.


When you click Button3, a Gaussian blur (noise‑reduction blur) is applied to the image in Image1 and displayed in Image2.


When you click Button4, a smoothing/average blur is applied to the image in Image1 and displayed in Image2.


When you click Button5, a median filter (effective for removing salt‑and‑pepper/spike noise) is applied to the image in Image1 and displayed in Image2.

Source Code for the "UMamBasicFilter.pas" File

unit UMamBasicFilter;

interface

uses System.Types,System.UITypes, System.Math,
     System.Generics.Collections, System.Generics.Defaults,
     VCL.Graphics;

Type
  TMamBilateral=(Bilateral3x3, Bilateral5x5, Bilateral7x7);
  TMamGaussian=(Gaussian3x3, Gaussian5x5, Gaussian7x7);
  TMedian=record
    v:Byte;
    x:Integer;
    y:Integer;
  end;

// Create a grayscale version of the src image and store it in dest
procedure MamGrayScale(src,dest:VCL.Graphics.TBitmap);

// Apply an Unsharp Mask (sharpening) to src and store the result in dest
// (Strength: 0.1–10.0)
procedure MamUnSharp(src,dest:VCL.Graphics.TBitmap;Strength:Single=1);

// Apply a Gaussian blur (noise‑reduction blur) to src and store the result in dest
procedure MamGaussian(src,dest:VCL.Graphics.TBitmap;
  sm:TMamGaussian=TMamGaussian.Gaussian5x5);

// Apply a smoothing/average blur to src and store the result in dest
// (Strength: 1–20)
procedure MamBlur(src,dest:VCL.Graphics.TBitmap;Strength:Integer=2);

// Apply a median filter (effective for removing salt‑and‑pepper/spike noise)
// (Strength: 1–4)
procedure MamMedian(src,dest:VCL.Graphics.TBitmap;Strength:Integer=1);



implementation
Type
  TSRGB=record B,G,R:Single; end;

  TRGB=record B,G,R:Byte; end;
  TRGBArr=array[0..65535] of TRGB;
  PRGBArr=^TRGBArr;
  TRGBArrArr=array[0..65535] of PRGBArr;

  TMn=array of array of single;
  TGauss=array of array of single;

const
  //Gaussian filter coefficient matrices
  gauss3:array[0..2]of array[0..2]of single=
    (
      (1,2,1),(2,4,2),(1,2,1)
    );
  gauss5:array[0..4]of array[0..4]of single=
    (
      ( 1, 4, 6, 4, 1), ( 4,16,24,16, 4), ( 6,24,36,24, 6),
      ( 4,16,24,16, 4), ( 1, 4, 6, 4, 1)
    );
  gauss7:array[0..6]of array[0..6] of single=
    (
      ( 1,  6, 15, 20, 15,  6, 1), ( 6, 36, 90,120, 90, 36, 6),
      (15, 90,225,300,225, 90,15), (20,120,300,400,300,120,20),
      (15, 90,225,300,225, 90,15), ( 6, 36, 90,120, 90, 36, 6),
      ( 1,  6, 15, 20, 15,  6, 1)
    );

// Comb sort
procedure CombSort(var a:Array of TMedian);
  // Swap the values of two TMedian records
  procedure SwapInt(var v1,v2:TMedian);
  var sw:TMedian;
  begin
    sw:=v1;
    v1:=v2;
    v2:=sw;
  end;
var between:Integer; // gap between compared elements
    i,l,h:integer;
begin
  l:=Low(a);
  h:=High(a);
  // Initial comparison gap
  between:=System.Math.Floor((h-l+1)/1.3);
  while between>0 do // Stop when the gap becomes 0
  begin
    i:=l;
    while h>=(i+between) do
    begin
      if a[i].v>a[i+between].v then
        SwapInt(a[i],a[i+between]);
      inc(i);
    end;
    // Reduce the comparison gap (divide by 1.3 and floor)
    between:=System.Math.Floor(between/1.3);
  end;
end;


//Create a grayscale version of the src image and store it in dest
procedure MamGrayScale(src,dest:VCL.Graphics.TBitmap);
var v:byte;
    x,y:integer;
    SrcBmp,DestBmp:TBitmap;//src,destの一時画像
    fRect:TRect;
    SrcRGB,DestRGB:PRGBArr;
begin
  if not assigned(src) then exit;
  if not assigned(dest) then dest:=VCL.Graphics.TBitmap.Create;
  dest.SetSize(src.Width,src.Height);
  fRect.Left:=0;
  fRect.Top:=0;
  fRect.Width:=src.Width;
  fRect.Height:=src.Height;

  SrcBmp:=VCL.Graphics.TBitmap.Create;
  DestBmp:=VCL.Graphics.TBitmap.Create;
  try
    SrcBmp.PixelFormat:=pf24bit;
    SrcBmp.SetSize(fRect.Width,fRect.Height);
    SrcBmp.Canvas.Draw(0,0,src);
    DestBmp.PixelFormat:=pf24bit;
    DestBmp.SetSize(fRect.Width,fRect.Height);
    for y := 0 to SrcBmp.Height-1 do
    begin
      SrcRGB:=SrcBmp.Scanline[y];
      DestRGB:=DestBmp.Scanline[y];
      for x := 0 to fRect.Width-1 do
      begin
        v:=Round(
          0.299*SrcRGB[x].R+ 0.587*SrcRGB[x].G+ 0.114*SrcRGB[x].B
        );
        DestRGB[x].R:=v;
        DestRGB[x].G:=v;
        DestRGB[x].B:=v;
      end;
    end;
    dest.Canvas.Draw(0,0,DestBmp);
  finally
    SrcBmp.Free;
    DestBmp.Free;
  end;
end;

// Apply an Unsharp Mask (sharpening) to src and store the result in dest
// (Strength: 0.1–10.0)
procedure MamUnSharp(src,dest:VCL.Graphics.TBitmap;Strength:Single=1);
var m:array of array of Single;
    mn,center:integer;
    x,y,i,j,xx,yy:Integer;
    fRect:TRect;
    SrcBmp,DestBmp:VCL.Graphics.TBitmap; // temporary bitmaps for src and dest
    SrcRGB,DestRGB:TRGBArrArr;
    fSRGB:TSRGB;
begin
  if not assigned(src) then exit;
  if not assigned(dest) then dest:=VCL.Graphics.TBitmap.Create;
  dest.SetSize(src.Width,src.Height);

  if Strength<0.1 then Strength:=0.1;
  if Strength>10 then Strength:=10;

  fRect.Left:=0;
  fRect.Top:=0;
  fRect.Width:=src.Width;
  fRect.Height:=src.Height;

  mn:=3;
  center:=(mn-1) div 2;
  // Create the Unsharp Mask kernel
  SetLength(m,mn);
  for y := Low(m) to High(m) do
  begin
    SetLength(m[y],mn);
    for x := Low(m[y]) to High(m[y]) do
    begin
      if (y=center)and(x=center) then
        m[y][x]:=1+8*Strength/9
      else
        m[y][x]:=-Strength/9;
    end;
  end;

  SrcBmp :=VCL.Graphics.TBitmap.Create;
  DestBmp:=VCL.Graphics.TBitmap.Create;
  try
    SrcBmp.PixelFormat:=pf24bit;
    SrcBmp.SetSize(fRect.Width,fRect.Height);
    SrcBmp.Canvas.Draw(0,0,src);
    DestBmp.PixelFormat:=pf24bit;
    DestBmp.SetSize(fRect.Width,fRect.Height);

    //Retrieve all scanlines at once
    for j := 0 to fRect.Height-1 do
    begin
      SrcRGB[j]:=SrcBmp.ScanLine[j];
      DestRGB[j]:=DestBmp.ScanLine[j];
    end;

    for j := 0 to fRect.Height-1 do
    begin
      for i := 0 to fRect.Width-1 do
      begin
        fSRGB.R:=0;
        fSRGB.G:=0;
        fSRGB.B:=0;
        for y := 0 to mn-1 do
        begin
          for x := 0 to mn-1 do
          begin
            yy:=j+(y-center);
            if (yy<0)or(yy>=fRect.Height) then
              yy:=j-(y-center);
            xx:=i+(x-center);
            if (xx<0)or(xx>=fRect.Width) then
              xx:=i-(x-center);
            fSRGB.R:=fSRGB.R+m[y][x]*SrcRGB[yy][xx].R;
            fSRGB.G:=fSRGB.G+m[y][x]*SrcRGB[yy][xx].G;
            fSRGB.B:=fSRGB.B+m[y][x]*SrcRGB[yy][xx].B;
          end;
        end;
        if fSRGB.R>255 then fSRGB.R:=255;
        if fSRGB.G>255 then fSRGB.G:=255;
        if fSRGB.B>255 then fSRGB.B:=255;
        if fSRGB.R<0 then fSRGB.R:=0;
        if fSRGB.G<0 then fSRGB.G:=0;
        if fSRGB.B<0 then fSRGB.B:=0;

        DestRGB[j][i].R:=Round(fSRGB.R);
        DestRGB[j][i].G:=Round(fSRGB.G);
        DestRGB[j][i].B:=Round(fSRGB.B);
      end;
    end;

    dest.Canvas.Draw(0,0,DestBmp);
  finally
    SrcBmp.Free;
    DestBmp.Free;
  end;
end;


// Apply a Gaussian blur (noise‑reduction blur) to src and store the result in dest
procedure MamGaussian(src,dest:VCL.Graphics.TBitmap;
  sm:TMamGaussian=TMamGaussian.Gaussian5x5);
var m:array of array of Single;
    mn,center:Integer;
    msum:Single;
    x,y,i,j,xx,yy:Integer;
    fRect:TRect;
    SrcBmp,DestBmp:VCL.Graphics.TBitmap; // temporary bitmaps for src and dest
    SrcRGB,DestRGB:TRGBArrArr;
    fSRGB:TSRGB;
begin
  if not assigned(src) then exit;
  if not assigned(dest) then dest:=VCL.Graphics.TBitmap.Create;
  dest.SetSize(src.Width,src.Height);

  // Create the Gaussian kernel matrix
  mn:=3;
  msum:=0;
  if sm=TMamGaussian.Gaussian5x5 then mn:=5;
  if sm=TMamGaussian.Gaussian7x7 then mn:=7;
  SetLength(m,mn);
  for j := Low(m) to High(m) do
  begin
    SetLength(m[j],mn);
    for i := Low(m[j]) to High(m[j]) do
    begin
      if mn=3 then
      begin
        m[j][i]:=gauss3[j][i];
        msum:=msum+gauss3[j][i];
      end
      else if mn=5 then
      begin
        m[j][i]:=gauss5[j][i];
        msum:=msum+gauss5[j][i];
      end
      else
      begin
        m[j][i]:=gauss7[j][i];
        msum:=msum+gauss7[j][i];
      end;
    end;
  end;
  center:=(mn-1) div 2;
  fRect.Left:=0;
  fRect.Top:=0;
  fRect.Width:=src.Width;
  fRect.Height:=src.Height;

  SrcBmp :=VCL.Graphics.TBitmap.Create;
  DestBmp:=VCL.Graphics.TBitmap.Create;
  try
    SrcBmp.PixelFormat:=pf24bit;
    SrcBmp.SetSize(fRect.Width,fRect.Height);
    SrcBmp.Canvas.Draw(0,0,src);
    DestBmp.PixelFormat:=pf24bit;
    DestBmp.SetSize(fRect.Width,fRect.Height);

    // Retrieve all scanlines at once
    for j := 0 to fRect.Height-1 do
    begin
      SrcRGB[j]:=SrcBmp.ScanLine[j];
      DestRGB[j]:=DestBmp.ScanLine[j];
    end;

    for j := 0 to fRect.Height-1 do
    begin
      for i := 0 to fRect.Width-1 do
      begin
        fSRGB.R:=0;
        fSRGB.G:=0;
        fSRGB.B:=0;
        for y := 0 to mn-1 do
        begin
          for x := 0 to mn-1 do
          begin
            yy:=j+(y-center);
            if (yy<0)or(yy>=fRect.Height) then
              yy:=j-(y-center);
            xx:=i+(x-center);
            if (xx<0)or(xx>=fRect.Width) then
              xx:=i-(x-center);
            fSRGB.R:=fSRGB.R+m[y,x]*SrcRGB[yy][xx].R/msum;
            fSRGB.G:=fSRGB.G+m[y,x]*SrcRGB[yy][xx].G/msum;
            fSRGB.B:=fSRGB.B+m[y,x]*SrcRGB[yy][xx].B/msum;
          end;
        end;
        if fSRGB.R>255 then fSRGB.R:=255;
        if fSRGB.G>255 then fSRGB.G:=255;
        if fSRGB.B>255 then fSRGB.B:=255;
        if fSRGB.R<0 then fSRGB.R:=0;
        if fSRGB.G<0 then fSRGB.G:=0;
        if fSRGB.B<0 then fSRGB.B:=0;

        DestRGB[j][i].R:=Round(fSRGB.R);
        DestRGB[j][i].G:=Round(fSRGB.G);
        DestRGB[j][i].B:=Round(fSRGB.B);
      end;
    end;

    dest.Canvas.Draw(0,0,DestBmp);
  finally
    SrcBmp.Free;
    DestBmp.Free;
  end;
end;


// Apply a blur filter (smoothing / average blur) to src and store the result in dest
// (Strength: 1–20)
procedure MamBlur(src,dest:VCL.Graphics.TBitmap;Strength:Integer=2);
var mn,center:Integer;
    x,y,i,j,xx,yy:Integer;
    fRect:TRect;
    SrcBmp,DestBmp:VCL.Graphics.TBitmap; // temporary bitmaps for src and dest
    SrcRGB,DestRGB:TRGBArrArr;
    fSRGB:TSRGB;
begin
  if not assigned(src) then exit;
  if not assigned(dest) then dest:=VCL.Graphics.TBitmap.Create;
  dest.SetSize(src.Width,src.Height);

  if Strength<1 then Strength:=1;
  if Strength>10 then Strength:=10;

  mn:=Strength*2+1;
  center:=(mn-1) div 2;

  fRect.Left:=0;
  fRect.Top:=0;
  fRect.Width:=src.Width;
  fRect.Height:=src.Height;

  SrcBmp :=VCL.Graphics.TBitmap.Create;
  DestBmp:=VCL.Graphics.TBitmap.Create;
  try
    SrcBmp.PixelFormat:=pf24bit;
    SrcBmp.SetSize(fRect.Width,fRect.Height);
    SrcBmp.Canvas.Draw(0,0,src);
    DestBmp.PixelFormat:=pf24bit;
    DestBmp.SetSize(fRect.Width,fRect.Height);

    // Retrieve all scanlines at once
    for j := 0 to fRect.Height-1 do
    begin
      SrcRGB[j]:=SrcBmp.ScanLine[j];
      DestRGB[j]:=DestBmp.ScanLine[j];
    end;

    for j := 0 to fRect.Height-1 do
    begin
      for i := 0 to fRect.Width-1 do
      begin
        fSRGB.R:=0;
        fSRGB.G:=0;
        fSRGB.B:=0;
        for y := 0 to mn-1 do
        begin
          for x := 0 to mn-1 do
          begin
            yy:=j+(y-center);
            if (yy<0)or(yy>=fRect.Height) then
              yy:=j-(y-center);
            xx:=i+(x-center);
            if (xx<0)or(xx>=fRect.Width) then
              xx:=i-(x-center);
            fSRGB.R:=fSRGB.R+SrcRGB[yy][xx].R/mn/mn;
            fSRGB.G:=fSRGB.G+SrcRGB[yy][xx].G/mn/mn;
            fSRGB.B:=fSRGB.B+SrcRGB[yy][xx].B/mn/mn;
          end;
        end;
        if fSRGB.R>255 then fSRGB.R:=255;
        if fSRGB.G>255 then fSRGB.G:=255;
        if fSRGB.B>255 then fSRGB.B:=255;
        if fSRGB.R<0 then fSRGB.R:=0;
        if fSRGB.G<0 then fSRGB.G:=0;
        if fSRGB.B<0 then fSRGB.B:=0;

        DestRGB[j][i].R:=Round(fSRGB.R);
        DestRGB[j][i].G:=Round(fSRGB.G);
        DestRGB[j][i].B:=Round(fSRGB.B);
      end;
    end;
    dest.Canvas.Draw(0,0,DestBmp);
  finally
    SrcBmp.Free;
    DestBmp.Free;
  end;
end;


// Median filter (effective for removing salt‑and‑pepper / spike noise)
// (Strength: 1–4)
procedure MamMedian(src,dest:VCL.Graphics.TBitmap;Strength:Integer=1);
var mn:Integer;
    Num:Integer;
    x,y,i,j,xx,yy:Integer;
    fRect:TRect;
    SrcBmp,DestBmp,GrayBmp:VCL.Graphics.TBitmap; // temporary bitmaps for src and dest
    SrcRGB,DestRGB,GrayRGB:TRGBArrArr;
    fMedian:TArray;
begin
  if not assigned(src) then exit;
  if not assigned(dest) then dest:=VCL.Graphics.TBitmap.Create;
  dest.SetSize(src.Width,src.Height);

  if Strength<1 then Strength:=1;
  if Strength>4 then Strength:=4;

  mn:=Strength*2+1;

  fRect.Left:=0;
  fRect.Top:=0;
  fRect.Width:=src.Width;
  fRect.Height:=src.Height;

  // Default PixelFormat is TPixelFormat.BGRA
  SrcBmp :=VCL.Graphics.TBitmap.Create;
  DestBmp:=VCL.Graphics.TBitmap.Create;
  GrayBmp:=VCL.Graphics.TBitmap.Create;
  try
    SrcBmp.PixelFormat:=pf24bit;
    SrcBmp.SetSize(fRect.Width,fRect.Height);
    SrcBmp.Canvas.Draw(0,0,src);
    DestBmp.PixelFormat:=pf24bit;
    DestBmp.SetSize(fRect.Width,fRect.Height);
    GrayBmp.PixelFormat:=pf24bit;
    GrayBmp.SetSize(fRect.Width,fRect.Height);
    MamGrayScale(SrcBmp,GrayBmp);

    // Retrieve all scanlines at once
    for j := 0 to fRect.Height-1 do
    begin
      SrcRGB[j]:=SrcBmp.ScanLine[j];
      DestRGB[j]:=DestBmp.ScanLine[j];
      GrayRGB[j]:=GrayBmp.Scanline[j];
    end;

    for j := 0 to fRect.Height-1 do
    begin
      for i := 0 to fRect.Width-1 do
      begin
        Num:=0;
        for y := 0 to mn-1 do
        begin
          for x := 0 to mn-1 do
          begin
            yy:=j+y-(mn div 2);
            xx:=i+x-(mn div 2);
            if not ((xx<0) or
                    (xx>=fRect.Width) or
                    (yy<0) or
                    (yy>=fRect.Height)) then
            begin
              Num:=Num+1;
              SetLength(fMedian,Num);
              fMedian[Num-1].v:=GrayRGB[yy][xx].R;
              fMedian[Num-1].x:=xx;
              fMedian[Num-1].y:=yy;
            end;
          end;
        end;
        CombSort(fMedian);

        DestRGB[j][i].R:=SrcRGB[fMedian[Num Div 2].y][fMedian[Num Div 2].x].R;
        DestRGB[j][i].G:=SrcRGB[fMedian[Num Div 2].y][fMedian[Num Div 2].x].G;
        DestRGB[j][i].B:=SrcRGB[fMedian[Num Div 2].y][fMedian[Num Div 2].x].B;
      end;
    end;

    dest.Canvas.Draw(0,0,DestBmp);
  finally
    SrcBmp.Free;
    DestBmp.Free;
    GrayBmp.Free;
  end;
end;

end.