web-dev-qa-db-fra.com

Obtenir des données GPS à partir d'une image EXIF ​​en C #

Je développe un système qui permet à une image d'être téléchargée sur un serveur à l'aide d'ASP.NET C #. Je traite l'image et tout fonctionne très bien. J'ai réussi à trouver une méthode qui lit les données EXIF ​​de la date de création et je les analyse en tant que DateTime. Cela fonctionne très bien aussi.

J'essaie maintenant de lire les données GPS de l'EXIF. Je veux capturer les chiffres de latitude et de longitude.

J'utilise cette liste comme référence aux données EXIF ​​(en utilisant les numéros pour les éléments de propriété) http://www.exiv2.org/tags.html

Voici la méthode pour capturer la date créée (ce qui fonctionne).

public DateTime GetDateTaken(Image targetImg)
{
    DateTime dtaken;

    try
    {
        //Property Item 306 corresponds to the Date Taken
        PropertyItem propItem = targetImg.GetPropertyItem(0x0132);

        //Convert date taken metadata to a DateTime object
        string sdate = Encoding.UTF8.GetString(propItem.Value).Trim();
        string secondhalf = sdate.Substring(sdate.IndexOf(" "), (sdate.Length - sdate.IndexOf(" ")));
        string firsthalf = sdate.Substring(0, 10);
        firsthalf = firsthalf.Replace(":", "-");
        sdate = firsthalf + secondhalf;
        dtaken = DateTime.Parse(sdate);
    }
    catch
    {
        dtaken = DateTime.Parse("1956-01-01 00:00:00.000");
    }
    return dtaken;
}

Ci-dessous ma tentative de faire de même pour le GPS.

public float GetLatitude(Image targetImg)
{
    float dtaken;

    try
    {
        //Property Item 0x0002 corresponds to the Date Taken
        PropertyItem propItem = targetImg.GetPropertyItem(2);

        //Convert date taken metadata to a DateTime object
        string sdate = Encoding.UTF8.GetString(propItem.Value).Trim();
        dtaken = float.Parse(sdate);
    }
    catch
    {
        dtaken = 0;
    }
    return dtaken;
}

La valeur qui entre et sdate est "5\0\0\0\0\0\0l\t\0\0d\0\0\0\0\0\0\0\0\0\0".

Et cela provient d'une image prise par un iPhone 4 qui transporte les données GPS EXIF.

Je sais qu'il y a des cours qui font cela, mais je préférerais écrire les miens - je suis ouvert aux suggestions cependant :-)

Merci d'avance.

25
tmutton

Selon le lien affiché ci-dessus par tomfanning, l'élément de propriété 0x0002 est la latitude exprimée sous la forme PropertyTagTypeRational. Le type rationnel est défini comme ...

Spécifie que le membre de données de la valeur est un tableau de paires d'entiers longs non signés. Chaque paire représente une fraction; le premier entier est le numérateur et le second est le dénominateur.

Vous essayez de l'analyser en tant que chaîne alors qu'il ne s'agit que d'une série d'octets. Selon ce qui précède, il devrait y avoir 3 paires d’entiers non signés 32 bits dans ce tableau d'octets, que vous pouvez récupérer à l'aide des éléments suivants:

uint degreesNumerator   = BitConverter.ToUInt32(propItem.Value, 0);
uint degreesDenominator = BitConverter.ToUInt32(propItem.Value, 4);
uint minutesNumerator   = BitConverter.ToUInt32(propItem.Value, 8);
uint minutesDenominator = BitConverter.ToUInt32(propItem.Value, 12);
uint secondsNumerator   = BitConverter.ToUInt32(propItem.Value, 16);
uint secondsDenominator = BitConverter.ToUInt32(propItem.Value, 20);

Ce que vous faites avec ces valeurs une fois que vous les avez, c'est que vous travailliez :) Voici ce que disent les docs:

