Binarisation d'images

Description

IMPORTANT: Avant d'ouvrir les fichiers qui se terminent par ...FrameUnit.pas, BIEN LIRE LA REMARQUE FINALE!

C'est suite à une suggestion de Caribensila (voir http://www.delphifr.com/codes/RECHERCHE-SUPPRESSION-IMAGES-DOUBLE-BASEE-SUR-COMPARAISON-INTELLIGENTE_38711.aspx) que j'ai programmé ça. C'est une application simple de traitement d'images. Elle permet de faire subir à une image originale une série de transformations successives et d'afficher l'image résultante. Le but à l'origine était de trouver une méthode pour extraire un grain de beauté d'une photographie pour étudier son aspect au cours du temps.

Pour utiliser le programme, il faut choisir une image initiale, puis ajoutter des filtres, et enfin cliquer sur le bouton "Lancer le calcul". Il y a une fonction de zoom, et on peut faire s'afficher les images initiale et finale en surimpression avec une transparence paramètrable. Avec le menu principal, on peut importer et exporter des chaines de filtres (fichiers *.filter , il y en a 3 fournis).

Pour l'instant il y a 8 filtres disponibles:
+ Filtre médian: un filtre statistique qui remplace chaque pixel par sa valeur médiane à l'intérieur d'une boîte carrée de taille paramètrable (c'est à dire la valeur du milieu lorsqu'on classe les intensités des pixels voisins dans la boite par ordre croissant)
+ Extracteur de canaux: ce filtre extrait des canaux d'une image. Par exemple, on peut extraire le canal vert d'une image en RGB
+ RGB -> HSV: transformation RGB vers HSV (Hue, Saturation, Value) c'est à dire en français la teinte, la saturation et l'intensité lumineuse. C'est un autre mode de représentation des couleurs, très utile en traitement d'images.
+ HSV -> RGB: la transformation réciproque
+ RGB -> Luminance: extraction de la luminance
+ Binarisation: une fonction qui détermine, par une méthode mathématique basée sur l'étude d'ensembles aléatoires, un niveau statistiquement représentatif qui détermine 2 zones dans l'image: l'intérieur et l'extérieur. L'intérieur est transformé en blanc, l'extérieur en noir. Cette fonction est très lente, car elle met en jeu des tas de calculs.
+ Norme du gradient: calcule la norme du gradient de l'image. Utile par exemple pour la détection de bords.
+ Seuillage: met en blanc les pixels dont la valeur est plus grande qu'un certain seuil, en noir sinon.

Sauf dans le cas de RGB->HSV, HSV->RGB et "Extraction de canaux", les filtres travaillent séparément sur tous les canaux de l'image. Je n'ai pas mis de vérification du nombre de canaux, par exemple si vous essayez de transformer une image en HSV qlors qu'elle n'a qu'un seul canal, il se produira une erreur pas très explicite (access violation vraisemblablement), donc il ne faut pas faire n'importe quoi!

Les filtres sont "enchainés", c'est à dire qu'ils s'appliquent l'un après l'autre. Par exemple "RGB -> Luminance" suivi de "Norme du gradient" extraira la norme du gradient de la luminance.

Il y a des exemples de chaines de filtres fournis dans le dossier Exemples/, ainsi que des images pour tester. Dans la plupart des cas, en utilisant le filtre Luminance.filter on arrive à extraire le grain de beauté de l'image (c'est à dire que l'image est bien séparée en 2 parties, l'une étant le grain de beauté). Mais ça ne marche pas à tous les coups... Je suis en train de travailler sur une méthode plus précise, mais ça risque de prendre des temps de calcul prohibitifs...

Il y a une unité de traitement d'images fournie avec le projet: ImgUtils.pas. Elle définit une classe TBitmapData utile pour gérer les images et faire des calculs dessus. J'ai conscience que ça manque de commentaires, je les mettrai si quelqu'un les demande.

Source / Exemple :


unit MainFormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ImgUtils, ExtDlgs, JPEG, ExtCtrls, Buttons, StdCtrls, ComCtrls,
  MedianBoxFilterFrameUnit, ChannelExtractFrameUnit, RGB2HSVFrameUnit,
  HSV2RGBFrameUnit, RGB2LuminanceFrameUnit, BinarizeFrameUnit,
  GradientNormFrameUnit, TresholdFrameUnit, FilterFrameUnit,
  Menus, ToolWin, ImgList;

