这里写目录标题

  • 效果
  • 需要用到的模块
  • 处理步骤以及代码讲解
  • 1、获得照片的拍摄时间
  • 2、获取拍照地点
  • 3、图片转正
  • 4、 添加水印
  • 完整代码



我们平时想打印图片的时候,希望能给照片加上时间、地点,以便以后看到照片的时候不会忘记。


手工一张张添加太麻烦,而且容易出错。以下是使用代码获取原始图片的拍摄时间和地点,并添加水印的方法,在Mac OS下对jpeg、jpg文件处理已经测试通过了。

效果

首先我们看下效果,第一张是原图,第二张是添加水印时间和地点后的图:

图片添加水印 Java 图片添加水印时间_水印


图片添加水印 Java 图片添加水印时间_时间_02

需要用到的模块

exifread
PIL
requests
json

处理步骤以及代码讲解

1、获得照片的拍摄时间

这里我们需要用到exifread模块以获取照片的拍照时间。1

def getPhotoTime(filename):
    '''得到照片的拍照时间(如果获取不到拍照时间,则使用文件的创建时间)
    '''
    try:
        if os.path.isfile(filename):
            fd = open(filename, 'rb')
        else:
            raise "[%s] is not a file!\n" % filename
    except:
        raise "unopen file[%s]\n" % filename
        
    #默认用图像文件的创建日期作为拍摄日期(如果有照片的拍摄日期,则修改为拍摄日期
    state = os.stat(filename)
    dateStr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(state[-2]))

    data = exifread.process_file( fd )
    if data: #取得照片的拍摄日期,改为拍摄日期
        try:
            t = data['EXIF DateTimeOriginal'] #转换成 yyyy-mm-dd_hh:mm:ss的格式
            dateStr = str(t).replace(":","-")[:10] + str(t)[10:]
        except:
            pass

    return dateStr

2、获取拍照地点

这里我们依然要用到exifread模块以获取照片的拍照的经纬度,在使用baidu的api逆地理编码得到对应的具体位置。2
注意在使用baidu的api之前需要去百度地图开放平台注册账号,申请secret_key,可以参考别人的申请ak3

def format_lat_lng(data):
    '将exif得到的经纬度转化成数值, 这个有点笨重'
    list_tmp=str(data).replace('[', '').replace(']', '').split(',')
    l= [ele.strip() for ele in list_tmp]
    data_sec = int(l[-1].split('/')[0]) /(int(l[-1].split('/')[1])*3600)# 秒的值
    data_minute = int(l[1])/60
    data_degree = int(l[0])
    result=data_degree + data_minute + data_sec
    return result

def getLocationBy_lat_lng(lat, lng):
    """
    使用Geocoding API把经纬度坐标转换为结构化地址。需要注册baidu map api的key
    """
    secret_key = 'XXXXX你得ak密钥'  # 请修改这里
    
    # 使用说明http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding-abroad
    baidu_map_api = 'http://api.map.baidu.com/reverse_geocoding/v3/?ak={0}&output=json&coordtype=wgs84ll&location={1},{2}'.format(
        secret_key, lat, lng)
    response = requests.get(baidu_map_api)
    content = response.text
    baidu_map_address = json.loads(content)
    formatted_address = baidu_map_address["result"]["formatted_address"]
    return formatted_address

def getLocation(filename):
    '得到照片的拍照位置(如果获取不到,则为空字符串)'
    try:
        if os.path.isfile(filename):
            fd = open(filename, 'rb')
        else:
            raise "[%s] is not a file!\n" % filename
    except:
        raise "unopen file[%s]\n" % filename
        
    # 图像文件的拍摄地址默认为空
    locationStr= ''
    
    data = exifread.process_file( fd )
    if data: #取得照片的拍摄位置
        try:
            lat= format_lat_lng(data['GPS GPSLatitude'])  # [34, 12, 9286743/200000] -> xx.xxxxx
            lng= format_lat_lng(data['GPS GPSLongitude']) # [108, 57, 56019287/1000000] -> xx.xxxx
            locationStr= getLocationBy_lat_lng(lat, lng)
        except:
            pass
        
    return locationStr

3、图片转正

对于手机、相机等设备拍摄的照片,由于手持方向的不同,拍出来的照片可能是旋转0°、90°、180°和270°。即使在电脑上利用软件将其转正,他们的exif信息中还是会保留方位信息。
在用PIL读取这些图像时,读取的是原始数据,也就是说,即使电脑屏幕上显示是正常的照片,用PIL读进来后,也可能是旋转的图像,并且图片的size也可能与屏幕上的不一样。
对于这种情况,可以利用PIL读取exif中的orientation信息,然后根据这个信息将图片转正后,再进行后续操作,具体如下。4

