python获取原图GPS位置信息,轻松得到你的活动轨迹
一、图像EXIF信息
介绍
EXIF(Exchangeable image file format,可交换图像文件格式)是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据,如拍摄时间、图像分辨率、感光值、GPS坐标等。
Exif最初由日本电子工业发展协会在1996年制定,版本为1.0。1998年,升级到2.1,增加了对音频文件的支持。2002年3月,发表了2.2版。
Exif可以附加于JPEG、TIFF、RIFF等文件之中,为其增加有关数码相机拍摄信息的内容和索引图或图像处理软件的版本信息。
Exif信息是可以被任意编辑的,因此只有参考的功能。Exif信息以0xFFE1作为开头标记,后两个字节表示Exif信息的长度。所以Exif信息最大为64 kb,而内部采用TIFF格式。
查看EXIF信息
windows文件属性
- 注:本文处理图片针对原图
Windows7以上操作系统具备对Exif的原生支持,Windows系统下,可以通过鼠标右键点击图片打开菜单,点击属性并切换到详细信息标签下,即可直接获取图片的EXIF信息。
在线查看器
- 图虫EXIF查看器
https://exif.tuchong.com/
- 我爱斗图(一个表情包网站😂)
https://www.52doutu.cn/tools/exif
exifread 库
exifread模块为python读取图片EXIF信息的库。
exifread模块的下载地址:https://pypi.python.org/pypi/ExifRead
安装exifread库
pip install exifread
主要使用process_file函数进行解析,传入图片文件对象,返回一个包含图片信息的字典。其中,exif中GPS格式为DMS格式,即:D(degree,度)、M(minute,分)、S(second,秒),因此要进行转换才能得到常见的double类型的经纬度值。下面就用python + exifread读取图片的详细信息。
import exifread
with open('IMG_20190618_163339.jpg', 'rb') as f:
exif_dict = exifread.process_file(f)
print('拍摄时间:', exif_dict['EXIF DateTimeOriginal'])
print('照相机制造商:', exif_dict['Image Make'])
print('照相机型号:', exif_dict['Image Model'])
print('照片尺寸:', exif_dict['EXIF ExifImageWidth'], exif_dict['EXIF ExifImageLength'])
# 经度
lon_ref = exif_dict["GPS GPSLongitudeRef"].printable
lon = exif_dict["GPS GPSLongitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(",")
lon = float(lon[0]) + float(lon[1]) / 60 + float(lon[2]) / float(lon[3]) / 3600
if lon_ref != "E":
lon = lon * (-1)
# 纬度
lat_ref = exif_dict["GPS GPSLatitudeRef"].printable
lat = exif_dict["GPS GPSLatitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(",")
lat = float(lat[0]) + float(lat[1]) / 60 + float(lat[2]) / float(lat[3]) / 3600
if lat_ref != "N":
lat = lat * (-1)
print('照片的经纬度:', (lat, lon))
for key in exif_dict:
print("%s: %s" % (key, exif_dict[key]))
输出:
拍摄时间: 2019:06:18 16:33:40
照相机制造商: HUAWEI
照相机型号: HRY-AL00Ta
照片尺寸: 3968 2976
照片的经纬度: (13.787098884444445, 100.62936401361111)
Image ImageWidth: 3968
Image ImageLength: 2976
Image BitsPerSample: [8, 8, 8]
Image Make: HUAWEI
Image Model: HRY-AL00Ta
Image Orientation: 0
Image XResolution: 72
Image YResolution: 72
Image ResolutionUnit: Pixels/Inch
Image Software: HRY-AL00Ta 9.0.1.130(C00E130R4P1)
Image DateTime: 2019:06:18 16:33:40
Image YCbCrPositioning: Centered
Image ExifOffset: 290
GPS GPSVersionID: [2, 2, 0, 0]
GPS GPSLatitudeRef: N
GPS GPSLatitude: [13, 47, 847249/62500]
GPS GPSLongitudeRef: E
GPS GPSLongitude: [100, 37, 45710449/1000000]
............................省略
二、经纬度转地址
要想将图片中的经纬度信息转换为详细地址,同样也有很多方法,比如在线查询、地图API或者利用python的地理位置信息库:geopy。
在线查询
- GPS查询网址1
http://www.gpsspg.com/maps.htm
- GPS查询网址2
http://www.gzhatu.com/dingwei.html
没错,这张图片是我在泰国拍的😀
地图API
这里以百度地图API为例,需要去官网注册创建应用获取AK码。
百度地图:http://lbsyun.baidu.com
这里以我之前拍的重庆彩车照进行试验,从输出结果上看算是非常精准定位了。
import json
import requests
import exifread
with open('IMG_20191019_164726.jpg', 'rb') as f:
exif_dict = exifread.process_file(f)
# 经度
lon_ref = exif_dict["GPS GPSLongitudeRef"].printable
lon = exif_dict["GPS GPSLongitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(",")
lon = float(lon[0]) + float(lon[1]) / 60 + float(lon[2]) / float(lon[3]) / 3600
if lon_ref != "E":
lon = lon * (-1)
# 纬度
lat_ref = exif_dict["GPS GPSLatitudeRef"].printable
lat = exif_dict["GPS GPSLatitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(",")
lat = float(lat[0]) + float(lat[1]) / 60 + float(lat[2]) / float(lat[3]) / 3600
if lat_ref != "N":
lat = lat * (-1)
print('照片的经纬度:', (lat, lon))
# 调用百度地图api转换经纬度为详细地址
secret_key = 'MAsVGINLNyTGiM4UulcaeluCekGnAFxj' # 百度地图api 需要注册创建应用
baidu_map_api = 'http://api.map.baidu.com/reverse_geocoding/v3/?ak={}&output=json&coordtype=wgs84ll&location={},{}'.format(secret_key, lat, lon)
content = requests.get(baidu_map_api).text
gps_address = json.loads(content)
# 结构化的地址
formatted_address = gps_address["result"]["formatted_address"]
# 国家(若需访问境外POI,需申请逆地理编码境外POI服务权限)
country = gps_address["result"]["addressComponent"]["country"]
# 省
province = gps_address["result"]["addressComponent"]["province"]
# 市
city = gps_address["result"]["addressComponent"]["city"]
# 区
district = gps_address["result"]["addressComponent"]["district"]
# 语义化地址描述
sematic_description = gps_address["result"]["sematic_description"]
print(formatted_address)
print(gps_address["result"]["business"])
输出
照片的经纬度: (29.564165115277778, 106.54840087888888)
重庆市渝中区学田湾正街2号11楼
大礼堂,上清寺,大溪沟
geopy库
geopy使python开发人员能够使用第三方地理编码程序和其他数据源(包括谷歌地图,必应地图,Nominatim等),轻松定位全球各地的地址、城市、国家和地标的坐标。
安装
pip install geopy
通过geopy进行经纬度查询
import exifread
from geopy.geocoders import Nominatim
with open('IMG_20190618_163339.jpg', 'rb') as f:
exif_dict = exifread.process_file(f)
# 经度
lon_ref = exif_dict["GPS GPSLongitudeRef"].printable
lon = exif_dict["GPS GPSLongitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(",")
lon = float(lon[0]) + float(lon[1]) / 60 + float(lon[2]) / float(lon[3]) / 3600
if lon_ref != "E":
lon = lon * (-1)
# 纬度
lat_ref = exif_dict["GPS GPSLatitudeRef"].printable
lat = exif_dict["GPS GPSLatitude"].printable[1:-1].replace(" ", "").replace("/", ",").split(",")
lat = float(lat[0]) + float(lat[1]) / 60 + float(lat[2]) / float(lat[3]) / 3600
if lat_ref != "N":
lat = lat * (-1)
print('照片的经纬度:', (lat, lon))
reverse_value = str(lat) + ', ' + str(lon)
geolocator = Nominatim()
location = geolocator.reverse(reverse_value)
print('照片的经纬度信息:')
print((location.latitude, location.longitude))
print('照片的地址信息:')
print(location.address)
输出
照片的经纬度: (13.787098884444445, 100.62936401361111)
照片的经纬度信息:
(13.787094791472175, 100.6293647961708)
照片的地址信息:
กรุงเทพมหานคร, เขตวังทองหลาง, 10310, ประเทศไทย
三、搞事情
一张图片可以暴露你什么时间在什么地方,甚至体现了你当时在做什么,想想有多可怕,不信可以看看这个:
不过对于个人来说,图片exif信息可以让我们更好地管理自己拍摄的图片库。比如说:
按时间归类
可以通过exif时间信息,将图片归类到不同时间命名的文件夹下。
import os
import exifread
import shutil
imgs_path = 'E:\泰国游'
for img in os.listdir(imgs_path):
img_path = os.path.join(imgs_path, img)
img_file = open(img_path, 'rb')
exif_dict = exifread.process_file(img_file)
date = exif_dict['EXIF DateTimeOriginal']
date = date.values.replace(':', '_')
year_month_day = date[:10]
target_path = os.path.join('E:\旅游', year_month_day)
if not os.path.exists(target_path):
os.mkdir(target_path)
shutil.copy(img_path, target_path)
旅游地图
下面以本人2019年下半年的手机图片数据进行统计分析,并结合pyecharts和Mapbox进行展示。
- pyecharts可视化
pyecharts:一个大神创建的轮子,将python与echarts结合的强大的数据可视化工具。注意版本不同,有些接口调用是有区别的。
pip install pyecharts
pip install echarts-countries-pypkg
pip install pyecharts-jupyter-installer==0.0.3
统计和转换地址的代码,输出为csv文件:
import os
import json
import requests
import pandas as pd
import exifread
from geopy.geocoders import Nominatim
secret_key = '###################'
def convert_dms2dd(coord_arr):
arr = str(coord_arr).replace('[', '').replace(']', '').split(', ')
d = float(arr[0])
m = float(arr[1])
s = float(arr[2].split('/')[0]) / float(arr[2].split('/')[1])
dd = float(d) + (float(m) / 60) + (float(s) / 3600)
return dd
def get_img_infor_tup(photo):
img_file = open(photo, 'rb')
image_map = exifread.process_file(img_file)
img_dict = {}
img_dict['Image Name'] = os.path.basename(photo)
try:
img_dict['width'] = image_map['Image ImageWidth'].printable
img_dict['length'] = image_map['Image ImageLength'].printable
# 图片的经度
img_longitude = convert_dms2dd(image_map["GPS GPSLongitude"].printable)
if image_map["GPS GPSLongitudeRef"].printable != "E":
img_longitude = img_longitude * (-1)
img_dict['longitude'] = img_longitude
# 图片的纬度
img_latitude = convert_dms2dd(image_map["GPS GPSLatitude"].printable)
if image_map["GPS GPSLatitudeRef"].printable != "N":
img_latitude = img_latitude * (-1)
img_dict['latitude'] = img_latitude
altitude = image_map['GPS GPSAltitude'].printable
if '/' in altitude:
altitude = float(altitude.split('/')[0]) / float(altitude.split('/')[1])
img_dict['altitude'] = altitude
# 照片拍摄时间
img_dict['date'] = image_map["EXIF DateTimeOriginal"].printable
img_file.close()
# 返回经纬度元组
return img_dict
except Exception as e:
img_file.close()
print('ERROR:图片中不包含Gps信息')
return None
def get_detail_infor_by_baidu(lat, lon):
baidu_map_api = 'http://api.map.baidu.com/reverse_geocoding/v3/?ak={0}&output=json&coordtype=wgs84ll&location={1},' \
'{2}'.format(secret_key, lat, lon)
content = requests.get(baidu_map_api).text
gps_address = json.loads(content)
return gps_address["result"]
def img_data_statistic(imgs_path):
info_list = []
for file_name in os.listdir(imgs_path):
img_path = os.path.join(imgs_path, file_name)
info_dict = get_img_infor_tup(img_path)
if info_dict is not None:
gps_address_dict = get_detail_infor_by_baidu(info_dict['latitude'], info_dict['longitude'])
# 省
info_dict['province'] = gps_address_dict["addressComponent"]["province"]
# 市
info_dict['city'] = gps_address_dict["addressComponent"]["city"]
# 区
info_dict['district'] = gps_address_dict["addressComponent"]["district"]
info_dict['formatted_address'] = gps_address_dict["formatted_address"]
info_list.append(info_dict)
# break
img_df = pd.DataFrame(info_list)
img_df.to_csv('imgInfo.csv', index=False, encoding='utf-8')
if __name__ == '__main__':
imgs_path = 'E:/photo'
img_data_statistic(imgs_path)
可视化,主要是通过pyecharts进行绘图。
import pandas as pd
from pyecharts.charts import Bar
df = pd.read_csv('imgInfo.csv',sep=',')
data = df["province"].value_counts()
bar = (
Bar()
.add_xaxis(data.index.tolist())
.add_yaxis("图片数量", data.values.tolist())
.set_global_opts(title_opts=opts.TitleOpts(title="各省分布情况", subtitle='19年下半年去了哪儿'),
xaxis_opts=opts.AxisOpts(name="省份",axislabel_opts={"rotate":45}))
)
bar.render_notebook()
同理,各个城市的统计图。
以及,在地图上进行可视化。
import json
import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import Geo
from pyecharts.globals import CurrentConfig, NotebookType
CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_NOTEBOOK
def is_Chinese(word):
for ch in word:
if '\u4e00' <= ch <= '\u9fff':
return True
return False
df = pd.read_csv('imgInfo.csv',sep=',')
data = df["city"].value_counts()
# 找出国外地址,Geo添加自定义点
thailand_address = []
json_data= {}
for city in data.index:
if not is_Chinese(city):
json_data[city] = df[['longitude','latitude']][df["city"] == city].mean().values.tolist()
thailand_address.append(city)
json_str = json.dump(json_data, open('thailand.json', 'w', encoding='utf-8'), ensure_ascii=False,indent=4)
# 链式调用
c = (
Geo()
.add_schema(maptype="world")
.add_coordinate_json(json_file='thailand.json')
.add("去过的城市", [list(z) for z in zip(data.index.tolist(), data.values.tolist())],
symbol_size = 30, large_threshold = 2000, symbol="pin")
.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(title_opts=opts.TitleOpts(title="2019下半年你去过哪儿"))
)
c.render_notebook()
国内地图展示
- Mapbox可视化
可以直接将包含经度和维度的统计数据(csv格式)拖入到网站:
https://www.mapbox.cn/labs/mbxdataexplorer/