Python で写真から位置情報を抜き出し、市区町村ごとに分ける

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、私は昔からいろんな「珍スポット」に行くのが好きなのですが、そうなるとやはり写真をバンバン撮ることになります。

それ自体はいいことなのですが、やはり何年も写真を取り続けると次のような問題がやってきます。

「あの場所の写真、どこにあるんだっけ…💦」

そうです。
写真の数が多すぎて「手作業で探すのはムリあるな、、、じゃ、もういいっか」となってしまうんですね。

そこで❗

折角プログラムのお仕事をしてるので、今回はGoogle Cloudを使って「写真を市区町村別で振り分ける」という実装をしてみることにしました。

ぜひ何かの参考になりましたら嬉しいです😄✨

「やっとドラクエっぽい
景色を見られました 👇
(拡大できます)」



実行環境: Python 3

実装する方法

今回は、以下の手順で市区町村ごとに「フォルダ振り分け」を実装します。

  1. 写真から位置情報(GPS)を取得
  2. その位置情報を使って、Google Cloud から住所を取得(ジオコーディング)
  3. 住所から写真を各フォルダに振り分ける

ちなみにGoogle Cloudの「ジオコーディング API」は、総合的にマップ API の料金として換算されるのですが、毎月 $200 分は無料になるので、単純計算で40,000回までは無料で使えるということになります。(ただし、他のAPIとも合算になるので注意してください)

※ また、(もちろんですが)写真にGPSデータが埋め込まれていない場合はうまくいきません。ご自身のスマホで設定を確認してから実行してください。

では今回も楽しんでやってみましょう❗

パッケージをインストールする

では、実際の作業に入る前に必要になるパッケージをインストールしておきましょう。

以下のコマンドを実行してください。

pip3 install exif

これで写真からGPSデータを抜き出すことができるようになりました。

Google Cloud で Geocoding API を有効にする

では、まずは「GPSデータ → 住所」へ変換するための「Geocoding API」を有効にします。

Google Cloud へログインし、コンソール画面上部にあるAPI検索ボックスに「geocoding」と入力すると、候補に「Geocoding API」が表示されますので、クリックしてください。