def orientate(img):
    '''利用PIL读取exif中的orientation信息,然后根据这个信息将图片转正后,再进行后续操作
    '''
    try:
        orientation= None
        for i in ExifTags.TAGS.keys() : 
            if ExifTags.TAGS[i]=='Orientation' : # 肯定会找到orientation,所以不需要对None做处理
                orientation= i
                break 
        exif=dict(img._getexif().items())
        if   exif[orientation] == 3 : 
            img=img.rotate(180, expand = True)
        elif exif[orientation] == 6 : 
            img=img.rotate(270, expand = True)
        elif exif[orientation] == 8 : 
            img=img.rotate(90, expand = True)
    except:
        pass
    return img

4、 添加水印

最后是添加水印,这一步是将上面的步骤整合起来,做处理。
这一步根据不同的图片需要调整字体大小(size),字体位置(d_width,d_height)和字体填充颜色(fillcolor)。5

def add_watermark(filename, text):
    '为照片文件添加水印,filename是照片文件名,text是水印时间和位置'
    # 创建输出文件夹
    outdir= 'watermark/'
    mymkdir(outdir) 

    # 创建绘画对象
    image= Image.open(filename)
    image= orientate(image) # 将图片转正
    draw= ImageDraw.Draw(image)
    width, height= image.size # 宽度,高度
    size= int(0.04*width)  # 字体大小(可以调整0.04)
    myfont= ImageFont.truetype('/Library/Fonts/Arial Unicode.ttf', size=size) # 80, 4032*3024
    #fillcolor= '#000000' # RGB黑色
    fillcolor= '#fbfafe'

    # 参数一:位置(x轴,y轴);参数二:填写内容;参数三:字体;参数四:颜色    
    d_width, d_height=0.5*width, 0.92*height # 字体的相对位置(0.5, 0.92可以根据图片调整)
    draw.text((d_width, d_height), text, font=myfont, fill=fillcolor) # (-1200, -320)

    new_filename= get_new_filename(filename)
    image.save(outdir + new_filename)

完整代码

以下是完整代码,并附上github的链接(链接中有原始的示例图片)。

''' 
批量给图片增加时间水印的Python脚本程序【目前测试为Mac OS X下的jpeg文件】。
遍历指定目录(含子目录)的照片文件,根据拍照时间给照片在右下角添加时间和地点的水印。


!该程序需要安装exifread,PIL,requests等模块,否则无法使用。
例如,Linux/Mac OS X下命令行安装该模块:sudo pip install exifread

!该程序需要加baidu api的secret_key(在getLocationBy_lat_lng函数中);
!该程序可以手动调整字体大小(size),字体位置(d_width,d_height)和字体填充颜色(fillcolor)(在add_watermark函数中)。

获取拍照时间 From 'https://www.racksam.com/2014/05/14/python-script-to-change-pictures-filenames/'
拍照地点 参考 http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding-abroad
watermark 参考  
orientation 参考 
'''
#!/usr/bin/env python
# -*- coding: utf-8 -*-  
import os
import stat
import time
import math
import exifread
from PIL import Image, ImageDraw, ImageFont, ExifTags
import requests
import json

'=================分割线==================='
# 创建文件夹
def mymkdir(path):
    if not os.path.exists(path):
        os.mkdir(path)

# 判断是否是支持的文件类型
SUFFIX_FILTER = ['.jpg','.png','.mpg','.mp4','.thm','.bmp','.jpeg','.avi','.mov'] # 仅测试了jpeg,jpg
def isTargetedFileType(filename):
    '根据文件扩展名,判断是否是需要处理的文件类型;仅支持x.x格式'
    filename_nopath = os.path.basename(filename)
    f,e = os.path.splitext(filename_nopath)
    if e.lower() in SUFFIX_FILTER:
        return True
    else:
        return False

# 新文件的名字
def get_new_filename(filename):
    f, e= filename.split('.')
    return "%s_watermark.%s" % (f, e)

'=================分割线==================='

'=================begin{得到照片的拍摄时间}==================='
def getPhotoTime(filename):
    '''得到照片的拍照时间(如果获取不到拍照时间,则使用文件的创建时间)
    '''
    try:
        if os.path.isfile(filename):
            fd = open(filename, 'rb')
        else:
            raise "[%s] is not a file!\n" % filename
    except:
        raise "unopen file[%s]\n" % filename
        
    #默认用图像文件的创建日期作为拍摄日期(如果有照片的拍摄日期,则修改为拍摄日期
    state = os.stat(filename)
    dateStr = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(state[-2]))

    data = exifread.process_file( fd )
    if data: #取得照片的拍摄日期,改为拍摄日期
        try:
            t = data['EXIF DateTimeOriginal'] #转换成 yyyy-mm-dd_hh:mm:ss的格式
            dateStr = str(t).replace(":","-")[:10] + str(t)[10:]
        except:
            pass

    return dateStr
'=================end{得到照片的拍摄时间}==================='


'=================begin{得到照片的拍摄地点}==================='
def format_lat_lng(data):
    '将exif得到的经纬度转化成数值, 这个有点笨重'
    list_tmp=str(data).replace('[', '').replace(']', '').split(',')
    l= [ele.strip() for ele in list_tmp]
    data_sec = int(l[-1].split('/')[0]) /(int(l[-1].split('/')[1])*3600)# 秒的值
    data_minute = int(l[1])/60
    data_degree = int(l[0])
    result=data_degree + data_minute + data_sec
    return result

