在日常的工作生活中,我们会经常与图片打交道,比如

  • 下载图片
  • 压缩图片
  • 删除图片的元数据防止隐私泄漏
  • 拼接长图
  • 图片文字识别
  • 加水印
  • 将多个图片转成一个 pdf 文件

今天就来分享下如何用简单的方法使用 Python 来玩转这些操作。

1、下载图片

下载图片是最常见的操作了,只要找到图片的超链接,就可以轻松下载它。具体方法就是使用标准库或者 requests 库去请求这个 url,然后将得到的数据保存为文件即可。

有三种方法来下载图片,这里分别写了三个函数,如下所示:

方法一,使用标准库。

  1. from urllib.request import urlretrieve
  2. import ssl
  3. def urllib_download(img_url, download_path):
  4. ssl._create_default_https_context = ssl._create_unverified_context # 不校验ssl
  5. urlretrieve(img_url, download_path)

方法二,使用 requests。

import requests
def request_download(img_url, download_path):
    r = requests.get(img_url)
    with open(download_path, 'wb') as f:
        f.write(r.content)

方法三,使用 requests 的流式下载,适用于较大,网速慢,容易下载失败的图片。

import requests
def requests_chunk_download(img_url, download_path):
    r = requests.get(img_url, stream=True)
    with open(download_path, 'wb') as f:
        for chunk in r.iter_content(chunk_size=32):
            f.write(chunk)

主函数:

from pathlib import Path
if __name__ == '__main__':
    img_url = 'https://tinypng.com/images/panda-developing-2x.png'
    download_path = Path('./download_images')
    download_path.mkdir(exist_ok=True)
    urllib_download(img_url,f"{download_path}/images1.png")
    request_download(img_url, f"{download_path}/images2.png")
    requests_chunk_download(img_url, f"{download_path}/images3.png")

玩转图片 - 图1

可以看出三种方法下载的图片是一样的。完整代码

如果自动下载某网站的全部图片,其实一点也不复杂,无非就是找规律,如何获取全部图片的 url,然后循环调用下载函数,也可以使用递归来实现,具体方法可以参考 让 Python 自动下载网站所有文件

获取图片 url 可能会涉及正则表达式,关于正则表达式,可以参考 学会正则表达式,玩弄文本于股掌之中

2、压缩图片

有一次我用邮箱向老板发送 5 张图片时,foxmail 提示我是否启用 QQ 邮箱的超大附件功能,原来 5 张图片已经 40+ MB,现在的手机拍摄的真得太清晰了。

不过工作中的图片能看清楚内容就可以了,完全没有必要整那么清晰,文件太大,发给老板,老板打开图片也会卡,体验非常不好,于是我就想如何使用 Python 来压缩图片。

找了很多方法,都不是很理想,有的软件下载后才发现是付费的,有的在使用时直接导致程序卡死,有的压缩率不够需要多次压缩,有的要求原始图片大小不能超过 5 M,有的失真有些严重。

直到我用了 tinypng 的 api 接口,才发现这真的好用,图片几乎不失真,大多都控制在 1 MB 之内,在此分享给大家。

先打开 https://tinypng.com/developers, 在下方输入你的用户名和邮箱,就可以获取一个 API KEY。

然后 pip 安装一下这个库:

pip install tinify

编写三行代码就可以对图片进行压缩处理了:

import tinify
tinify.key = '此处填入你的key'
tinify.from_file(src_img_path).to_file(dst_img_path)

其中 src_img_path 是原图片,dst_img_path 是压缩后的图片。

比如找个目录,对文件批量压缩一下,完整代码如下:

import tinify
from pathlib import Path
import os

tinify.key = '此处填入你的key'
path = "./download_images" # 图片存放的路径

for dirpath, dirs, files in os.walk(path):
    for file in files:
        file = Path(dirpath)/Path(file)
        if file.suffix.lower() in ['.jpg','.png','.gif']:
            print("compressing ..."+ file.as_posix())
            tinify.from_file(file.as_posix()).to_file(file.with_suffix(".compressed.jpg").as_posix())

玩转图片 - 图2

可以看到,压缩后的文件大小在 1M 左右或 1M 之内,打开文件对比,基本看不出任何区别:

玩转图片 - 图3

3、删除图片的元数据

现在大部分快递已经可以对地址信息进行加密,大家的隐私保护意识也越来越高,可是一不小心,你随手发布的照片就可能暴露了你的位置信息。

因此,发布照片时去除照片的位置、设备、时间等隐私信息显得很有必要,这些信息又叫元数据,也就是 metadata。

Python 删除图片的元数据是有一个三方库 piexif,我使用它删除后,再用 exiftool 查看时,仍然可以查到许多隐私信息。