La latitude est exprimée sous forme de trois valeurs rationnelles donnant les degrés, minutes et secondes respectivement. Lorsque les degrés, les minutes et les secondes sont exprimés, le format est dd/1, mm/1, ss/1. Lorsque des degrés et des minutes sont utilisés et que, par exemple, des fractions de minutes ont jusqu'à deux décimales, le format est jj/1, mmmm/100, 0/1.

24
Jon Grant

Un moyen simple (et rapide!) Consiste à utiliser ma bibliothèque open source MetadataExtractor:

var gps = ImageMetadataReader.ReadMetadata(path)
                             .OfType<GpsDirectory>()
                             .FirstOrDefault();

var location = gps.GetGeoLocation();

Console.WriteLine("Image at {0},{1}", location.Latitude, location.Longitude);

La bibliothèque est écrite en C # pur et prend en charge de nombreux formats d’image et décode les données spécifiques à de nombreux modèles de caméras.

Il est disponible via NuGet ou GitHub .

14
Drew Noakes

J'ai trouvé un moyen de récupérer les données GPS EXIF ​​sous forme de flotteurs. J'ai adapté le code de Jon Grant comme suit ...

public static float? GetLatitude(Image targetImg)
{
    try
    {
        //Property Item 0x0001 - PropertyTagGpsLatitudeRef
        PropertyItem propItemRef = targetImg.GetPropertyItem(1);
        //Property Item 0x0002 - PropertyTagGpsLatitude
        PropertyItem propItemLat = targetImg.GetPropertyItem(2);
        return ExifGpsToFloat(propItemRef, propItemLat);
    }
    catch (ArgumentException)
    {
        return null;
    }
}
public static float? GetLongitude(Image targetImg)
{
    try
    {
        ///Property Item 0x0003 - PropertyTagGpsLongitudeRef
        PropertyItem propItemRef = targetImg.GetPropertyItem(3);
        //Property Item 0x0004 - PropertyTagGpsLongitude
        PropertyItem propItemLong = targetImg.GetPropertyItem(4);
        return ExifGpsToFloat(propItemRef, propItemLong);
    }
    catch (ArgumentException)
    {
        return null;
    }
}
private static float ExifGpsToFloat(PropertyItem propItemRef, PropertyItem propItem)
{
    uint degreesNumerator = BitConverter.ToUInt32(propItem.Value, 0);
    uint degreesDenominator = BitConverter.ToUInt32(propItem.Value, 4);
    float degrees = degreesNumerator / (float)degreesDenominator;

    uint minutesNumerator = BitConverter.ToUInt32(propItem.Value, 8);
    uint minutesDenominator = BitConverter.ToUInt32(propItem.Value, 12);
    float minutes = minutesNumerator / (float)minutesDenominator;

    uint secondsNumerator = BitConverter.ToUInt32(propItem.Value, 16);
    uint secondsDenominator = BitConverter.ToUInt32(propItem.Value, 20);
    float seconds = secondsNumerator / (float)secondsDenominator;

    float coorditate = degrees + (minutes / 60f) + (seconds / 3600f);
    string gpsRef = System.Text.Encoding.ASCII.GetString(new byte[1]  { propItemRef.Value[0] } ); //N, S, E, or W
    if (gpsRef == "S" || gpsRef == "W")
        coorditate = 0 - coorditate;
    return coorditate;
}
11
Paul

