La Référence Absolue sur les Technologies .NET

   
  Ajouter aux favoris  Imprimer  www.labo-dotnet.com
 
 

Lire les metadonnées EXIF

Par  Vincent Bourdon
Publié le 03/07/2003

Lire les metadonnées EXIF


Introduction
 

Qu'est-ce qu'un fichier EXIF JPEG ?

 Le format EXIF (Exchangeable Image File) est une spécification internationale qui permet aux entreprises d'imagerie d'encoder des méta données dans les en-têtes ou segments d'application d'un fichier JPEG. Ces méta données comprennent des informations sur la vitesse de l'obturateur, son ouverture, ainsi que la date et l'heure de la prise des images.

Les appareils photo numériques actuels stockent les images dans des fichiers compressés EXIF. Ces fichiers utilisent le format classique JPEG DCT. Cela signifie que les données d'image peuvent être lues par n'importe quelle application prenant en charge le format JPEG, notamment tous les navigateurs Web, les programmes de retouche d'image, de mise en page assistée par ordinateur et de création de documents .  

Histoire

 Le format Exif a été développé en 1996. A cette époque, la Japan Electronics and Information Technology Industries Association, JEITA, a reconnu le besoin d'établir un standard pour les images numériques. Cela s'est traduit par la création du premier standard Exif 1.0 fin 1996. 

Au milieu de 1998, les appareils numériques devinrent plus répandus et JEITA fit passer le standard à la version Exif 2.1, une révision qui contient en plus des spécifications pour les fichiers audio, permet aux images plus complexes d'en changer la chrominance et établit la façon dont les miniatures sont incluses dans les en-têtes des images. 

En Avril 2002, JEITA a annoncé la nouvelle version d'Exif contenant des paramètres directement liés à l'impression des photos : Exif Print (V2.2). 

Pour terminer cette petite introduction il faut retenir que les méta données du standard EXIF contiennent des tonnes d’informations sur vos photos qui vont des données de base comme la résolution jusqu'aux coordonnées GPS, ce sont toutes ces informations que nous allons récupérer avec le frameworks .NET. Comme nous nous intéressons ici uniquement au données des images, je vais suivre la version 2.1 qui est fonctionnel et donc couramment utilisée. 

Explication Détaillée
Comment marche le EXIF ?


Suivant que l’image est compressée ou non EXIF utilise deux formats différents :

-         JPEG pour les images compressées

-         TIFF pour les images non compressés. 

 L’enregistrement des meta données va donc différer en fonction de la compression. Voici les schémas pour les deux standards :

 

 

Ne cherchez pas à comprendre exactement les schémas, car ce n’est pas notre but ici. Il faut aussi savoir que EXIF donne la possibilité d’enregistrer directement dans l’image une miniature d’elle-même.

 

Plus simplement chaque champ IDF correspond à une méta donnée. L’IDF est formé de quatre sous champs :

-         Le Tag : un identifiant unique pour chaque méta donnée sur deux octets

-         Le Type : c’est le type de données : octet, ascii ….

-         Count : c’est en fait la taille des données

-         Valeur : La valeur de la méta donnée

 

Le Framework .NET dans tout ça ?


 Le framework .NET va être d’une grande aide, car il contient une classe PropertyItem qui permet très simplement de récupérer et de réécrire les valeurs des meta données. 

Fonctionnement de la classe Image 
La classe PropertyItem ne s’utilise pas directement il faut en fait créer un objet de type image.
La classe Image contient alors plusieurs choses intéressantes :

-         une propriété PropertyItems qui est un tableau des objets PropertyItem qui décrivent cet objet Image

-         une propriété PropertyIdList c’est un tableau des ID des propriétés stockées dans cet objet Image.

-         Une méthode GetPropertyItem qui retourne un objet PropertyItem correspondant au Tag passé en paramètre.

-         Une méthode RemovePropertyItem qui supprime l’objet PropertyItem correspondant au Tag passé en paramètre.

-         Une méthode SetPropertyItem qui affecte la valeur spécifiée à l'élément de propriété spécifié 

Fonctionnement de la classe PropertyItem


Voyons maintenant le détail de la classe PropertyItem. Cette classe contient quatre propriétés :