type
  TMainForm = class(TForm)
    OpenPictureDialog1: TOpenPictureDialog;
    Panel1: TPanel;
    Edit1: TEdit;
    SpeedButton1: TSpeedButton;
    GroupBox1: TGroupBox;
    ListBox1: TListBox;
    PopupMenu1: TPopupMenu;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    ToolButton2: TToolButton;
    Panel2: TPanel;
    GroupBox2: TGroupBox;
    ToolButton3: TToolButton;
    ToolButton4: TToolButton;
    ImageList1: TImageList;
    ToolButton5: TToolButton;
    Panel3: TPanel;
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    ScrollBox1: TScrollBox;
    PaintBox1: TPaintBox;
    TabSheet2: TTabSheet;
    ScrollBox2: TScrollBox;
    PaintBox2: TPaintBox;
    TabSheet3: TTabSheet;
    ScrollBox3: TScrollBox;
    PaintBox3: TPaintBox;
    GroupBox3: TGroupBox;
    TrackBar1: TTrackBar;
    GroupBox4: TGroupBox;
    TrackBar2: TTrackBar;
    MainMenu1: TMainMenu;
    Fichier1: TMenuItem;
    Exporterlachanedefiltres1: TMenuItem;
    Importerunechanedefiltres1: TMenuItem;
    Lancerlachane1: TMenuItem;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
    procedure ToolButton2Click(Sender: TObject);
    procedure ToolButton4Click(Sender: TObject);
    procedure ToolButton5Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure PaintBox2Paint(Sender: TObject);
    procedure PaintBox3Paint(Sender: TObject);
    procedure Lancerlachane1Click(Sender: TObject);
    procedure TrackBar2Change(Sender: TObject);
    procedure Exporterlachanedefiltres1Click(Sender: TObject);
    procedure Importerunechanedefiltres1Click(Sender: TObject);
  private
    FSrcBitmap,FDstBitmap,FPreviewBitmap:TBitmap;
  public
    procedure RegisterFilter(FilterClass:TFilterFrameClass);
    procedure FilterMenuItemClick(Sender:TObject);

    procedure UpdateGUI;
    procedure UpdatePreview;

    function GetZoomRect:TRect;

    procedure ResetProcess;
    procedure Process;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.FormCreate(Sender: TObject);
begin
  SpeedButton1.Align:=alRight;
  Edit1.Align:=alRight;
  FSrcBitmap:=TBitmap.Create;
  FDstBitmap:=TBitmap.Create;
  FPreviewBitmap:=TBitmap.Create;
  RegisterFilter(TMedianBoxFilterFrame);
  RegisterFilter(TChannelExtractFrame);
  RegisterFilter(TRGB2HSVFrame);
  RegisterFilter(THSV2RGBFrame);
  RegisterFilter(TRGB2LuminanceFrame);
  RegisterFilter(TBinarizeFrame);
  RegisterFilter(TGradientNormFrame);
  RegisterFilter(TTresholdFrame);
end;

procedure TMainForm.SpeedButton1Click(Sender: TObject);
var
  p:TBitmap;
begin
  if OpenPictureDialog1.Execute then begin
    Edit1.Text:=OpenPictureDialog1.FileName;
    p:=LoadBitmapFromFile(OpenPictureDialog1.FileName);
    FSrcBitmap.Destroy;
    FSrcBitmap:=p;
    UpdatePreview;
    ResetProcess;
    Lancerlachane1.Enabled:=(FSrcBitmap.Width>0) and (FSrcBitmap.Height>0);
  end;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FSrcBitmap.Destroy;
  FDstBitmap.Destroy;
  FPreviewBitmap.Destroy;
end;

procedure TMainForm.RegisterFilter(FilterClass: TFilterFrameClass);
var
  m:TMenuItem;
begin
  m:=TMenuItem.Create(Self);
  m.Caption:=FilterClass.Caption;
  m.Tag:=Integer(FilterClass);
  m.OnClick:=FilterMenuItemClick;
  PopupMenu1.Items.Add(m);
  RegisterClass(FilterClass);
end;

