Получаване на 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
Уверихте ли се, че изображението е геомаркирано, като прочетете изображението в друг софтуер? Геомаркирането (вграждане на EXIF ​​данни) може да бъде деактивирано (което е логично) в iPhone. И така, 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);

Какво ще правите с тези стойности, след като ги получите, трябва да изработите :) Ето какво казват документите:

Географската ширина се изразява като три рационални стойности, даващи съответно градуси, минути и секунди. Когато са изразени градуси, минути и секунди, форматът е dd/1, mm/1, ss/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.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 - Географска ширина на крайната точка. Географската ширина се изразява като три рационални стойности, даващи съответно градуси, минути и секунди. Когато са изразени градуси, минути и секунди, форматът е dd/1, mm/1, ss/1. Когато се използват градуси и минути и например части от минути са дадени до два знака след десетичната запетая, форматът е дд/1, мммм/100, 0/1.

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

0x0016 - Географска дължина на крайната точка. Географската дължина се изразява като три рационални стойности, даващи съответно градуси, минути и секунди. Когато са изразени градуси, минути и секунди, форматът е ddd/1, mm/1, ss/1. Когато се използват градуси и минути и например части от минути са дадени до два знака след десетичната запетая, форматът е ddd/1, mmmm/100, 0/1.

person tomfanning    schedule 13.02.2011
comment
Здравей Том, благодаря ти за отговора. Опитах тези, но те не работят (все още излиза с глупости). Може да се наложи да потърся получаване на EXIF ​​клас.. - person tmutton; 14.02.2011

Благодаря, кодът е наред, но поне един неуспешен,

uint минути = Числител на минути / Знаменател на минути;

не дава точен резултат, когато минутите Denominator не са равни на 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