バイラテラルフィルタ(bilateral filter)を写真画像に適用させて美肌に加工する(FMX) ~Delphiでお手軽プログラミング

バイラテラルフィルタ(bilateral filter)を写真画像に適用させて美肌に加工する(FMX) ~Delphiでお手軽プログラミング

(参考)バイキュービック法(bicubic)で拡大縮小する(VCL)
(参考)バイキュービック法(bicubic)で拡大縮小する(FMX)
(参考)バイラテラルフィルタ(bilateral filter)で美肌に加工(VCL)
(参考)ガンマ(gamma)補正を画像に適用する(VCL)
(参考)ガンマ(gamma)補正を画像に適用する(FMX)
(参考)ソーベルフィルタ(Sobel filter)で境界(エッジ)検出(VCL)

バイラテラルフィルタを使用する為のファイルの準備

本ページの下部のソースコードをコピーして「UFMXMamBilateralFilter.pas」ファイルを作成し、 プロジェクトフォルダ内に入れる。

プロジェクトを作成してソースコードを記述する

プロジェクトを新規作成(FMXアプリケーション)し、フォーム(Form1)にTImageを2個、TButtonを1個配置する。
Image1のPictureプロパティから、バイラテラルフィルタを適用したい画像をロードしておく。
TButton1をダブルクリックして、以下ソースコードを記述する。
unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics,
  FMX.Dialogs, FMX.StdCtrls, FMX.Controls.Presentation, FMX.Objects;

type
  TForm1 = class(TForm)
    Image1: TImage;
    Image2: TImage;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { private 宣言 }
  public
    { public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses UFMXMamBilateralFilter;

procedure TForm1.Button1Click(Sender: TObject);
var src,dest:FMX.Graphics.TBitmap;
    i :integer;
const
    repeat_count:integer=5; //バイラテラルフィルタの適用回数
begin
  src:=TBitmap.Create;
  dest:=TBitmap.Create;
  try
    src.Assign(Image1.Bitmap);
    //バイラテラルフィルタをrepeat_count回適用する
    for i := 1 to repeat_count do
    begin
      //バイラテラルフィルタの適用
      MamBilateral(src, dest, TMamBilateral.Bilateral5x5, 20);
      src.Assign(dest);
    end;
    Image2.Bitmap.Assign(src);
  finally
    dest.Free;
    src.Free;
  end;
end;

end.

実行する

実行ボタンを押して実行します。(デバッグ実行でもOK)

Button1をクリックすると、Image1画像にバイラテラルフィルタ5x5を5回適用した画像をImage2に表示します。
左の梨の写真に、バイラテラルフィルタ(5×5)が5回適用され、皮のブツブツが少し滑らかになって、美肌になっている?
とても処理が重い(3x3フィルタは軽めだが、7x7フィルタは特に重い)ので高解像度画像に適用する場合は要注意。

「UFMXMamBilateralFilter.pas」ファイルのソースコード

unit UFMXMamBilateralFilter;

interface

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

Type
  TMamBilateral=(Bilateral3x3, Bilateral5x5, Bilateral7x7);

//src画像にバイラテラルフィルタをかけてdestに作成する
procedure MamBilateral(src,dest:FMX.Graphics.TBitmap;
  bai:TMamBilateral=Bilateral5x5; sigma:Integer=20);

//src画像のグレースケール画像をdestに作成する
procedure MamGrayScale(src,dest:FMX.Graphics.TBitmap);

const
  //ガウシアンフィルタ係数配列
  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)
    );

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

  TRGBA=record B,G,R,A:Byte; end;
  TRGBAArr=array[0..32767] of TRGBA;
  PRGBAArr=^TRGBAArr;
  TRGBAArrArr=array[0..32767] of PRGBAArr;

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

//輝度差の正規分布とガウシアンフィルターの係数を掛け合わせる
procedure MamLuminance(i,j,mn:integer;fRect:TRect;
  var GrayRGBA:TRGBAArrArr;var s:TMn;gauss:TGauss;sigma:integer=20);
var x,y,sig:integer;
    sum:single;
