在 C# 中从图像的 EXIF 获取 GPS 数据

发布于 2024-10-17 07:41:52 字数 1709 浏览 8 评论 0原文

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

我现在正在尝试从 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"

这来自 iPhone 4 拍摄的图像,该图像确实携带 GPS EXIF 数据。

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

提前致谢。

I am developing a system that allows for an image to be uploaded to a server using ASP.NET C#. I am processing the image and all is working great. I have managed to find a method that reads the Date Created EXIF data and am parsing it as a DateTime. That works great too.

I am now trying to read GPS data from the EXIF. I am wanting to capture the Latitude and Longitude figures.

I am using this list as a reference to the EXIF data (using the numbers for the property items) http://www.exiv2.org/tags.html

Here is the method to capture the date created (which works).

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

Below is my attempt at doing the same for 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;
}

The value that's coming out and into sdate is "5\0\0\0\0\0\0l\t\0\0d\0\0\0\0\0\0\0\0\0\0"

And that is coming from an image that was taken by an iPhone 4 which does carry the GPS EXIF data.

I know there are classes out there that do this but would prefer to write my own - I am open to suggestions though :-)

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

你在我安 2024-10-24 07:41:53

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

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

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

I know this is an old post, but I wanted to provide an answer that helped me.

I used ExifLibrary located on here to both write and read metadata from image files:
https://code.google.com/p/exiflibrary/wiki/ExifLibrary

This was simple and easy to use. I hope this helps someone else.

挥剑断情 2024-10-24 07:41:53

您首先必须读取指示 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

You first have to read the bytes that indicate whether the EXIF data is in big endian or little endian format so you don't mess up everything.

Then you need to scan each IFD of the image looking for the GPSInfo tag (0x25 0x88), if you do NOT find this tag inside any IFD means that the image doesn't have any GPS info. If you do find this tag, read the 4 bytes of it's values, which gives you an offset to another IFD, the GPS IFD, inside this IFD you only need to retrieve the values of the following tags:

0x00 0x02 - For the latitude

0x00 0x04 - For the longitude

0x00 0x06 - For the altitude

Each of these values are unsigned rationals.

Here you can find how to do almost everything: http://www.media.mit.edu/pia/Research/deepview/exif.html

娜些时光,永不杰束 2024-10-24 07:41:53

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

If you're using the ImageMagick library, this is very straightforward.

Assuming you start off with an image stream:

  1. Convert to an MagickImage object
  2. Get the exif info
  3. Get the coordinates using @Cesar answer above

Like so:

     //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;
            }
誰認得朕 2024-10-24 07:41:53

您是否尝试过 http://msdn.microsoft.com/en-us/library/ms534416(v=vs.85).aspx" rel="nofollow noreferrer">http:// /msdn.microsoft.com/en-us/library/ms534416(v=vs.85).aspx 看起来也像 GPS 纬度?

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

以下是他们的描述:

0x0013 - 以空字符结尾的字符串,指定目标点的纬度是北纬还是南纬。 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。

Have you tried tags 0x0013-16 per http://msdn.microsoft.com/en-us/library/ms534416(v=vs.85).aspx which also looks like GPS latitude?

Not sure what distinguishes these from the lower numbered tags, but worth a try.

Here are their descriptions:

0x0013 - Null-terminated character string that specifies whether the latitude of the destination point is north or south latitude. N specifies north latitude, and S specifies south latitude.

0x0014 - Latitude of the destination point. The latitude is expressed as three rational values giving the degrees, minutes, and seconds respectively. When degrees, minutes, and seconds are expressed, the format is dd/1, mm/1, ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format is dd/1, mmmm/100, 0/1.

0x0015 - Null-terminated character string that specifies whether the longitude of the destination point is east or west longitude. E specifies east longitude, and W specifies west longitude.

0x0016 - Longitude of the destination point. The longitude is expressed as three rational values giving the degrees, minutes, and seconds respectively. When degrees, minutes, and seconds are expressed, the format is ddd/1, mm/1, ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format is ddd/1, mmmm/100, 0/1.

春花秋月 2024-10-24 07:41:53

谢谢,代码很好,但至少有一个失败,

uint 分钟=分钟分子/分钟分母;

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

Thanks, the code is fine but at least one failor,

uint minutes = minutesNumerator / minutesDenominator;

give not an exact result, when the minutesDenominator is not equal 1, in example in my exif=16,

恋竹姑娘 2024-10-24 07:41:52

一种简单(且快速!)的方法是使用我的开源 MetadataExtractor 库:

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

var location = gps.GetGeoLocation();

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

该库是用纯 C# 编写的,支持多种图像格式并解码特定于多种相机型号的数据。

它可以通过 NuGetGitHub

A simple (and fast!) way is to use my open source MetadataExtractor library:

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

var location = gps.GetGeoLocation();

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

The library is written in pure C# and supports many image formats and decodes data specific to many camera models.

It's available via NuGet or GitHub.

荒岛晴空 2024-10-24 07:41:52

根据 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。当使用度和分钟时,例如,分钟的小数部分保留两位小数,格式为 dd/1、mmmm/100、0/1。

According to the link posted above by tomfanning, property item 0x0002 is the latitude expressed as a PropertyTagTypeRational. The rational type is defined as...

Specifies that the value data member is an array of pairs of unsigned long integers. Each pair represents a fraction; the first integer is the numerator and the second integer is the denominator.

You are trying to parse it as a string when it's actually just a series of bytes. According to the above, there should be 3 pairs of 32-bit unsigned integers packed into that byte array, which you can retrieve using the following:

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

What you do with these values after you've got them is for you to work out :) Here's what the docs say:

Latitude is expressed as three rational values giving the degrees, minutes, and seconds respectively. When degrees, minutes, and seconds are expressed, the format is dd/1, mm/1, ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two decimal places, the format is dd/1, mmmm/100, 0/1.

困倦 2024-10-24 07:41:52

我遇到了这个问题,寻找一种将 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;
}

I ran across this looking for a way to get the EXIF GPS data as a set of floats. I've adapted the code from Jon Grant as follows...

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;
}
无需解释 2024-10-24 07:41:52

这是正在运行的代码,我发现使用浮动时出现一些错误。
我希望这对某人有帮助。

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

Here is the code working, I found some errors working with float.
I hope this help somebody.

  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;
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文