SE_BOKUのまとめノート的ブログ

SE_BOKUが知ってること・勉強したこと・考えたことetc

Python3で「Exif情報の参照・削除」と「GPSから経度・緯度を計算」/Pythonサンプル

f:id:arakan_no_boku:20200707230113p:plain

目次

Pythonで写真データからExif情報を参照・削除

 Exifは、Exchangeable image file format(エクスチェンジャブル・イメージ・ファイル・フォーマット)の略で、「エグジフ」もしくは「イグジフ」と読みます。

写真用のメタデータを含む画像ファイルフォーマットのことで、JPEGTIFFJPEG XR(HD Photo)などの画像形式が対応しています。

 

Exif情報をPythonで取り出すサンプル 

PythonのPILライブラリを使うと、Exif情報をとりだせます。

でも、JPEG形式の写真なら必ずExif情報が埋め込まれているわけでもなく、かつ、ファイルによって何が埋め込まれているかもバラバラです。

とりあえず、JPEG画像に埋め込まれているExif情報を一覧するクラスです。

from PIL import Image
import PIL.ExifTags as ExifTags


class ExifImage:

    def __init__(self, fname):
        # 画像ファイルを開く --- (*1)
        self.img = Image.open(fname)
        self.exif = {}
        if self.img._getexif():
            for k, v in self.img._getexif().items():
                if k in ExifTags.TAGS:
                    self.exif[ExifTags.TAGS[k]] = v
 
    def print(self):
        if self.exif:
            for k, v in self.exif.items():
                print(k, ":", v)
        else:
            print("exif情報は記録されていません。")

適当なJPEG画像をで試してみます。

 

作ったサンプルのお試しイメージ

Pythonのソースと同じフォルダに、test.jpgの名称で保存して実行します。

a = ExifImage("test.jpg")
a.print()

まずは、インターネットから適当なJPEG画像を保存してやってみます。

上記を実行すると。

exif情報は記録されていません。

 でした。

どうやら、exif情報を記録しないフォーマットになっているようです。

ということで。

以前、使った自分のスマホでとった情報をみてみます。

f:id:arakan_no_boku:20200722223002p:plain

結果はこんな感じです。

ExifVersion : b'0220'
ShutterSpeedValue : (4322, 1000)
DateTimeOriginal : 2019:07:27 14:54:24
DateTimeDigitized : 2019:07:27 14:54:24
ApertureValue : (200, 100)
BrightnessValue : (0, 100)
MeteringMode : 2
FlashPixVersion : b'0100'
Flash : 0
FocalLength : (3570, 1000)
ColorSpace : 1
ExifImageWidth : 224
ExifInteroperabilityOffset : 746
FocalLengthIn35mmFilm : 0
SceneCaptureType : 0
SubsecTime : 033
SubsecTimeOriginal : 033
SubsecTimeDigitized : 033
ExifImageHeight : 224
ImageLength : 4160
Make : SG
Model : S1
SensingMethod : 0
Orientation : 1
YCbCrPositioning : 1
ExposureTime : (49995372, 1000000000)
XResolution : (72, 1)
YResolution : (72, 1)
FNumber : (200, 100)
SceneType : b'\x00'
ExposureProgram : 0
ISOSpeedRatings : 382
ResolutionUnit : 2
ExposureMode : 0
ImageWidth : 3120
WhiteBalance : 0
Software : Ralpha,9.0010.30, NORMAL 0, V010.0080.01, V001.0020.07
DateTime : 2019:07:27 14:54:24
ExifOffset : 254

 

主なExif情報TAGの日本語訳 

一覧にしてみました。

Exifタグ 意味
ApertureValue 絞り
BrightnessValue 輝度
ColorSpace 色空間情報
ExposureBiasValue 露出補正値
ExposureMode 露出モード
ExposureProgram 露出プログラム
ExposureTime 露出時間
Flash フラッシュ有無
FlashPixVersion 対応フラッシュピックスバージョン
FNumber F値
FocalLength 焦点距離
FocalLengthIn35mmFilm 35mm 換算焦点距離
ImageLength 画像サイズ
ImageWidth 画像幅
SensingMethod センサー方式
ShutterSpeedValue シャッタースピード
WhiteBalance ホワイトバランス

 