begin
  sig:=sigma*sigma;
  sum:=0;
  for x := 0 to mn*2 do
    for y := 0 to mn*2 do
    begin
      if ((i-mn+x)<0) or
         ((i-mn+x)>=fRect.width) or
         ((j-mn+y)<0) or
         ((j-mn+y)>=fRect.height) then
      begin
        s[x][y]:=0;
      end
      else
      begin
        s[x][y]:=
          (GrayRGBA[j][i].R) -
          (GrayRGBA[j-mn+y][i-mn+x].R);
        s[x][y]:=Power(2.71828182845905,
          (-s[x][y]*s[x][y]/sig))*gauss[x,y];
      end;
      sum:=sum+s[x,y];
    end;
  for x := 0 to mn*2 do
    for y := 0 to mn*2 do
    begin
      s[x][y]:=s[x][y]/sum;
    end;
end;

procedure MamBilateral(src,dest:FMX.Graphics.TBitmap;
  bai:TMamBilateral=Bilateral5x5;sigma:integer=20);
var i,j,x,y,xx,yy:integer;
    smn:TMn;
    gauss:TGauss;
    fRGBA:TSRGBA;
    GrayRGBA,SrcRGBA,DestRGBA:TRGBAArrArr;
    mn:Integer;
    fRect:TRect;
    SrcData,DestData,GrayData:TBitmapData;
    GrayBmp,SrcBmp,DestBmp:FMX.Graphics.TBitmap;//src,destの一時画像
begin
  if not assigned(src) then exit;
  if not assigned(dest) then dest:=FMX.Graphics.TBitmap.Create;

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

  mn:=1;//Bilateral3x3
  if bai=Bilateral5x5 then mn:=2;
  if bai=Bilateral7x7 then mn:=3;

  //ガウシアンフィルタとバイラテラルフィルタの行列の次元の設定
  setlength(gauss,mn*2+1);
  setlength(smn,mn*2+1);
  for i := 0 to mn*2 do
  begin
    setlength(gauss[i],mn*2+1);
    setlength(smn[i],mn*2+1);
  end;
  //ガウシアンフィルタの行列生成
  if bai=TMamBilateral.Bilateral3x3 then
    for i := 0 to length(gauss3)-1 do
      for j := 0 to length(gauss3[i])-1 do
        gauss[i][j]:=gauss3[i][j];
  if bai=TMamBilateral.Bilateral5x5 then
    for i := 0 to length(gauss5)-1 do
      for j := 0 to length(gauss5[i])-1 do
        gauss[i][j]:=gauss5[i][j];
  if bai=TMamBilateral.Bilateral7x7 then
    for i := 0 to length(gauss7)-1 do
      for j := 0 to length(gauss7[i])-1 do
        gauss[i][j]:=gauss7[i][j];

  //PixelFormatはTPixelFormat.BGRAがデフォルト
  SrcBmp :=FMX.Graphics.TBitmap.Create;
  DestBmp:=FMX.Graphics.TBitmap.Create;
  GrayBmp:=FMX.Graphics.TBitmap.Create;
  try
    SrcBmp.Width :=fRect.Width;
    SrcBmp.Height:=fRect.Height;
    SrcBmp.Canvas.BeginScene();
    SrcBmp.Canvas.DrawBitmap(src,fRect,fRect,1,true);
    SrcBmp.Canvas.EndScene;
    DestBmp.Width:=fRect.Width;
    DestBmp.Height:=fRect.Height;
    dest.Width:=fRect.Width;
    dest.Height:=fRect.Height;
    //グレースケール画像の生成
    MamGrayScale(SrcBmp,GrayBmp);

    //ビットマップのデータマップを取得
    GrayBmp.Map(TMapAccess.Read,GrayData);
    SrcBmp.Map(TMapAccess.Read,SrcData);
    DestBmp.Map(TMapAccess.Write,DestData);
    try
      //スキャンラインの一括取得
      for j := 0 to fRect.Height-1 do
      begin
        GrayRGBA[j]:=GrayData.GetScanline(j);
        SrcRGBA[j]:=SrcData.GetScanLine(j);
        DestRGBA[j]:=DestData.GetScanLine(j);
      end;

      for j := 0 to fRect.Height-1 do
      begin
        for i := 0 to fRect.Width-1 do
        begin
          //行列をsmnに取得する
          MamLuminance(i,j,mn,fRect,GrayRGBA,smn,gauss,sigma);
          fRGBA.R:=0;
          fRGBA.G:=0;
          fRGBA.B:=0;
          fRGBA.A:=255;
          for y := 0 to mn*2 do
          begin
            for x := 0 to mn*2 do
            begin
              xx:=i+x-mn;
              yy:=j+y-mn;
              if not ((xx<0) or
                      (xx>=fRect.Width) or
                      (yy<0) or
                      (yy>=fRect.Height)) then
              begin
                fRGBA.R:=fRGBA.R+smn[x,y]*SrcRGBA[yy][xx].R;
                fRGBA.G:=fRGBA.G+smn[x,y]*SrcRGBA[yy][xx].G;
                fRGBA.B:=fRGBA.B+smn[x,y]*SrcRGBA[yy][xx].B;
              end;
            end;
          end;
          DestRGBA[j][i].R:=Round(fRGBA.R);
          DestRGBA[j][i].G:=Round(fRGBA.G);
          DestRGBA[j][i].B:=Round(fRGBA.B);
          DestRGBA[j][i].A:=Round(fRGBA.A);
        end;
      end;
    finally
      SrcBmp.Unmap(SrcData);
      DestBmp.Unmap(DestData);
      GrayBmp.Unmap(GrayData);
    end;
    dest.Canvas.BeginScene();
    dest.Canvas.DrawBitmap(DestBmp,fRect,fRect,1,true);
    dest.Canvas.EndScene;
  finally
    SrcBmp.Free;
    DestBmp.Free;
    GrayBmp.Free;
  end;