def getLocationBy_lat_lng(lat, lng):
    """
    使用Geocoding API把经纬度坐标转换为结构化地址。需要注册baidu map api的key
    """
    secret_key = 'XXXXX你得ak密钥'  # 请修改这里
    
    # 使用说明http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding-abroad
    baidu_map_api = 'http://api.map.baidu.com/reverse_geocoding/v3/?ak={0}&output=json&coordtype=wgs84ll&location={1},{2}'.format(
        secret_key, lat, lng)
    response = requests.get(baidu_map_api)
    content = response.text
    baidu_map_address = json.loads(content)
    formatted_address = baidu_map_address["result"]["formatted_address"]
    return formatted_address

def getLocation(filename):
    '得到照片的拍照位置(如果获取不到,则为空字符串)'
    try:
        if os.path.isfile(filename):
            fd = open(filename, 'rb')
        else:
            raise "[%s] is not a file!\n" % filename
    except:
        raise "unopen file[%s]\n" % filename
        
    # 图像文件的拍摄地址默认为空
    locationStr= ''
    
    data = exifread.process_file( fd )
    if data: #取得照片的拍摄位置
        try:
            lat= format_lat_lng(data['GPS GPSLatitude'])  # [34, 12, 9286743/200000] -> xx.xxxxx
            lng= format_lat_lng(data['GPS GPSLongitude']) # [108, 57, 56019287/1000000] -> xx.xxxx
            locationStr= getLocationBy_lat_lng(lat, lng)
        except:
            pass
        
    return locationStr

'=================end{得到照片的拍摄地点}==================='

def orientate(img):
    '''对于手机、相机等设备拍摄的照片,由于手持方向的不同,拍出来的照片可能是旋转0°、90°、180°和270°。即使在电脑上利用软件将其转正,他们的exif信息中还是会保留方位信息。
        在用PIL读取这些图像时,读取的是原始数据,也就是说,即使电脑屏幕上显示是正常的照片,用PIL读进来后,也可能是旋转的图像,并且图片的size也可能与屏幕上的不一样。
        对于这种情况,可以利用PIL读取exif中的orientation信息,然后根据这个信息将图片转正后,再进行后续操作,具体如下。
    '''
    try:
        orientation= None
        for i in ExifTags.TAGS.keys() : 
            if ExifTags.TAGS[i]=='Orientation' : # 肯定会找到orientation,所以不需要对None做处理
                orientation= i
                break 
        exif=dict(img._getexif().items())
        if   exif[orientation] == 3 : 
            img=img.rotate(180, expand = True)
        elif exif[orientation] == 6 : 
            img=img.rotate(270, expand = True)
        elif exif[orientation] == 8 : 
            img=img.rotate(90, expand = True)
    except:
        pass
    return img


def add_watermark(filename, text):
    '为照片文件添加水印,filename是照片文件名,text是水印时间和位置'
    # 创建输出文件夹
    outdir= 'watermark/'
    mymkdir(outdir) 

    # 创建绘画对象
    image= Image.open(filename)
    image= orientate(image) # 将图片转正
    draw= ImageDraw.Draw(image)
    width, height= image.size # 宽度,高度
    size= int(0.04*width)  # 字体大小(可以调整0.04)
    myfont= ImageFont.truetype('/Library/Fonts/Arial Unicode.ttf', size=size) # 80, 4032*3024
    #fillcolor= '#000000' # RGB黑色
    fillcolor= '#fbfafe'

    # 参数一:位置(x轴,y轴);参数二:填写内容;参数三:字体;参数四:颜色    
    d_width, d_height=0.5*width, 0.92*height # 字体的相对位置(0.5, 0.92可以根据图片调整)
    draw.text((d_width, d_height), text, font=myfont, fill=fillcolor) # (-1200, -320)

    new_filename= get_new_filename(filename)
    image.save(outdir + new_filename)


def scandir(startdir):
    '遍历指定目录,对满足条件的文件进行改名或删除处理'
    os.chdir(startdir) # 改变当前工作目录
    for obj in os.listdir(os.curdir) :
        if os.path.isfile(obj):
            if isTargetedFileType(obj): # 对满足过滤条件的文件,加时间和地点水印
                photoTime = getPhotoTime(obj) # 获得照片的拍摄时间,当作水印的内容
                location= getLocation(obj) # 获得照片的拍摄位置,当作水印的内容

                print("%s    %s" % (obj, photoTime+' '+location))
                add_watermark(obj, location +'\n'+ photoTime) #加时间和地点水印


if __name__ == "__main__":
    path = "./fig" # 照片位置
    scandir(path)

  1. https://www.racksam.com/2014/05/14/python-script-to-change-pictures-filenames/ ↩︎
  2. http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding-abroad ↩︎
  3. https://www.jianshu.com/p/6ad61317d988 ↩︎