九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、私は昔からいろんな「珍スポット」に行くのが好きなのですが、そうなるとやはり写真をバンバン撮ることになります。
それ自体はいいことなのですが、やはり何年も写真を取り続けると次のような問題がやってきます。
「あの場所の写真、どこにあるんだっけ…💦」
そうです。
写真の数が多すぎて「手作業で探すのはムリあるな、、、じゃ、もういいっか」となってしまうんですね。
そこで❗
折角プログラムのお仕事をしてるので、今回はGoogle Cloud
を使って「写真を市区町村別で振り分ける」という実装をしてみることにしました。
ぜひ何かの参考になりましたら嬉しいです😄✨
「やっとドラクエっぽい
景色を見られました 👇
(拡大できます)」
実行環境: Python 3
目次
実装する方法
今回は、以下の手順で市区町村ごとに「フォルダ振り分け」を実装します。
- 写真から位置情報(GPS)を取得
- その位置情報を使って、Google Cloud から住所を取得(ジオコーディング)
- 住所から写真を各フォルダに振り分ける
ちなみに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 Cloud
でGeocoding API
が使えるようになりました。
API アクセスキーを取得する
次にPython
からGoogle Cloud
にアクセスするための「API キー」を取得します。
以下の手順でページ移動してください。
- 画面左上のハンバーガーボタン
- API とサービス
- 認証情報
すると、認証情報のページに移動するので、同じく以下の手順でクリックしてください。
- 認証情報を作成
- 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つ表示されました。
では、実際のフォルダはどうなったでしょうか・・・・・・
想定通り都道府県のフォルダが出来ています。
では、この中から「いつか住んでみたい」と思うほど気に入っている岡山県のフォルダを見てみましょう。
どうでしょうか・・・・・・?
はい❗
うまく市区町村フォルダに分類されています。
では、最近行った瀬戸内市の中身を見てみましょう。
写真は保存されているでしょうか・・・・・・???
はい❗
きちんと瀬戸内市内で撮影した写真が保存されていました。
全て成功です😄✨
企業様へのご提案
今回のように写真に埋め込まれている位置情報を使えば、市区町村ごとに分類するだけでなく、それらを地図上で表示させることもできます。
また、位置情報だけでなく撮影された日時や高度などその他の情報も取得することができます。
そのため、もし御社が移動を多くされる業務をお持ちでしたら、業務効率化のお力になれるかと思います。
もしこういった機能をご希望でしたらお問い合わせよりお気軽にご連絡ください。お待ちしております😄✨
おわりに
ということで、今回はPython
を使って写真のGPS位置情報から市区町村ごとのフォルダに分類してみました。
このようにGoogle Cloud
だけでなく、有名なAWS
(Amazon Web Service)を使うことで個人的でもいろいろ実現できるので、日曜大工ならぬ「日曜プログラマ」として楽しんでいただけると嬉しいです。(しかも無料枠があるのでうまくすればお金はかかりません😉✨)
ということで、最近は「自分でなんでも開発する」から「あるものを組み合わせる」パターンも大事なんだなと感じています。
ぜひ皆さんも趣味と実益を兼ねてチャレンジしてみてくださいね。
ではでは〜❗
「日本一短い
『ぶつぶつ川』を見に行きたい❗
たった 13.5 m 😂」