procedure TMainForm.UpdateGUI;
begin
  if ListBox1.ItemIndex>-1 then begin
    while GroupBox2.ControlCount>0 do
      GroupBox2.Controls[0].Parent:=nil;
    with TFilterFrame(ListBox1.Items.Objects[ListBox1.ItemIndex]) do begin
      Align:=alClient;
      Parent:=GroupBox2;
    end;
    ToolButton2.Enabled:=True;
  end else
    ToolButton2.Enabled:=False;
  ToolButton4.Enabled:=ListBox1.ItemIndex>0;
  ToolButton5.Enabled:=(ListBox1.ItemIndex>-1) and (ListBox1.ItemIndex<ListBox1.Count-1);
  UpdatePreview;
end;

procedure TMainForm.FilterMenuItemClick(Sender: TObject);
var
  f:TFrame;
begin
  with TFilterFrameClass(TMenuItem(Sender).Tag) do begin
    f:=Create(ListBox1);
    f.Name:='';
    ListBox1.ItemIndex:=ListBox1.Items.AddObject(Caption,f);
  end;
  UpdateGUI;
end;

procedure TMainForm.ListBox1Click(Sender: TObject);
begin
  UpdateGUI;
end;

procedure TMainForm.ToolButton2Click(Sender: TObject);
var
  a:Integer;
begin
  a:=ListBox1.ItemIndex;
  ListBox1.Items.Objects[a].Destroy;
  ListBox1.Items.Delete(a);
  if a>=ListBox1.Items.Count then
    Dec(a);
  ListBox1.ItemIndex:=a;
  UpdateGUI;
end;

procedure TMainForm.ToolButton4Click(Sender: TObject);
begin
  ListBox1.Items.Exchange(ListBox1.ItemIndex,ListBox1.ItemIndex-1);
  UpdateGUI;
end;

procedure TMainForm.ToolButton5Click(Sender: TObject);
begin
  ListBox1.Items.Exchange(ListBox1.ItemIndex,ListBox1.ItemIndex+1);
  UpdateGUI;
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  TrackBar1.SetTick(0);
  Lancerlachane1.ImageIndex:=4;
end;

procedure TMainForm.TrackBar1Change(Sender: TObject);
begin
  UpdatePreview;
end;

procedure TMainForm.UpdatePreview;
var
  r:TRect;
begin
  r:=GetZoomRect;
  PaintBox1.BoundsRect:=r;
  PaintBox1.Invalidate;
  PaintBox2.BoundsRect:=r;
  PaintBox2.Invalidate;
  PaintBox3.BoundsRect:=r;
  PaintBox3.Invalidate;
end;

function TMainForm.GetZoomRect: TRect;
const
  T:array[-5..5] of Single=(0.1,0.2,0.3,0.5,0.75,1,1.5,2,3,5,10);
begin
  Result.TopLeft:=Point(0,0);
  Result.Right:=Round(FSrcBitmap.Width*T[TrackBar1.Position]);
  Result.Bottom:=Round(FSrcBitmap.Height*T[TrackBar1.Position]);
end;

procedure TMainForm.PaintBox1Paint(Sender: TObject);
begin
  PaintBox1.Canvas.StretchDraw(GetZoomRect,FSrcBitmap);
end;

procedure TMainForm.PaintBox2Paint(Sender: TObject);
begin
  PaintBox2.Canvas.StretchDraw(GetZoomRect,FDstBitmap);
end;

procedure TMainForm.PaintBox3Paint(Sender: TObject);
var
  BF:TBlendFunction;
begin
  FPreviewBitmap.Width:=FSrcBitmap.Width;
  FPreviewBitmap.Height:=FSrcBitmap.Height;
  FPreviewBitmap.Canvas.Draw(0,0,FSrcBitmap);
  with BF do begin
    BlendOp:=AC_SRC_OVER;
    BlendFlags:=0;
    SourceConstantAlpha:=TrackBar2.Position;
    AlphaFormat:=0;
  end;
  with FDstBitmap do
    Windows.AlphaBlend(FPreviewBitmap.Canvas.Handle,0,0,FPreviewBitmap.Width,FPreviewBitmap.Height,Canvas.Handle,0,0,Width,Height,BF);
  PaintBox3.Canvas.StretchDraw(GetZoomRect,FPreviewBitmap);
