Получение данных GPS из EXIF ​​изображения в C #

Я разрабатываю систему, которая позволяет загружать изображение на сервер с помощью ASP.NET C #. Я обрабатываю изображение и все отлично работает. Мне удалось найти метод, который считывает данные EXIF ​​даты создания и анализирует их как DateTime. Это тоже отлично работает.

Я сейчас пытаюсь прочитать данные GPS из EXIF. Я хочу запечатлеть значения широты и долготы.

Я использую этот список как ссылку на данные EXIF ​​(используя числа для элементов свойств) http://www.exiv2.org/tags.html

Вот метод фиксации даты создания (который работает).

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;
}

Ниже я попытался сделать то же самое для 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;
}

Значение, которое выходит и вводится в sdate, - "5 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0l \ t \ 0 \ 0d \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0"

И это происходит из изображения, сделанного iPhone 4, на котором есть данные GPS EXIF.

Я знаю, что есть классы, которые делают это, но предпочел бы написать свои собственные - я открыт для предложений :-)

Заранее спасибо.


person tmutton    schedule 13.02.2011    source источник
comment
Удостоверились ли вы, что изображение имеет геотеги, читая изображение в каком-либо другом программном обеспечении? В iPhone можно отключить геотегирование (встраивание данных EXIF) (что логично). Итак, вы на 100% уверены, что эта функция была включена при создании изображения? И, если он включен, уверены ли вы, что телефон находился в каком-то месте (а не в какой-то комнате), когда был сделан снимок? Телефон должен получать сигнал со спутника.   -  person Sarwar Erfan    schedule 13.02.2011
comment
Привет, Сарвар, я только что проверил изображение перед загрузкой, и оно содержит информацию GPS. Я просмотрел сведения о свойствах файла, и они там: Широта, Долгота и Высота.   -  person tmutton    schedule 13.02.2011
comment
Если вы все же решите сдаться или, возможно, захотите проверить образец исходного кода. Мне хорошо послужил MetaDataSpr.   -  person Steven Jeuris    schedule 14.02.2011


Ответы (9)


Согласно ссылке, размещенной выше Tomfanning , элемент свойства 0x0002 - это широта, выраженная как PropertyTagTypeRational. Рациональный тип определяется как .. .

Указывает, что элемент данных значения представляет собой массив пар длинных целых чисел без знака. Каждая пара представляет собой дробь; первое целое число является числителем, а второе целое число - знаменателем.

Вы пытаетесь разобрать его как строку, хотя на самом деле это всего лишь серия байтов. Согласно вышеизложенному, в этот массив байтов должно быть 3 пары 32-битных целых чисел без знака, которые вы можете получить, используя следующее:

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);

Что вы делаете с этими значениями после того, как они у вас есть, вам предстоит работать :) Вот что говорится в документации:

Широта выражается в виде трех рациональных значений, дающих градусы, минуты и секунды соответственно. При выражении в градусах, минутах и ​​секундах используется формат дд / 1, мм / 1, сс / 1. Когда используются градусы и минуты и, например, доли минут указаны с точностью до двух десятичных знаков, формат будет дд / 1, мммм / 100, 0/1.

person Jon Grant    schedule 14.02.2011
comment
Привет, Джон, спасибо за эту информацию. Я сейчас не сижу за компьютером с исходными файлами, но буду в ближайшие несколько дней - надеюсь, я смогу попробовать это и вернуться к вам с некоторым успехом! - person tmutton; 15.02.2011
comment
Привет, Джон, я сейчас за компьютером и только что попробовал твой код, он работает. Он преобразует его в числа. Мне просто нужно сообразить, что с ними теперь делать! Спасибо! - person tmutton; 19.02.2011
comment
Если кому-то это понадобится, я нашел здесь небольшой классный класс code.google.com/p/exifbitmap/source/browse/trunk/Exif_Bitmap/ - есть метод под названием GPSLatitude, который кто-то любезно написал для вычисления десятичного значения. которую я искал! - person tmutton; 19.02.2011

Простой (и быстрый!) Способ - использовать мою библиотеку MetadataExtractor с открытым исходным кодом:

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

var location = gps.GetGeoLocation();

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

Библиотека написана на чистом C # и поддерживает множество форматов изображений и декодирует данные, характерные для многих моделей камер.

Он доступен через NuGet или GitHub.

person Drew Noakes    schedule 24.09.2015
comment
Привет, Дрю - спасибо, что сообщили мне об этом. Я сейчас не над этим работаю, но, возможно, займусь этим в будущем. Я обязательно проверю вашу библиотеку, когда это сделаю. Спасибо! - person tmutton; 28.09.2015
comment
Пожалуйста. Надеюсь, этот ответ поможет и кому-то другому. - person Drew Noakes; 28.09.2015
comment
Я думаю, что так и будет. Проблема требует такой библиотеки серебряной пули. Удачи! - person tmutton; 28.09.2015
comment
Потрясающая библиотека - есть ли способ записать эти данные обратно в изображение? - person link64; 09.11.2017
comment
Нет, сейчас нет. - person Drew Noakes; 09.11.2017

Я наткнулся на это в поисках способа получить данные EXIF ​​GPS в виде набора поплавков. Я адаптировал код Джона Гранта следующим образом ...

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;
}
person Paul    schedule 18.01.2013
comment
другой ответ ниже предлагает использовать двойные вместо float в ExifGpsToFloat - person George Birbilis; 29.03.2014
comment
Вы можете избежать выдачи исключений для изображений, отличных от GPS, используя что-то вроде: если (targetImg.PropertyIdList.Contains (3)) - person pkr2000; 12.06.2016

Вот код работает, я обнаружил некоторые ошибки, работая с float. Надеюсь, это кому-нибудь поможет.

  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;
    }