(⚠ご注意: もしGoogle Cloudにプロジェクトを作っていない場合は適当にプロジェクトを作っておいてください。詳しくは こちら

すると、以下のような表示になりますので、「有効にする」ボタンをクリックします。

これで、Google CloudGeocoding APIが使えるようになりました。

API アクセスキーを取得する

次にPythonからGoogle Cloudにアクセスするための「API キー」を取得します。

以下の手順でページ移動してください。

  1. 画面左上のハンバーガーボタン
  2. API とサービス
  3. 認証情報

すると、認証情報のページに移動するので、同じく以下の手順でクリックしてください。

  1. 認証情報を作成
  2. API キー

クリックすると以下のように自動で「API キー」を作成してくれます。
後で使うのでメモしておいてください。

※ なお、このAPIキーは無制限にAPIへアクセスすることができます。そのため、もし本番環境で使う場合は「このAPIだけ OK にする」といった制限をかけておくことをおすすめします👍

Python のコードをつくる

では、本題のPythonプログラミングです。
まずは実際のコードです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
from exif import Image
import os
import glob
import json
import pathlib
import shutil

# 定数
GCP_API_KEY = '(ここにGoogle Cloud の APIキー)'
PHOTO_DIR = './photos'
SAVE_DIR = './classified_photos'

# 関数(ref -> N:北緯|S:南緯|W:西経|E:東経)
def convert_DMS_to_DD(DMS, ref):
    dd = DMS[0] + DMS[1]/60 + DMS[2]/(60*60)
    if ref == 'S' or ref == 'W':
        dd = dd * -1
    return dd

def get_coordinates(image):
    latitude = None
    longitude = None
    if hasattr(image, 'gps_latitude_ref') and hasattr(image, 'gps_latitude'): # 緯度
        latitude_ref = image.gps_latitude_ref # N or S(北緯 or 南緯)
        latitude = convert_DMS_to_DD(image.gps_latitude, latitude_ref)
    if hasattr(image, 'gps_longitude_ref') and hasattr(image, 'gps_longitude'): # 経度
        longitude_ref = image.gps_longitude_ref # W or E(西経 or 東経)
        longitude = convert_DMS_to_DD(image.gps_longitude, longitude_ref)
    return latitude,longitude

def get_location_from_gcp(latitude, longitude): # Geocoding API から「都道府県」と「市区町村」を取得
    prefecture = ''
    city = ''
    url = 'https://maps.googleapis.com/maps/api/geocode/json?latlng='+ str(latitude) +'%2C'+ str(longitude) +'&key='+ GCP_API_KEY +'&language=ja'
    response = requests.request('GET', url)
    if response.status_code == 200:
        addresses = json.loads(response.text)
        results = addresses['results']
        for result in results:
            address_components = result['address_components']
            for address_component in address_components:
                types = address_component['types']
                if 'locality' in types or 'administrative_area_level_2' in types:
                    city = address_component['long_name']
                elif 'administrative_area_level_1' in types:
                    prefecture = address_component['long_name']
    return prefecture, city

def save_classifed_photo(path, prefecture, city):
    if city == '':
        city = '不明'
    saving_prefecture_dir = SAVE_DIR +'/'+ prefecture
    saving_city_dir = saving_prefecture_dir +'/'+ city
    pathlib.Path(saving_prefecture_dir).mkdir(parents=True, exist_ok=True) 
    pathlib.Path(saving_city_dir).mkdir(parents=True, exist_ok=True)
    filename = os.path.basename(path)
    saving_path = saving_city_dir +'/'+ filename
    shutil.copyfile(path, saving_path)
    print('Saved: '+ filename)

# フォルダから全ファイル名を取得
photo_paths = glob.glob(PHOTO_DIR +'/*')

for photo_path in photo_paths:
    with open(photo_path, 'rb') as f:
        image = Image(f)
        if image.has_exif == True:
            latitude,longitude = get_coordinates(image)
            if latitude != None and longitude != None:
                prefecture,city = get_location_from_gcp(latitude, longitude) # 都道府県 & 市区町村 を取得
                if prefecture != '':
                    save_classifed_photo(photo_path, prefecture, city)
                else:
                    print('都道府県&市区町村が取得できません')
            else:
                print('位置データが取得できません')
        else:
            print('Exif データが取得できません')

少しコードが複雑な部分もあるのでひとつずつご紹介していきます。

定数

この中に先ほどGoogle Cloudで取得した「APIキー」をセットしてください。

convert_DMS_to_DD()

この関数は、座標のフォーマット変換をしています。
シンプルに言うと、「度・分・秒」形式 → 十進数(十進角と言うらしいです)に変換しています。

これは、Geocoding APIの形式に合わせるためです。

なお、日本に住んでいるとあまり関係ありませんが、「西経」もしくは「南緯」の位置データの場合は数値がマイナスになるようになっています。

get_coordinates()

ここでは、画像から取得したExifデータの中から必要なものだけ抜き出し、convert_DMS_to_DD()へ送信し、最終的に十進数の位置情報を取得しています。

get_location_from_gcp()

ここが一番今回のメインになる部分で、Geocoding APIを使って住所(都道府県や市区町村、通りの名前など)を取得しています。

なお、今回は以下の条件で都道府県か市区町村かを判別しています。

  • types に 「locality」もしくは「administrative_area_level_2」が入っている 👉 市区町村
  • types に「administrative_area_level_1」が入っている 👉 都道府県

※ たくさん実行してないので、例外が出てくるかもですが・・・あしからず😅

save_classifed_photo()

最後に判別した写真ファイルを各フォルダに保存しています。

なお、もし市区町村だけが取得出来ていない場合は「都道府県 > 不明」フォルダに保存されるようにしています。

テストしてみる

では、実際にテストしてみましょう❗

まずは、photosフォルダに以下7つの写真を設置します。(私が旅した各地の写真です👍)

そして、振り分け先のフォルダclassified_photosが空であることを確認します。

では、準備は完了しました。
以下のコマンドを実行してみましょう。GO❗

python3 classify_photos.py

すると・・・・・・

はい❗
「保存しました」メッセージが7つ表示されました。

では、実際のフォルダはどうなったでしょうか・・・・・・

想定通り都道府県のフォルダが出来ています。
では、この中から「いつか住んでみたい」と思うほど気に入っている岡山県のフォルダを見てみましょう。

どうでしょうか・・・・・・?

はい❗
うまく市区町村フォルダに分類されています。

では、最近行った瀬戸内市の中身を見てみましょう。

写真は保存されているでしょうか・・・・・・???

はい❗
きちんと瀬戸内市内で撮影した写真が保存されていました。

全て成功です😄✨

企業様へのご提案

今回のように写真に埋め込まれている位置情報を使えば、市区町村ごとに分類するだけでなく、それらを地図上で表示させることもできます。

また、位置情報だけでなく撮影された日時や高度などその他の情報も取得することができます。

そのため、もし御社が移動を多くされる業務をお持ちでしたら、業務効率化のお力になれるかと思います。

もしこういった機能をご希望でしたらお問い合わせよりお気軽にご連絡ください。お待ちしております😄✨

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はPythonを使って写真のGPS位置情報から市区町村ごとのフォルダに分類してみました。

このようにGoogle Cloudだけでなく、有名なAWS(Amazon Web Service)を使うことで個人的でもいろいろ実現できるので、日曜大工ならぬ「日曜プログラマ」として楽しんでいただけると嬉しいです。(しかも無料枠があるのでうまくすればお金はかかりません😉✨)

ということで、最近は「自分でなんでも開発する」から「あるものを組み合わせる」パターンも大事なんだなと感じています。

ぜひ皆さんも趣味と実益を兼ねてチャレンジしてみてくださいね。

ではでは〜❗

「日本一短い
『ぶつぶつ川』を見に行きたい❗
たった 13.5 m 😂」

このエントリーをはてなブックマークに追加       follow us in feedly