【问题标题】:Getting GPS data from an image's EXIF in C#在 C# 中从图像的 EXIF 中获取 GPS 数据
【发布时间】:2020-06-24 01:37:01
【问题描述】:

我正在开发一个允许使用 ASP.NET C# 将图像上传到服务器的系统。我正在处理图像,一切都很好。我设法找到一种方法来读取 Date Created EXIF 数据并将其解析为 DateTime。这也很好用。

我现在正尝试从 EXIF 读取 GPS 数据。我想捕捉纬度和经度的数字。

我将此列表用作对 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"

这是来自带有 GPS EXIF 数据的 iPhone 4 拍摄的图像。

我知道有一些课程可以做到这一点,但我更愿意自己编写 - 不过我愿意接受建议 :-)

提前致谢。

【问题讨论】:

  • 您是否通过在其他软件中读取图像来确保该图像被地理标记?地理标记(嵌入 EXIF 数据)可以在 iPhone 中禁用(这是合乎逻辑的)。那么,您是否 100% 确定在拍摄图像时启用了该功能?而且,如果启用,您确定手机在拍摄照片时位于某个位置(而不是某个房间内)吗?手机必须从卫星接收信号。
  • 嗨 Sarwar,我刚刚在上传前检查了图像,它确实包含 GPS 信息。我查看了文件的属性详细信息,它们在那里,纬度、经度和高度。
  • 如果您决定放弃,或者想查看示例源代码。 MetaDataSpr 对我很有帮助。

标签: c# gps latitude-longitude exif


【解决方案1】:

根据 tomfanning 的link posted above,属性项 0x0002 是表示为PropertyTagTypeRational 的纬度。有理类型是defined as...

指定值数据成员是一个无符号长整数对数组。每对代表一个分数;第一个整数是分子,第二个整数是分母。

当它实际上只是一系列字节时,您试图将其解析为字符串。根据上述内容,该字节数组中应该包含 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。当使用度数和分钟时,例如,分钟的小数部分最多保留两位小数,格式为 dd/1、mmmm/100、0/1。

【讨论】:

  • 嗨,乔恩,感谢您提供的信息。我目前不在电脑前,但我会在接下来的几天内使用源文件 - 希望我可以尝试一下,并成功回复您!
  • 嗨乔恩,我现在在电脑前,刚刚尝试了你的代码,它可以工作。它将其转换为数字。我现在只需要弄清楚如何处理它们!谢谢!
  • 以防万一有人需要它,我在这里找到了一个不错的小类code.google.com/p/exifbitmap/source/browse/trunk/Exif_Bitmap/… -- 有一个名为 GPSLatitude 的方法,有人已经编写了它来计算我正在寻找的十进制值!
【解决方案2】:

一种简单(而且快速!)的方法是使用我的开源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 获得。

【讨论】:

  • 嗨,德鲁 - 感谢您告知我这件事。这不是我目前正在做的事情,但将来可能会做。当我这样做时,我一定会检查你的库。谢谢!
  • 不客气。希望这个答案也能帮助其他人。
  • 我想会的。问题需要像这样的灵丹妙药库。祝你好运!
  • 很棒的库 - 有什么方法可以将此数据写回图像?
  • 暂时没有,没有。
【解决方案3】:

我遇到了这个,寻找一种将 EXIF GPS 数据作为一组浮点数的方法。我已将 Jon Grant 的代码改编如下...

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

【讨论】:

  • 下面的其他答案建议在 ExifGpsToFloat 中使用双精度而不是浮点数
  • 你可以避免使用类似的东西抛出非 GPS 图像的异常; if (targetImg.PropertyIdList.Contains(3))
【解决方案4】:

这是代码,我发现使用 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;
    }

【讨论】:

  • 图像 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);
【解决方案5】:

我知道这是一篇旧帖子,但我想提供一个对我有帮助的答案。

我使用位于此处的 ExifLibrary 从图像文件中写入和读取元数据: https://code.google.com/p/exiflibrary/wiki/ExifLibrary

这简单易用。我希望这对其他人有帮助。

【讨论】:

    【解决方案6】:

    您首先必须读取指示 EXIF 数据是大端还是小端格式的字节,这样您就不会搞砸一切。

    然后您需要扫描图像的每个 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

    【讨论】:

      【解决方案7】:

      您是否尝试过标记 0x0013-16 per http://msdn.microsoft.com/en-us/library/ms534416(v=vs.85).aspx,看起来也像 GPS 纬度?

      不确定这些与编号较低的标签有何区别,但值得一试。

      以下是他们的描述:

      0x0013 - 以 Null 结尾的字符串,指定目标点的纬度是北纬还是南纬。 N为北纬,S为南纬。

      0x0014 - 目标点的纬度。纬度表示为三个有理值,分别给出度、分和秒。度、分、秒表示时,格式为dd/1、mm/1、ss/1。当使用度数和分钟时,例如,分钟的小数部分最多保留两位小数,格式为 dd/1、mmmm/100、0/1。

      0x0015 - 以空字符结尾的字符串,指定目标点的经度是东经还是西经。 E 为东经,W 为西经。

      0x0016 - 目标点的经度。经度表示为三个有理值,分别给出度、分和秒。度、分、秒表示时,格式为ddd/1、mm/1、ss/1。当使用度数和分钟时,例如,分钟的小数部分最多保留两位小数,格式为 ddd/1、mmmm/100、0/1。

      【讨论】:

      • 嗨,汤姆,感谢您的回复。我尝试了这些,但它们没有用(仍然出现乱码)。我可能不得不考虑获得一个 EXIF 类..
      【解决方案8】:

      谢谢,代码没问题,但至少有一个失败者,

      uint minutes = minutesNumerator / minutesDenominator;

      当分钟分母不等于 1 时,例如在我的 exif=16 中,给出不准确的结果,

      【讨论】:

      • 这应该是一个评论 - 一旦你有足够的代表:-)
      【解决方案9】:

      如果您使用的是 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;
                  }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-01
        • 1970-01-01
        • 2021-08-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多