person Cesar    schedule 18.06.2013
comment
Изображение image = Image.FromFile (@C: \ ... \ Test.jpg); PropertyItem PropertyTagGpsLatitudeRef = image.GetPropertyItem (1); // EnlemRef PropertyItem PropertyTagGpsLatitude = image.GetPropertyItem (2); // Enlem PropertyItem PropertyTagGpsLongitudeRef = image.GetPropertyItem (3); PropertyItem PropertyTagGpsLongitude = image.GetPropertyItem (4); двойная широта = ExifGpsToDouble (PropertyTagGpsLatitudeRef, PropertyTagGpsLatitude); двойная долгота = ExifGpsToDouble (PropertyTagGpsLongitudeRef, PropertyTagGpsLongitude); - person Mehmet Kurt; 30.07.2019

Я знаю, что это старый пост, но я хотел дать ответ, который мне помог.

Я использовал ExifLibrary, расположенную здесь, для записи и чтения метаданных из файлов изображений: https://code.google.com/p/exiflibrary/wiki/ExifLibrary.

Это было просто и удобно. Надеюсь, это поможет кому-то другому.

person Jason Foglia    schedule 08.01.2015

Сначала вам нужно прочитать байты, которые указывают, находятся ли данные EXIF ​​в формате big endian или little endian, чтобы вы не все испортили.

Затем вам нужно сканировать каждый IFD изображения в поисках тега GPSInfo (0x25 0x88), если вы НЕ найдете этот тег внутри какого-либо IFD, это означает, что изображение не имеет информации GPS. Если вы все же найдете этот тег, прочтите 4 байта его значений, что дает вам смещение к другому IFD, GPS IFD, внутри этого IFD вам нужно только получить значения следующих тегов:

0x00 0x02 - Для широты

0x00 0x04 - Для долготы

0x00 0x06 - Для высоты

Каждое из этих значений является беззнаковым рациональным числом.

Здесь вы можете узнать, как сделать почти все: http://www.media.mit.edu/pia/Research/deepview/exif.html.

person Delta    schedule 14.02.2011

Вы пробовали теги 0x0013-16 для http://msdn.microsoft.com/en-us/library/ms534416(v=vs.85).aspx, что также похоже на широту GPS?

Не уверен, что отличает их от тегов с меньшим номером, но попробовать стоит.

Вот их описания:

0x0013 - Символьная строка с завершающим нулем, которая указывает, является ли широта конечной точки северной или южной широтой. N указывает северную широту, а S указывает южную широту.

0x0014 - Широта точки назначения. Широта выражается в виде трех рациональных значений, дающих градусы, минуты и секунды соответственно. При выражении в градусах, минутах и ​​секундах используется формат дд / 1, мм / 1, сс / 1. Когда используются градусы и минуты и, например, доли минут указаны с точностью до двух десятичных знаков, формат будет дд / 1, мммм / 100, 0/1.

0x0015 - Символьная строка с завершающим нулем, которая указывает, является ли долгота конечной точки восточной или западной долготой. E указывает восточную долготу, а W указывает западную долготу.

0x0016 - Долгота пункта назначения. Долгота выражается в виде трех рациональных значений, дающих градусы, минуты и секунды соответственно. Когда выражаются градусы, минуты и секунды, используется формат ддд / 1, мм / 1, сс / 1. Когда используются градусы и минуты и, например, доли минут указаны с точностью до двух десятичных знаков, формат будет ддд / 1, мммм / 100, 0/1.

person tomfanning    schedule 13.02.2011
comment
Привет, Том, спасибо за ответ. Я пробовал их, но они не работали (все еще выходит с тарабарщиной). Возможно, мне придется посмотреть на получение класса EXIF ​​.. - person tmutton; 14.02.2011

Спасибо, код в порядке, но по крайней мере один сбойник,

uint minutes = minutesNumerator / minutesDenominator;

дать неточный результат, когда значение minutesDenominator не равно 1, например, в моем exif = 16,

person Claus    schedule 24.02.2013
comment
это должен быть комментарий - когда у вас будет достаточно репутации :-) - person kleopatra; 24.02.2013

Если вы используете библиотеку ImageMagick, это очень просто.

Предполагая, что вы начинаете с потока изображений:

  1. Преобразовать в объект MagickImage
  2. Получить информацию exif
  3. Получите координаты, используя ответ @Cesar выше

Вот так:

     //1
using (MagickImage image = new MagickImage(photoBlob))
                    {                  
                        var exifData = image.GetExifProfile();
                        
                        if (exifData != null)
                        {
                            //2  
                            var gpsLong = exifData.GetValue(ExifTag.GPSLongitude);
                            var gpsLongRef = exifData.GetValue(ExifTag.GPSLongitudeRef);
                            var gpsLatitude = exifData.GetValue(ExifTag.GPSLatitude);
                            var gpsLatitudeRef = exifData.GetValue(ExifTag.GPSLatitudeRef);
    
                            var longitude = GetCoordinates(gpsLongRef.ToString(), gpsLong.Value);
                            var latitude = GetCoordinates(gpsLatitudeRef.ToString(), gpsLatitude.Value);
                        }
                    }
        
//3     
      private static double GetCoordinates(string gpsRef, Rational[] rationals)
            {
                double degrees = rationals[0].Numerator / rationals[0].Denominator;
                double minutes = rationals[1].Numerator / rationals[1].Denominator;
                double seconds = rationals[2].Numerator / rationals[2].Denominator;
    
                double coordinate = degrees + (minutes / 60d) + (seconds / 3600d);
                if (gpsRef == "S" || gpsRef == "W")
                    coordinate *= -1;                 
               return coordinate;
            }
person iKnowNothing    schedule 23.06.2020