Zum Inhalt

Exif und C#: Wege um an die GPS-Metadaten aus Bildern zu kommen

Das Auslesen von Exif-Metadaten aus JPEG-Bildern ist für zahlreiche Anwendungsgebiete sehr interessant. Anhand der GPS-Dateien kann man so beispielsweise seine Sammlung nach Aufnahmeort gruppieren oder alle Fotos finden, die mit einer bestimmten Kamera gemacht wurden.

In Ruby ist das Auslesen der Exif-Informationen dank Exifr sehr einfach. Für C# fehlt ein klarer Favorit, was zahlreiche kleine und wenig verwendete Bibliotheken hervorbrachte. Während solche Konkurrenzsituationen sonst zu einem belebten Markt führen, ist es in C# eher eine Spirale nach unten: Noch weniger Funktionalität und noch weniger Dokumentation – so jedenfalls scheint es bei einem Blick auf die Suchresultate in NuGet.

Exif-Informationen mit den Bordmittel von .Net auslesen

Über die Klasse Bitmap aus System.Drawing kann man sich über die PropertyItems zu den Exif-Informationen vorarbeiten. Dies ist gut geeignet um eine Liste aller gesetzten Exif-Felder zu erhalten. Möchte man aber auch die Inhalte habe, so wird es sehr mühsam. Um beispielsweise die GPS-Koordinaten auszulesen, gilt es diese Herausforderungen zu lösen:

  1. Typen-Nummer der Felder für den Längen- und Breitengrad finden
  2. Typen-Nummer für die Richtung des Längen- und Breitengrades finden (Nord/Süd, Ost/West)
  3. Felder extrahieren
  4. Inhalt der Felder korrekt konvertieren
  5. Anhand der Richtung die Koordinaten korrekt umrechnen

Nach langer Suche, einigen Fehlschlägen und viel kopiertem Code bin ich bei diesen 2 Methoden angekommen:

public void ExtrahiereGPSInformationen(string filePath)
{
    using (var fileStream = File.Open(filePath, FileMode.Open))
    {
        var image = new Bitmap(fileStream);

        // Definition: https://msdn.microsoft.com/de-de/library/ms534416.aspx
        var typeLatitudeRef = 0x0001;
        var typeLatitude = 0x0002;
        var typeLongitudeRef = 0x0003;
        var typeLongitude = 0x0004;

        try
        {
            var latitudeRef = image.GetPropertyItem(typeLatitudeRef);
            var latitude = image.GetPropertyItem(typeLatitude);
            var latitudeAsDouble = ExifGpsToDouble(latitudeRef, latitude);

            var longitudeRef = image.GetPropertyItem(typeLongitudeRef);
            var longitude = image.GetPropertyItem(typeLongitude);
            var longitudeAsDouble = ExifGpsToDouble(longitudeRef, longitude);

            Console.WriteLine($"{latitudeAsDouble}  {longitudeAsDouble}");
        }
        catch (Exception)
        {
            Console.WriteLine("Keine GPS-Metadaten gefunden");
        }
    }
}


/// <summary>
/// Wandelt GPS-Daten in Doubles um.
/// Von "Cesar" aus http://stackoverflow.com/questions/4983766/getting-gps-data-from-an-images-exif-in-c-sharp
/// </summary>
/// <param name="propItemRef"></param>
/// <param name="propItem"></param>
/// <returns></returns>
public 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 = Encoding.ASCII.GetString(new byte[1] { propItemRef.Value[0] }); //N, S, E, or W
    if (gpsRef == "S" || gpsRef == "W")
        coorditate = coorditate * -1;
    return coorditate;
}

Während dieser Ansatz nun die GPS-Koordinaten liefert, fehlen mir für alle anderen Felder eine vergleichbare Konvertierung. Dieses mühsame abringen der Feldinhalte dürfte wohl für so mache verlassene Exif-Bibliothek in C# verantwortlich sei.

Auslesen mit Exif.Net

Exif.Net von William Hemmingsson ist erfreulich gut dokumentiert. Man findet alles was man zum Starten braucht direkt in der Readme-Datei. Leider ist man auch sehr schnell an den Grenzen dieser Bibliothek angekommen. So gibt es zwar zahlreiche Felder für Angaben zur Aufnahmeart, der Auflösung oder zum Blitz, allerdings nichts zu den GPS-Informationen.

Der Code zum Auslesen des Kamera-Modells ist dafür sehr einfach:

1
2
3
4
5
6
7
8
using (var s = File.Open(filePath, FileMode.Open))
{
    var image = new Bitmap(s);
    var exif = new Exif(image.PropertyItems);

    Console.WriteLine($"Hersteller: {exif.Make}");
    Console.WriteLine($"Kamera: {exif.Model}");
}

Aktuell ist der Einsatzbereich von Exif.Net daher sehr beschränkt. Mit genug Zeit, Geduld und Wissen könnte man die zahlreichen Erweiterungspunkte von Exif.Net nutzen und die fehlenden Felder selber ergänzen.

Auslesen mit ExifLib

Das Projekt ExifLib von Simon McKenzie ist da schon deutlich praktikabler. Die Exif-Felder sind als enum abgelegt, wodurch man die gewünschten Felder sehr einfach abrufen kann. Bei den GPS-Koordinaten muss man sich allerdings selber um die Umrechnung kümmern:

using (var reader = new ExifReader(filePath))
{
    Double[] gpsLongArray;
    Double[] gpsLatArray;
    string gpsLatRef;
    string gpsLongRef;

    if (reader.GetTagValue<Double[]>(ExifTags.GPSLongitude, out gpsLongArray)
        && reader.GetTagValue<Double[]>(ExifTags.GPSLatitude, out gpsLatArray)
        && reader.GetTagValue<string>(ExifTags.GPSLatitudeRef, out gpsLatRef)
        && reader.GetTagValue<string>(ExifTags.GPSLongitudeRef, out gpsLongRef))
    {
        var latitude = gpsLatArray[0] + gpsLatArray[1] / 60 + gpsLatArray[2] / 3600;
        latitude = Koordinaten.KorrekturBreite(gpsLatRef, latitude);

        var longitude = gpsLongArray[0] + gpsLongArray[1]/60 + gpsLongArray[2]/3600;
        longitude = Koordinaten.KorrekturLaenge(gpsLongRef, longitude);

        Console.WriteLine($"{latitude}  {longitude}");
    }
}

Falls man Koordinaten ohne Angabe der Himmelsrichtung ausgeben will, braucht es noch diesen Code:

public class Koordinaten
{
    public static double KorrekturLaenge(string laengeReferenz, double laenge)
    {
        if ("W".Equals(laengeReferenz))
        {
            laenge *= -1;
        }
        return laenge;
    }

    public static double KorrekturBreite(string breiteReferenz, double breite)
    {
        if ("S".Equals(breiteReferenz))
        {
            breite *= -1;
        }
        return breite;
    }
}

Damit wird aus einem 110.1116467 West die für Kartenanwendungen eher gebräuchlichen -110.1116467.

Auslesen mit MetadataExtractor

MetadataExtractor wird von Drew Noakes gepflegt und bietet wohl den grössten Funktionsumfang zum Extrahieren von Metadaten. Für GPS-Informationen steht nicht nur die dezimale Repräsentation zur Verfügung (36.98276 -110.111646666667), sondern auch die sexagesimale Notation mit Grad und Bogenminuten (36° 58' 57.94" -110° 6' 41.93"). Der notwendige Code zum Auslesen der GPS-Koordinaten ist im Vergleich zu den anderen Bibliotheken an Einfachheit kaum zu übertreffen:

var allMetadata = ImageMetadataReader.ReadMetadata(filePath);
var gpsInfo = allMetadata.OfType<GpsDirectory>().FirstOrDefault();

// Angaben in Grad, Bogenminuten und Bogensekunde
var latitude = gpsInfo?.GetDescription(GpsDirectory.TagLatitude);
var longitude = gpsInfo?.GetDescription(GpsDirectory.TagLongitude);
Console.WriteLine($"{latitude}  {longitude}");

// Angabe in Grad und Graddezimale
var gpsData = gpsInfo?.GetGeoLocation();
Console.WriteLine($"{gpsData?.Latitude}  {gpsData?.Longitude}");

Code Beispiele

Wer selber ein wenig mit diesen Code-Beispielen arbeiten möchte, findet diese als Visual-Studio Projekt auf GitHub.

Fazit

Wer mit C# Exif-Metadaten auslesen will, steht vor einem kleinen Abenteuer. Je nach gewünschten Feldern ist die eine oder andere Bibliothek besser. Für meine Anforderungen ist MetadataExtractor aktuell am besten geeignet und empfiehlt sich angesichts der Breite der unterstützten Felder, als Startpunkt für die eigenen Abklärungen.