-         ID : correspond au Tag EXIF

-         Value : c’est le tableau de valeurs

-         Len : longueur (en octets) du tableau de valeurs sur laquelle pointe la propriété Value (Count EXIF)

-         Type : Type de données des valeurs du tableau sur laquelle pointe la propriété Value. Les formats indiqués par les valeurs de la propriété Type sont présentés dans le tableau suivant

 

Valeur numérique

Description

1

Octet

2

Tableau d'objets Byte codés en ASCII

3

Entier de 16 bits

4

Entier de 32 bits

5

Tableau de deux objets Byte représentant un nombre rationnel

6

Non utilisé

7

Indéfini

8

Non utilisé

9

SLong

10

SRational

 

Comme on peut le remarquer toutes les propriétés correspondent exactement au IDF de EXIF, même les types d’objets présentés dans le tableau sont identiques. 

Création de la classe pour EXIF 
Petit cahier des charges


La classe permettra de lire les  méta données d’une image dont le chemin d’accès sera passé en paramètre dans le constructeur.

A partir de l’objet instancié il faudra pouvoir récupérer pour chaque méta donnée décrite par le standard EXIF 2.1 sa valeur, son type, son nom.

Cette classe ne permettra que la lecture dans un premier temps. 

Schéma des classes


Voici un schéma des classes que nous allons créer. 

 

 

Nous allons créer deux classes la classe ExifMetadata contiendra la liste de toute les méta données utilisées dans EXIF. Chaque méta donnée sera représentée par une instance publique de la classe MetadataDetail. Cette dernière permettra de récupérer les infos sur la méta donnée (nom, valeur …). 

Création de la classe MetadataDetail

 Je vais créer la classe en C# ici on pas parce que cela est plus simple mais tout simplement parce que les élèves de Supinfo ont travaillé avec ce langage. 

Voilà ma classe terminée : 

            public class MetadataDetail
            {
                  public int Hex;
                  public string Nom;
                  public byte []RawValue;
                  public string DisplayValue;
                  private int _Type;

                  public string Type
                  {
                        get
                        {
                             switch(_Type)
                              {
                                   case 1: return "Octet"; 
                                   case 2: return "Tableau d'objets Byte codés en ASCII";
                                   case 3: return "Entier de 16 bits";
                                   case 4: return "Entier de 32 bits";
                                   case 5: return "Tableau de deux objets Byte représentant un nombre rationnel";
                                   case 6: return "Non utilisé";
                                   case 7: return "Indéfini";
                                   case 8: return "Non utilisé";
                                   case 9: return "SLong";
                                   case 10: return "SRational";
                                   default : return "Indéfini";
                             }
                        }
                        set
                        {
                             _Type = int.Parse(value);
                        }
                  }
            }

 

Je vais vous expliquer le code qui reste assez simple.

Cette classe contient cinq membres, quatre sont tout simplement des variables publiques, et le dernier membre est une propriété. 

Les membres publics :

Hex : est l’identifiant Hexadécimal de la métadonnée (le Tag).

Nom : c’est le nom en anglais de la métadonnée.

Le tableau RawValue : contiendra la valeur encodé en byte de la métadonnée qui est dans l’image.

DisplayValue : c’est la valeur que l’on va affiché dans un programme, c’est la « traduction » de la RawValue. 

La propriété Type :

Qu’est qu’une propriété ?

Une propriété ce n’est rien d’autre que tout bêtement créer deux fonctions une pour lire la valeur et l’autre pour écrire la valeur d’un membre privé.

Cela fonctionne de la façon suivante :

On créer une variable privée, ici _Type , puis on créer ensuite une autre variable publique cette fois ci : Type.

Cette variable Type devient une propriété car elle contient deux méthodes : une méthode get qui doit retourner une valeur du même type que la propriété et une méthode set qui affecte un valeur par l’intermédiaire du mot réservé value. 

En gros ici j’ai utilisé une propriété pour ne pas recevoir le code de type de valeur mais bien le type clairement, pour ensuite pouvoir l’afficher si nécessaire. 

 

Création de la classe ExifMetadata