end;

//グレースケール変換
procedure MamGrayScale(src,dest:FMX.Graphics.TBitmap);
var v:byte;
    x,y:integer;
    SrcBmp,DestBmp:TBitmap;//src,destの一時画像
    fRect:TRect;
    SrcData,DestData:TBitmapData;
    SrcRGBA,DestRGBA:PRGBAArr;
begin
  if not assigned(src) then exit;
  if not assigned(dest) then dest:=FMX.Graphics.TBitmap.Create;

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

  //PixelFormatはTPixelFormat.BGRAがデフォルト
  SrcBmp:=FMX.Graphics.TBitmap.Create;
  DestBmp:=FMX.Graphics.TBitmap.Create;
  try
    SrcBmp.Width :=fRect.Width;
    SrcBmp.Height:=fRect.Height;
    SrcBmp.Canvas.BeginScene();
    SrcBmp.Canvas.DrawBitmap(src,fRect,fRect,1,true);
    SrcBmp.Canvas.EndScene;
    DestBmp.Width:=fRect.Width;
    DestBmp.Height:=fRect.Height;
    dest.Width:=fRect.Width;
    dest.Height:=fRect.Height;

    SrcBmp.Map(TMapAccess.Read,SrcData);
    DestBmp.Map(TMapAccess.Write,DestData);
    try
      for y := 0 to SrcBmp.Height-1 do
      begin
        SrcRGBA:=SrcData.GetScanline(y);
        DestRGBA:=DestData.GetScanline(y);
        for x := 0 to fRect.Width-1 do
        begin
          v:=Round(
            0.299*SrcRGBA[x].R+ 0.587*SrcRGBA[x].G+ 0.114*SrcRGBA[x].B
          );
          DestRGBA[x].R:=v;
          DestRGBA[x].G:=v;
          DestRGBA[x].B:=v;
          DestRGBA[x].A:=255;
        end;
      end;
    finally
      SrcBmp.Unmap(SrcData);
      DestBmp.Unmap(DestData);
    end;
    dest.Canvas.BeginScene();
    dest.Canvas.DrawBitmap(DestBmp,fRect,fRect,1,true);
    dest.Canvas.EndScene;
  finally
    SrcBmp.Free;
    DestBmp.Free;
  end;
end;

end.