也就是说 piexif 删除的不够彻底,于是我用 Python 封装了 exiftool,这下,图片的元数据可以删除的干干净净。

文件 exif_tool.py 代码如下:

import subprocess
import os
import json
from pathlib import Path
class ExifTool(object):

    sentinel = "{ready}\n"
    #windows
    #sentinel = "{ready}\r\n"

    def __init__(self, executable="/usr/bin/exiftool"):
        exiftool1 = Path("/usr/bin/exiftool")
        exiftool2 = Path("/usr/local/bin/exiftool")
        self.executable = executable
        if exiftool1.exists():
            self.executable = exiftool1.as_posix()
        elif exiftool2.exists():
            self.executable = exiftool2.as_posix()
        else:
            if Path(self.executable).exists():
                pass
            else:
                raise FileNotFoundError(self.executable)


    def __enter__(self):
        self.process = subprocess.Popen(
            [self.executable, "-stay_open", "True",  "-@", "-"],
            universal_newlines=True,
            stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.process.stdin.write("-stay_open\nFalse\n")
        self.process.stdin.flush()

    def execute(self, *args):
        args = args + ("-execute\n",)
        self.process.stdin.write(str.join("\n", args))
        self.process.stdin.flush()
        output = ""
        fd = self.process.stdout.fileno()
        while not output.endswith(self.sentinel):
            # output += os.read(fd, 4096).decode('utf-8',errors=)
            output += os.read(fd, 4096).decode('utf-8',"ignore")
        return output[:-len(self.sentinel)]

    def get_metadata(self, *filenames):
        """
        返回多个文件的 exif 信息
        """
        return json.loads(self.execute("-G", "-j", "-n", *filenames))

    def get_exif_info(self, source_img):
        """
        返回单个文件的 exif 信息
        """
        return self.get_metadata(source_img)[0]

    def delete_exif_info(self, source_img):
        '''
        删除 exif 信息后,返回剩余的 exif 信息
        '''
        self.execute("-all=",source_img)
        metadata = self.get_metadata(source_img)
        return metadata[0]

使用前先确保操作系统已经安装了 exiftool,安装方法:

  • MacOs:brew install exiftool
  • Linux:sudo apt-get install -y exiftoolsudo yum install perl-Image-ExifTool
  • Windows:访问 https://exiftool.org 进行下载安装。

程序默认读取两个位置:

/usr/bin/exiftool
/usr/local/bin/exiftool

也可以自己传入 exiftool 的执行路径。

使用举例:

from pprint import pprint
if __name__ == '__main__':
    with ExifTool() as e:
        exif = e.get_exif_info('/Users/aaron/Documents/个人/origin/文件1.jpg')
        pprint(exif)
        exif = e.delete_exif_info('/Users/aaron/Documents/个人/origin/文件1.jpg')
        print("========删除 exif 信息后========")
        pprint(exif)

大家可以用 piexif 和我这里提供的 exif_tool 做个对比,看看哪个删除的更彻底,有问题请留言讨论。

完整代码

4、拼接长图

思路也简单,也把要拼接的图片放在数组里面,然后计算图片的最大宽度作为拼接后图片的宽度,然后一张一张拼接即可。

排版可以选择靠左对齐,或者水平居中对齐,空白位置的颜色也可以自己定义。

具体代码如下:

from pathlib import Path
from PIL import Image

def concat_pics(pics:list, image_path:str):
    """
    :param pics: 待拼接的图片路径列表
    :param image_path: 拼接后的图片存放路径
    :return: None
    """
    img_list = []
    for img in pics:
        img_list.append(Image.open(img))

    width = 0
    height = 0
    for img in img_list:
        # 单幅图像尺寸
        w, h = img.size
        height += h
        # 取最大的宽度作为拼接图的宽度
        width = max(width, w)

    # 创建空白长图,这里可以传入 color 设置空白地方的颜色,默认黑色
    result = Image.new(img_list[0].mode, (width, height), color='#ffffff')
    # 拼接图片
    height = 0
    for img in img_list:
        w, h = img.size
        # 图片水平居中
        result.paste(img, box=(round(width / 2 - w / 2), height))
        height += h
    # 保存图片
    result.save(image_path)

if __name__ == '__main__':
    pics = []
    imgs_path = Path('./download_images')
    for img in imgs_path.iterdir():
        if img.suffix.lower() in ['.jpg']:
            pics.append(img.as_posix())
    concat_pics(pics,"./download_images/拼接长图.jpg")

执行后的效果如下所示:

完整代码

5、如何识别图片上的文字

这其实就是 OCR 了,非常实用,不过个人很难训练出优秀的模型,不如直接用大厂提供的 API。

举个例子,百度云的 AI 产品,你可以在终端下执行这样一个命令来进行安装。

pip install baidu-aip

在这里我使用了百度云提供的在线文字识别产品,提供了 AipOcr 函数实现用户验证、client.basicGeneral 函数实现文字识别功能。

先去网站 https://console.bce.baidu.com/ai/ 申请 App ID、Api Key、Secret Key。

代码如下:


from aip import AipOcr
""" 你的 APPID AK SK """
APP_ID = '你的 App ID'
API_KEY = '你的 Api Key'
SECRET_KEY = '你的 Secret Key'
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
""" 读取图片 """
def get_file_content(filePath):
    with open(filePath, 'rb') as fp:
        return fp.read()
image = get_file_content('example.png')
""" 调用通用文字识别, 图片参数为本地图片 """
result = client.basicGeneral(image)
print(result)

在这段代码里,实现了三个功能,分别是用户验证、读取图片和识别图片。

为了更直观地看到效果,我这里对着书拍个照片,然后让它识别一下:

原图如下:

玩转图片 - 图4

识别结果如下:

玩转图片 - 图5

6、给图片加水印

添加自己的水印来防止别人盗图,也可以宣传品牌形象,如果要为大量图片添加文字水印,不妨使用以下方法。

from PIL import Image, ImageDraw, ImageFont

def add_text_watermark(img, text):
    img = Image.open(img)
    draw = ImageDraw.Draw(img)
    myfont = ImageFont.truetype('/System/Library/Fonts/PingFang.ttc', size=30)
    fillcolor = "#ff0000"
    width, height = img.size
    draw.text((width - 200, height - 40), text, font=myfont, fill=fillcolor)
    return img


if __name__ == '__main__':
    image = './download_images/images1.jpg'
    img1 = add_text_watermark(image,'@Python七号')
    img1.save("./download_images/result_text_watermark.jpg","jpeg")

说明 draw.text((width - 200, height - 40), text, font=myfont, fill=fillcolor)
第一个括号填写的是位置,左上角的坐标是 (0,0),右下角的坐标是(width,heigth),本例中 (width - 200, height - 40) 相当于是右下角。

效果如下:

玩转图片 - 图6

那你可能又问下,如果加图片水印呢? 比如现在有一个 logo 想添加到图片上,道理是一样的,代码如下:

from PIL import Image, ImageDraw, ImageFont

def add_img_watermark(img, img_watermark):
    rgba_image = Image.open(img).convert("RGBA")
    rgba_watermark = Image.open(img_watermark).convert("RGBA")
    image_x, image_y = rgba_image.size
    watermark_x, watermark_y = rgba_watermark.size
    # 缩放图片
    scale = 10
    watermark_scale = max(image_x / (scale * watermark_x), image_y / (scale * watermark_y))
    new_size = (int(watermark_x * watermark_scale), int(watermark_y * watermark_scale))
    rgba_watermark = rgba_watermark.resize(new_size, resample=Image.ANTIALIAS)
    # 透明度
    rgba_watermark_mask = rgba_watermark.convert("L").point(lambda x: min(x, 180))
    rgba_watermark.putalpha(rgba_watermark_mask)

    watermark_x, watermark_y = rgba_watermark.size
    # 水印位置
    rgba_image.paste(rgba_watermark, ( (image_x - watermark_x)//2, image_y - watermark_y-5))  # 右上角

    return rgba_image.convert("RGB")


if __name__ == '__main__':
    image = './download_images/images1.jpg'
    img_watermark = "./download_images/logo.jpg"
    img2 = add_img_watermark(image, img_watermark)
    img2.save("./download_images/result_img_watermark.jpg")

效果如下图所示:

玩转图片 - 图7

多个图片生成一个 pdf 文件

先看下一个图片如何变成 pdf

from PIL import Image

image1 = Image.open("aaa.png")
im1 = image1.convert('RGB')
im1.save("bbb.pdf")

你看,三行代码搞定,是不是很简单,如果是多个,代码这样写:

from PIL import Image

from pathlib import Path

imgs_list = []
imgs_path = Path("download_images")

for img in imgs_path.iterdir():
    if img.suffix in ['.jpg','.png']:
        tmp_img = Image.open(img)
        imgs_list.append(tmp_img.convert('RGB'))

img1 = imgs_list.pop(0) #选择第一个图片做为保存的对象

img1.save("imgs.pdf",save_all = True, append_images = imgs_list) #保存时添加剩余的图片

最后的话

本文完整代码 https://gitee.com/somenzz/code-example/tree/master/pic

图片是我们接触最多的媒体文件了,这里分享了关于典型的图片的实用操作,需要的可以直接复制这里的代码使用。

如果遇到问题或者想了解关于图片的更多的实用操作,请留言,我会第一时间回复。