end;

procedure TMainForm.Process;
var
  p:TBitmapData;
  a:Integer;
begin
  p:=TBitmapData.CreateAsRGB(FSrcBitmap);
  try
    for a:=0 to ListBox1.Items.Count-1 do
      with TFilterFrame(ListBox1.Items.Objects[a]) do
        try
          Filter(p);
        except
          on e:Exception do begin
            Messagebox(0,PChar('Le filtre "'+Caption+'" n°'+IntToStr(a)+' a provoqué une erreur: '#13+e.Message),PChar('Exception '+e.ClassName),MB_ICONERROR);
            raise;
          end;
        else
          Messagebox(0,PChar('Le filtre "'+Caption+'" n°'+IntToStr(a)+' a provoqué une erreur non spécifiée'),PChar('Exception inconnue'),MB_ICONERROR);
          raise;
        end;
    p.WriteToBitmap(FDstBitmap);
  finally
    p.Destroy;
    UpdatePreview;
  end;
end;

procedure TMainForm.ResetProcess;
begin
  FDstBitmap.Width:=0;
  FDstBitmap.Height:=0;
  UpdatePreview;
end;

procedure TMainForm.Lancerlachane1Click(Sender: TObject);
begin
  Process;
end;

procedure TMainForm.TrackBar2Change(Sender: TObject);
begin
  PaintBox3Paint(nil);
end;

procedure TMainForm.Exporterlachanedefiltres1Click(Sender: TObject);
var
  f:TFileStream;
  a,b:Integer;
  s:string;
begin
  if SaveDialog1.Execute then begin
    f:=TFileStream.Create(SaveDialog1.FileName,fmOpenWrite or fmCreate);
    try
      f.Seek(0,soFromBeginning);
      for a:=0 to ListBox1.Items.Count-1 do begin
        s:=ListBox1.Items.Objects[a].ClassName;
        b:=Length(s);
        f.Write(b,SizeOf(b));
        f.Write(s[1],b);
        f.WriteComponent(ListBox1.Items.Objects[a] as TComponent);
      end;
    finally
      f.Destroy;
    end;
  end;
end;

procedure TMainForm.Importerunechanedefiltres1Click(Sender: TObject);
var
  f:TFileStream;
  g:TFilterFrame;
  a:Integer;
  s:string;
begin
  if OpenDialog1.Execute then begin
    f:=TFileStream.Create(OpenDialog1.FileName,fmOpenRead);
    try
      for a:=0 to ListBox1.Items.Count-1 do
        ListBox1.Items.Objects[a].Destroy;
      ListBox1.Items.Clear;
      ListBox1.ItemIndex:=-1;
      f.Seek(0,soFromBeginning);
      while f.Position<f.Size do begin
        f.Read(a,SizeOf(a));
        SetLength(s,a);
        f.Read(s[1],a);
        g:=TFilterFrameClass(GetClass(s)).Create(nil);
        g.DestroyComponents;
        g.Name:='';
        f.ReadComponent(g);
        ListBox1.Items.AddObject(g.Caption,g);
      end;
    finally
      f.Destroy;
      UpdateGUI;
    end;
  end;
end;

end.

Conclusion :


Remarque finale: les frames qui sont dans les fichiers se terminant par ...FrameUnit.pas sont des descendants de TFilterFrame. Pour cette raison, lorsqu'on les ouvre dans Delphi certaines propriétés qui ne devraient pas être là sont rajouttées dans le fichier .dfm correspondant par Delphi, car l'IDE ne connait pas la classe TFilterFrame. Ces 3 propriétés incorrectes sont les suivantes:

OldCreateOrder = True
PixelsPerInch = 96
TextHeight = 13

Si vous ouvrez l'un de ces fichiers avec Delphi, il faudra éditer manuellement le fichier *.dfm correspondant (par exemple en faisant Alt+F12) et supprimer ces 3 propriétés, enregistrer et fermer le fichier avant de compiler. Sinon, lors de la création du filtre correspondant, il se produira une erreur "Error while reading property OldCreateOrder: property does not exist". Il existe un moyen de contourner le problème, mais il implique l'installation d'un package, et je ne voulais pas alourdir le code.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.