web-dev-qa-db-ja.com

C#で画像のEXIFからGPSデータを取得する

ASP.NET C#を使用してサーバーに画像をアップロードできるシステムを開発しています。画像を処理していますが、すべて正常に機能しています。作成日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によって撮られた画像から来ています。

私はこれを行うクラスがそこにあることを知っていますが、私自身のクラスを書くことを好むでしょう-私は提案にもオープンです:-)

前もって感謝します。

28
tmutton

Tomfanningによる 上記のリンク によると、プロパティアイテム0x0002はPropertyTagTypeRationalで表される緯度です。有理型は として定義 ...

値のデータメンバーが、符号なし長整数のペアの配列であることを指定します。各ペアは分数を表します。最初の整数は分子で、2番目の整数は分母です。

実際には単なる一連のバイトである場合、それを文字列として解析しようとしています。上記によると、32バイトの符号なし整数の3つのペアがそのバイト配列にパックされているはずです。これは、以下を使用して取得できます。

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

これらの値を取得した後、これらの値を使って何をするかを考えます:)これはドキュメントが言うことです:

緯度は、度、分、秒をそれぞれ表す3つの有理数として表されます。度、分、秒を表す場合、形式はdd/1、mm/1、ss/1です。度と分が使用され、たとえば、分数の小数部が小数点以下2桁まで与えられる場合、形式はdd/1、mmmm/100、0/1です。

24
Jon Grant

簡単な(そして高速です!)方法は、私のオープンソース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 から利用できます。

17
Drew Noakes

私はフロートのセットとして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;
}
12
Paul

これがコードの動作ですが、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;
    }
11
Cesar

私はこれが古い投稿であることを知っていますが、私に役立つ答えを提供したいと思いました。

ここにあるExifLibraryを使用して、画像ファイルのメタデータの書き込みと読み取りの両方を行いました: https://code.google.com/p/exiflibrary/wiki/ExifLibrary

これはシンプルで使いやすかったです。これが他の誰かの役に立つことを願っています。

5
Jason Foglia

最初に、EXIFデータがビッグエンディアン形式かリトルエンディアン形式かを示すバイトを読み取って、すべてを台無しにしないようにする必要があります。

次に、画像の各IFDをスキャンしてGPSInfoタグ(0x25 0x88)を探す必要があります。IFG内でこのタグが見つからない場合は、画像にGPS情報がないことを意味します。このタグが見つかった場合は、その値の4バイトを読み取ると、別のIFD、つまりGPS IFDへのオフセットが得られます。このIFD内では、次のタグの値を取得するだけで済みます。

0x00 0x02-緯度用

0x00 0x04-経度

0x00 0x06-標高について

これらの各値は、符号なしの有理数です。

ここではほとんどすべてを行う方法を見つけることができます: http://www.media.mit.edu/pia/Research/deepview/exif.html

3
Delta

http://msdn.Microsoft.com/en-us/library/ms534416(v = vs.85).aspx ごとにタグ0x0013-16を試しましたか?これもGPS緯度のように見えますか?

これらと低い番号のタグとの違いはわかりませんが、試してみる価値はあります。

以下がその説明です。

0x0013-宛先ポイントの緯度が北または南の緯度であるかどうかを指定するヌル終了文字ストリング。 Nは北緯を指定し、Sは南緯を指定します。

0x0014-目的地点の緯度。緯度は、度、分、秒をそれぞれ表す3つの有理数として表されます。度、分、秒を表す場合、形式はdd/1、mm/1、ss/1です。度と分が使用され、たとえば、分数の小数部が小数点以下2桁まで与えられる場合、形式はdd/1、mmmm/100、0/1です。

0x0015-宛先ポイントの経度が東経または西経のどちらであるかを指定するヌル終了文字ストリング。 Eは東経を指定し、Wは西経を指定します。

0x0016-目的地の経度。経度は、度、分、秒をそれぞれ表す3つの有理数として表されます。度、分、秒を表す場合、形式はddd/1、mm/1、ss/1です。度と分が使用され、たとえば、分数の小数部が小数点以下2桁まで与えられる場合、形式はddd/1、mmmm/100、0/1です。

2
tomfanning

おかげで、コードは問題ありませんが、少なくとも1つの失敗、

int minutes = minutesNumerator/minutesDenominator;

例えば、exif = 16の場合、mintsDenominatorが1でない場合、正確な結果は得られません。

1
Claus