C’est dans cette classe que je vais instancier la classe MetadataDetail créée précédemment.
Pour chaque méta donnée du standard EXIF j’instancie la classe. 

public class ExifMetadata
      {
            //====== creer des instance pour chaque propriete 
            public MetadataDetail ExifOffset  = new MetadataDetail();
            public MetadataDetail GPSInfo  = new MetadataDetail();
            public MetadataDetail InteroperabilityOffset  = new MetadataDetail();  

            //A. Tags relating to image data structure
            public MetadataDetail ImageWidth = new MetadataDetail();
            public MetadataDetail ImageLength = new MetadataDetail();
            public MetadataDetail BitsPerSample = new MetadataDetail();
            public MetadataDetail Compression = new MetadataDetail();
            public MetadataDetail PhotometricInterpretation = new MetadataDetail();
            public MetadataDetail Orientation = new MetadataDetail();
            public MetadataDetail SamplesPerPixel = new MetadataDetail();
            public MetadataDetail PlanarConfiguration = new MetadataDetail();
            public MetadataDetail YCbCrSubSampling = new MetadataDetail();
            public MetadataDetail YCbCrPositioning = new MetadataDetail();
            public MetadataDetail XResolution = new MetadataDetail();
            public MetadataDetail YResolution = new MetadataDetail();
            public MetadataDetail ResolutionUnit = new MetadataDetail();  

            // B. Tags relating to recording offset
            public MetadataDetail StripOffsets = new MetadataDetail();
            public MetadataDetail RowsPerStrip = new MetadataDetail();
            public MetadataDetail StripByteCounts = new MetadataDetail();
            public MetadataDetail JPEGInterchangeFormat = new MetadataDetail();
            public MetadataDetail JPEGInterchangeFormatLength = new MetadataDetail();             

            //C. Tags Relating to Image Data Characteristics
            public MetadataDetail TransferFunction = new MetadataDetail();
            public MetadataDetail WhitePoint = new MetadataDetail();
            public MetadataDetail PrimaryChromaticities = new MetadataDetail();
            public MetadataDetail YCbCrCoefficients = new MetadataDetail();
            public MetadataDetail ReferenceBlackWhite = new MetadataDetail();  

            //D. Other Tags
            public MetadataDetail DateTime = new MetadataDetail();
            public MetadataDetail ImageDescription = new MetadataDetail();
            public MetadataDetail Make = new MetadataDetail();
            public MetadataDetail Model = new MetadataDetail();
            public MetadataDetail Software = new MetadataDetail();
            public MetadataDetail Artist = new MetadataDetail();
            public MetadataDetail Copyright = new MetadataDetail();  

            //A. Tags Relating to Version
            public MetadataDetail ExifVersion  = new MetadataDetail();
            public MetadataDetail FlashPixVersion  = new MetadataDetail();  

            //B. Tag Relating to Color Space
            public MetadataDetail ColorSpace  = new MetadataDetail();  

            //C. Tags Relating to Image Configuration
            public MetadataDetail PixelXDimension = new MetadataDetail();
            public MetadataDetail PixelYDimension = new MetadataDetail();
            public MetadataDetail ComponentsConfiguration  = new MetadataDetail();
            public MetadataDetail CompressedBitsPerPixel   = new MetadataDetail();  

            //D. Tags Relating to User Information
            public MetadataDetail MakerNote  = new MetadataDetail();
            public MetadataDetail UserComment  = new MetadataDetail();  

            //E. Tag Relating to Related File
            public MetadataDetail DateTimeOriginal  = new MetadataDetail();
            public MetadataDetail DateTimeDigitized  = new MetadataDetail();
            public MetadataDetail SubsecTime = new MetadataDetail();
            public MetadataDetail SubsecTimeOriginal = new MetadataDetail();
            public MetadataDetail SubsecTimeDigitized = new MetadataDetail();

 

            //G. Tags Relating to Picture-Taking Conditions
            public MetadataDetail ExposureTime = new MetadataDetail();
            public MetadataDetail FNumber = new MetadataDetail();
            public MetadataDetail ExposureProgram  = new MetadataDetail();
            public MetadataDetail SpectralSensitivity  = new MetadataDetail();
            public MetadataDetail ISOSpeedRatings  = new MetadataDetail();
            public MetadataDetail OECF  = new MetadataDetail();
            public MetadataDetail ShutterSpeedValue  = new MetadataDetail();
            public MetadataDetail ApertureValue  = new MetadataDetail();
            public MetadataDetail BrightnessValue  = new MetadataDetail();
            public MetadataDetail ExposureBiasValue  = new MetadataDetail();
            public MetadataDetail MaxApertureValue   = new MetadataDetail();
            public MetadataDetail MeteringMode  = new MetadataDetail();
            public MetadataDetail LightSource  = new MetadataDetail();
            public MetadataDetail Flash  = new MetadataDetail();
            public MetadataDetail SubjectArea  = new MetadataDetail();
            public MetadataDetail FocalLength   = new MetadataDetail();
            public MetadataDetail FlashEnergy  = new MetadataDetail();
            public MetadataDetail SpatialFrequencyResponse  = new MetadataDetail();
            public MetadataDetail FocalPlaneXResolution  = new MetadataDetail();
            public MetadataDetail FocalPlaneYResolution  = new MetadataDetail();
            public MetadataDetail FocalPlaneResolutionUnit  = new MetadataDetail();
            public MetadataDetail SubjectLocation  = new MetadataDetail();
            public MetadataDetail ExposureIndex  = new MetadataDetail();
            public MetadataDetail SensingMethod  = new MetadataDetail();
            public MetadataDetail FileSource   = new MetadataDetail();
            public MetadataDetail SceneType  = new MetadataDetail();
            public MetadataDetail CFAPattern = new MetadataDetail();
            public MetadataDetail CustomRendered = new MetadataDetail();
            public MetadataDetail ExposureMode = new MetadataDetail();
            public MetadataDetail WhiteBalance = new MetadataDetail();
            public MetadataDetail DigitalZoomRatio = new MetadataDetail();
            public MetadataDetail FocalLengthIn35mmFilm = new MetadataDetail();
            public MetadataDetail SceneCaptureType = new MetadataDetail();
            public MetadataDetail GainControl = new MetadataDetail();
            public MetadataDetail Contrast = new MetadataDetail();
            public MetadataDetail Saturation = new MetadataDetail();
            public MetadataDetail Sharpness = new MetadataDetail();
            public MetadataDetail DeviceSettingDescription = new MetadataDetail();
            public MetadataDetail SubjectDistanceRange = new MetadataDetail();  

            //H. Other tags
            public MetadataDetail ImageUniqueID = new MetadataDetail();           

       

Là encore il n’y rien de compliqué mais c’est assez long a écrire. 

Une fois la partie déclaration terminée il faut maintenant se lancer dans le code qui permet de récupérer et de traduire les données. Comme on peut s’en douter le code va être très long je ne donc pas vous écrire tout la classe mais des morceaux qui serviront d’exemples. 

La première chose à faire c’est un constructeur pour cette classe qui prendra en paramètre le chemin d’une photo : 

public ExifMetadata(string PhotoName)

            {

                  // creer une instance de l'image spécifié en paramettre

                  Image MyImage = Image.FromFile(PhotoName);

 

A partir du chemin de l’image je créer un objet de type Image qui va me permettre d’utiliser la méthode GetPropertyItem. 

Tout le reste du code sera aussi dans le constructeur ce qui permettra de récupérer directement les méta données lors de l’instance de l’objet. 

Exemple pour une string : 

Juste en dessus je vais aussi instancier un objet de la classe ASCIIEncding, car cet objet va permettre de décoder directement une valeur ASCII codé en byte, nous allons voir un exemple : 

// Declare un ASCIIEncoding pour retourner une string depuis des bytes

                  System.Text.ASCIIEncoding Value = new System.Text.ASCIIEncoding();

                 

// Make

                  try
                  {
                        Make.Nom = "Make";
                        Make.Hex = 0x10f;
                        Make.RawValue = MyImage.GetPropertyItem(Make.Hex).Value;
                        Make.Type = MyImage.GetPropertyItem(Make.Hex).Type.ToString();
                        Make.DisplayValue = Value.GetString(MyImage.GetPropertyItem(Make.Hex).Value);
                 }
                  catch(ArgumentException e)
                  {
                        //MessageBox.Show(e.Message);
                  }

Make correspond à la marque de l’appareil photo. J’utilise un Try/Catch au cas où la méta donnée n’existe pas dans le fichier, ce qui lève une exception de type ArgumentException.

Les deux premières lignes de code sur l’objet Make permettent d’initialiser son nom et son tag en valeur hexa.

Il me reste alors plus qu’à utiliser la méthode GetPropertyItem sur l’objet MyImage avec en paramètre la valeur hexa de Make pour récupérer la valeur brute de la méta donnée et aussi son type.

Ensuite je décode la valeur brute pour récupérer une string grâce à la méthode GetString. 

Exemple pour une valeur de type short ou long

// ImageWidth
                  try
                  {
                        ImageWidth.Nom = "ImageWidth";
                        ImageWidth.Hex = 0x100;
                        ImageWidth.RawValue = MyImage.GetPropertyItem(ImageWidth.Hex).Value;
                        ImageWidth.Type = MyImage.GetPropertyItem(ImageWidth.Hex).Type.ToString();
                        ImageWidth.DisplayValue = (BitConverter.ToInt32(MyImage.GetPropertyItem(ImageWidth.Hex).Value,0)).ToString();   
                  }
                  catch(ArgumentException e)
                  {
                        // si la taille de l'image n'est pas donnée dans les meta données
                        // j'affiche celle calculée
                        ImageWidth.DisplayValue = MyImage.Width.ToString();
                  }

Tout simplement ici pour j’utilise une méthode static « BitConverter.ToInt32 » pour convertir  des bits en int, puis la méthode  ToString par-dessus pour obtenir la valeur affichable. 

Le petit plus ici c’est que si l’information n’est pas fournie dans les méta données je donne la valeur réel de la taille de l’image récupérée dans le bloc catch. 

Exemple d’une valeur rationnelle 

                  //XResolution

                  try
                  {
                        Int32 nume; 
                        Int32 denom;
                        XResolution.Nom = "XResolution";
                        XResolution.Hex = 0x11A;
                        XResolution.RawValue = MyImage.GetPropertyItem(XResolution.Hex).Value;
                        XResolution.Type = MyImage.GetPropertyItem(XResolution.Hex).Type.ToString();
                        nume = BitConverter.ToInt32(XResolution.RawValue,0);
                        denom = BitConverter.ToInt32(XResolution.RawValue,4);
                        XResolution.DisplayValue = nume.ToString() +"/" + denom.ToString();                  

                  }
                  catch(ArgumentException e)
                  {
                        XResolution.DisplayValue = "72/1";
                        
                  }

 La valeur rationnelle est représentée par un tableau de huit Bytes, les quatre premiers représentent le numérateur et les quatre autres le dénominateur. Grâce à la fonction  ToInt32 je peux facilement convertir car elle prend en second paramètre l’index du tableau de bytes ou commence le int, c'est-à-dire 0 pour le numérateur, 4 pour le dénominateur, et elle utilise quatre bytes pour obtenir le int. Il ne reste alors plus qu’à présenter la valeur sous une forme correcte dans DisplayValue. 

Le bloc catch me permet ici d’utilise la valeur par défaut qui est de 72/1. 

Exemple de code pour une série de valeur spécifique :


 Dans le standard, pour certaines méta données, on récupère une valeur numérique qui signifie quelque chose de particulier, c’est le cas pas exemple de « ExposureProgram »   

//ExposureProgram

                  try
                  {
                        ExposureProgram.Nom = "ExposureProgram";
                        ExposureProgram.Hex = 0x8822;
                        ExposureProgram.RawValue = MyImage.GetPropertyItem(ExposureProgram.Hex).Value;
                        ExposureProgram.Type = MyImage.GetPropertyItem(ExposureProgram.Hex).Type.ToString();
                        switch(BitConverter.ToUInt16(ExposureProgram.RawValue,0))
                        {
      case 0: ExposureProgram.DisplayValue = "Not defined"; break;
      case 1: ExposureProgram.DisplayValue = "Manual"; break;
      case 2: ExposureProgram.DisplayValue = "Normal program"; break;
      case 3: ExposureProgram.DisplayValue = "Aperture priority"; break;
      case 4: ExposureProgram.DisplayValue = "Shutter priority"; break;
      case 5: ExposureProgram.DisplayValue = "Creative program"; break;
      case 6: ExposureProgram.DisplayValue = "Action program"; break;
      case 7: ExposureProgram.DisplayValue = "Portrait mode"; break;
      case 8: ExposureProgram.DisplayValue = "Landscape mode"; break;
      default : ExposureProgram.DisplayValue = "reserved"; break;   }

                  }
                  catch(ArgumentException e)
                  {
                        
                  }   

 

Comment Utiliser la classe


Référence


La classe que j’ai compilée se trouve dans une dll, que l’on peut bien sur réutiliser dans tous les programmes.

La première chose à faire pour pouvoir l’utiliser dans votre application .net est de la référencer de la manière suivante : 

Dans l’explorateur de solution, dans le projet concerné fait un clique droit sur Référence, et cliquez sur Ajouter une référence. 

Dans la fenêtre qui apparaît cliquez sur Parcourir… et sélectionnez la dll. Elle doit alors s’afficher en bas dans Composants sélectionnés. Faites OK 

La dll est maintenant référencée mais il reste a ajouter une ligne dans votre code source pour pouvoir utiliser directement la classe sans spécifier le namespace, ajoutez cette directive : 

using Exif; 

Récupération de valeurs

L’utilisation ce fait très simplement il ne suffit que de créer un objet de type ExifMetadata avec en paramètre le chemin de l’image a utiliser. On a alors directement accès au variable public exemple : 

ExifMetadata MyExifMetadata = new ExifMetadata(« c:\monimage.jpg »);  

 

Console.WriteLine(« taille de l’image : » +MyExifMetadata.ImageWidth.DisplayValue + «x» + MyExifMetadata.ImageLength.DisplayValue;

     

Téléchargement

La librairie : exifmetadata.dll

Liens de références
 

www.exif.org                           Site officiel de EXIF

 

 

Commentaires
Par JC SILERME 'JCS' le 02/11/2004 15:13
C'est très clair.
Mais le téléchargement ne fonctionne pas.
Merci

Par Nicolas DUSSET 'LeZigotto' le 14/05/2005 23:44
Bonjour,

Merci à l'auteur pour cet article très clair.
Cela m'a beaucoup aidé à comprendre commment récupérer ses très précieuse données! (Les informations sur le sujet étant rares!! Surtout pour le language C#)

Seulement la dll permet de récupérer uniquement les données EXIF pour la version 2.1

S'il était possible de donner accès aux sources complètes pour que je puisse rapidement faire une DLL compatible avec EXIF 2.2. (que je mettrais à dispo of course...)

Merci


Par Christophe BISMUTH 'cbismuth' le 07/08/2005 18:31
Bonjour,

Je programme en Java depuis plusieurs années, mais pour les métadonnées EXIF ce langage n'est pas suffisement ergonomique. Donc merci pour ces explications très claires.

J'avoue avoir eu beaucoup de mal avec les données techniques du format JPEG (voir le forum Java de Sun : http://forum.java.sun.com/thread.jspa?forumID=31&threadID=627234).

Concernant la version 2.2 des métadonnées EXIF pourrions-nous avoir plus d'informations ?
Je suis fraichement débutant en Visual Studio, .net & C#; outre le fichier DLL, votre code source est-il disponible ?

D'avance merci,
Christophe

Par Christophe BISMUTH 'cbismuth' le 07/08/2005 18:39
Fautes d'orthographe : "suffisement" / "suffisamment" & "fraichement" / "fraîchement".

Désolé...

Ajouter un commentaire
Vous devez être authentifié pour poster un commentaire. Vous pouvez vous authentifier ici.

 

International Masters SUPINFO

Laboratoire Sécurité

 
   
© Copyright SUPINFO - The International Institute of Information Technology
Conditions dutilisation et Copyright - Respect de la vie prive
Labo Microsoft | Forum Microsoft | Forums .Net | Labo .Net |