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.
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.