Exif情報をPythonで削除 

今更ながら、位置情報とかを含むExIf情報を確認する方法だけ紹介していたのも片手落ちだなと思うので、PythonExIf情報を削除する方法だけ、さらっと載せておきます。

from PIL import Image

img = Image.open('test.jpg')
data = img.getdata()
mode = img.mode
size = img.size

new_img = Image.new(mode, size)
new_img.putdata(data)
new_img.save('test_noexif.jpg')

PILを使って、画像を開き、画像本体のmodeとsizeとdataを取得し、それを使って新規作成したファイルに書き出す・・というだけです。

これでExIf情報はきれいになくなります。

 

Exif情報のGPS情報から経度・緯度を計算 

スマホGPS情報の埋め込みが許可されているなど、いろいろ条件がそろう必要がありますが、exifGPS情報が埋め込まれている場合があります。

 

経度・緯度の計算

GPS情報は、GPSInfoというタグで取得できます。

ただ、GPS情報には分数形式で納められているので、Googleマップの検索条件に使える「経度(latitude)・緯度(longitude)」の形式にするには、ちょっと計算してやる必要があります。

そのあたりの処理を追加して、さきほどのクラスを拡張してみました。

from PIL import Image
import PIL.ExifTags as ExifTags


class ExifImage:

    def __init__(self, fname):
        # 画像ファイルを開く --- (*1)
        self.img = Image.open(fname)
        self.exif = {}
        if self.img._getexif():
            for k, v in self.img._getexif().items():
                if k in ExifTags.TAGS:
                    self.exif[ExifTags.TAGS[k]] = v
 
    def print(self):
        if self.exif:
            for k, v in self.exif.items():
                print(k, ":", v)
        else:
            print("exif情報は記録されていません。")

    def __conv_deg(self, v):
        # 分数を度に変換
        d = float(v[0][0]) / float(v[0][1])
        m = float(v[1][0]) / float(v[1][1])
        s = float(v[2][0]) / float(v[2][1])
        return d + (m / 60.0) + (s / 3600.0)

    def get_gps(self):
        if "GPSInfo" in self.exif:
            gps_tags = self.exif["GPSInfo"]
        else:
            gps_tags = {}
        gps = {}
        if gps_tags:
            for t in gps_tags:
                gps[ExifTags.GPSTAGS.get(t, t)] = gps_tags[t]
            latitude = self.__conv_deg(gps["GPSLatitude"])
            lat_ref = gps["GPSLatitudeRef"]
            if lat_ref != "N":
                latitude = 0 - latitude
            longitude = self.__conv_deg(gps["GPSLongitude"])
            lon_ref = gps["GPSLongitudeRef"]
            if lon_ref != "E":
                longitude = 0 - longitude
            return '{:.06f}'.format(latitude) + "," + '{:.06f}'.format(longitude)
        else:
            return "経度・緯度は記録されていません。"

ちょうど、東京の京成立石駅のあたりで、とった写真があったので、それをtest.jpgとして保存し実行してみました。 

a = ExifImage("test.jpg")
print(a.get_gps())

実行した結果で出力されたのは  

35.738226, 139.848413 

これをGoogleマップの検索欄にコピーして検索してみます。

f:id:arakan_no_boku:20200723004334p:plain

おお。

ばっちり正解です。

まさしく撮影した場所のあたりにバルーンがたってます。

スマホで撮った写真をへたに共有すると、撮影場所までばれてしまうのですね。

まあ、撮影した風景をどこだったか忘れた場合とかは助かるでしょうが、注意が必要だなと、つくづく思います。 

今回はこんなところで。

ではでは。