Voici le code fonctionne, j'ai trouvé quelques erreurs avec float ..__ J'espère que cela aidera quelqu'un.

  private static double ExifGpsToDouble (PropertyItem propItemRef, PropertyItem propItem)
    {
        double degreesNumerator = BitConverter.ToUInt32(propItem.Value, 0);
        double degreesDenominator = BitConverter.ToUInt32(propItem.Value, 4);
        double degrees = degreesNumerator / (double)degreesDenominator;

        double minutesNumerator = BitConverter.ToUInt32(propItem.Value, 8);
        double minutesDenominator = BitConverter.ToUInt32(propItem.Value, 12);
        double minutes = minutesNumerator / (double)minutesDenominator;

        double secondsNumerator = BitConverter.ToUInt32(propItem.Value, 16);
        double secondsDenominator = BitConverter.ToUInt32(propItem.Value, 20);
        double seconds = secondsNumerator / (double)secondsDenominator;


        double coorditate = degrees + (minutes / 60d) + (seconds / 3600d);
        string gpsRef = System.Text.Encoding.ASCII.GetString(new byte[1] { propItemRef.Value[0] }); //N, S, E, or W
        if (gpsRef == "S" || gpsRef == "W")
            coorditate = coorditate*-1;     
        return coorditate;
    }
11
Cesar

Je sais que ceci est un ancien post, mais je voulais fournir une réponse qui m'a aidé. 

J'ai utilisé ExifLibrary situé ici pour écrire et lire des métadonnées à partir de fichiers image: https://code.google.com/p/exiflibrary/wiki/ExifLibrary

C'était simple et facile à utiliser. J'espère que ça aidera quelqu'un d'autre. 

4
Jason Foglia

Vous devez d’abord lire les octets indiquant si les données EXIF ​​sont au format big endian ou little endian afin de ne pas tout gâcher.

Ensuite, vous devez numériser chaque IFD de l'image à la recherche de la balise GPSInfo (0x25 0x88). Si vous ne la trouvez PAS dans un IFD, cela signifie que l'image ne contient aucune information GPS. Si vous trouvez cette balise, lisez les 4 octets de ses valeurs, ce qui vous donne un décalage par rapport à un autre IFD, l'IFD GPS, à l'intérieur de cet IFD, il vous suffit de récupérer les valeurs des étiquettes suivantes:

0x00 0x02 - Pour la latitude

0x00 0x04 - Pour la longitude

0x00 0x06 - Pour l'altitude

Chacune de ces valeurs sont des rationnels non signés.

Ici vous pouvez trouver comment faire presque tout: http://www.media.mit.edu/pia/Research/deepview/exif.html

3
Delta

Avez-vous essayé les balises 0x0013-16 par http://msdn.Microsoft.com/en-us/library/ms534416(v=vs.85).aspx qui ressemble également à la latitude GPS? 

Vous ne savez pas ce qui les distingue des étiquettes numérotées inférieures, mais cela vaut la peine d'essayer.

Voici leurs descriptions:

0x0013 - Chaîne de caractères terminée par un caractère nul, spécifiant si la latitude du point de destination est la latitude nord ou sud. N spécifie la latitude nord et S spécifie la latitude sud.

0x0014 - Latitude du point de destination. La latitude est exprimée sous forme de trois valeurs rationnelles donnant respectivement les degrés, minutes et secondes. Lorsque les degrés, les minutes et les secondes sont exprimés, le format est dd/1, mm/1, ss/1. Lorsque des degrés et des minutes sont utilisés et que, par exemple, des fractions de minutes ont jusqu'à deux décimales, le format est jj/1, mmmm/100, 0/1.

0x0015 - Chaîne de caractères terminée par un caractère nul indiquant si la longitude du point de destination est la longitude est ou ouest. E spécifie la longitude est et W spécifie la longitude ouest.

0x0016 - Longitude du point de destination. La longitude est exprimée sous forme de trois valeurs rationnelles donnant les degrés, minutes et secondes respectivement. Lorsque les degrés, les minutes et les secondes sont exprimés, le format est jj/1, mm/1, ss/1. Lorsque des degrés et des minutes sont utilisés et que, par exemple, des fractions de minutes ont jusqu'à deux décimales, le format est jj/1, mmmm/100, 0/1.

2
tomfanning

Merci, le code est correct mais au moins un échec,

uint minutes = minutesNumerator/minutesDenominator;

ne donne pas un résultat exact, lorsque minutesDenominator n'est pas égal à 1, par exemple dans mon exif = 16,

1
Claus