PIL
在Web开发过程中,经常需要保存用户头像等一系列静态图片资源,此时需要我们服务端对前台输入的数据进行严格判断,避免非图片类型文件上传到服务器
图片分类
常见的存储的格式有如下一些
WebP
WebP是Google推出的影像技术,可以使让网页中的图片有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快
WebP改善了JPEG格式的图片压缩技术
WebP使用了一种基于VP8(能以更少的数据提供更高质量的视频,而且只需较小的处理能力即可播放视频)编码,这种技术已在2010五月开源
利用预测编码技术,同时还采用了一种基于RIFF的非常轻量级的容器。这种容器只会给每张图片增加20字节,但能让图片作者保存他们想要存储的元数据(文件描述信息)
这种格式的主要优势在于高效率,在质量相同的情况下,WebP格式图像的体积要比JPEG格式图像小40%
微信公众号使用的就是这种类型
# 使用PIL将图片处理为webp格式
def jpeg2webp(filename):
if filename:
im = PIL.Image.open(filename)
im.save(filename.split('.')[0] + '.webp')
JPEG
Joint Photographic Expert Group
头部字段常见为:ffd8 ffe0
JPEG格式文件后辍名为.jpg或.jpeg,是最常用的图像文件
JPEG格式是一种有损图片压缩格式,能够将图片压缩在很小的存储空间
但是JPEG格式所使用的是有损压缩格式,虽然图片存储量会减少,但是对应质量也会有丢失
PNG
Graphics Interchange Format
头部字段常见为:8950 4e47
PNG是网上接受的最新图像文件格式。PNG能够提供长度比GIF小30%的无损压缩图像文件
较旧的浏览器和程序可能不支持PNG文件
PNG只需要下载1/64的图像数据就可以展示低分辨率的预览图像
GIF
GIF是1987年开发的图像文件格式
GIF文件的数据,是一种基于LZW算法的连续色调的无损压缩格式
GIF不属于任何应用程序。几乎所有相关软件都支持GIF
GIF格式的另一个特点是其在一个GIF文件中可以存多幅彩色图像
如果把存于一个文件中的多幅图像数据逐幅读出并显示到屏幕上,就可构成一种最简单的动画
PIL模块
是图像处理标准库;PIL功能非常强大,并且API的使用也非常简单
安装同样也非常简单
pip3 install pillow
导入和所安装的包名可能有些不同
import PIL
图片文件识别
文件是否为一张图片识别方式有如下几种
- 使用标准库
imghdr
import imghdr
def is_image(filename):
if filename:
type_ = imghdr.what(filename)
if not type_:
raise TypeError('Is Not a Image')
return type_
- 使用
PIL模块输出文件格式进行判断
文件对象的format属性可以得出当前图片文件格式,当文件无法处理为图片时,PIL打开图片对象时候将会抛出异常OSError
def is_image(filename):
if filename:
try:
im = PIL.Image.open(filename)
except OSError as e:
pass
else:
return im.format
图片格式转换
PIL模块对于图片格式的转换非常只能,只需要将转换后的文件名后缀确定即可
PIL模块会自动根据后缀对图像文件内容进行处理
def image2webp(filename):
# 图片转webp格式
if filename:
im = PIL.Image.open(filename)
im.save(filename.split('.')[0] + '.webp')
def image2png(filename):
# 图片转png格式
if filename:
im = PIL.Image.open(filename)
im.save(filename.split('.')[0] + '.png')
通过打开的文件对象所支持的save函数,传递新文件名作为参数即可
创建缩略图
图片缩略图的创建可以通过PIL模块中的thumbnail方法来进行创建
该方法需要一个元组作为参数,元组代表像素大小
thumbnail方法会通过所传递元组的值,来将图片像素设置为最大不超过元组内数据的图片
def image2thumb(filename):
im = PIL.Image.open(filename)
size_ = (150,150)
im.thumbnail(size_)
im.save('thumb-' + filename)
注意,这并不会真的创建一个像素为绝对150x150的图片,只是图片的像素不会超过150而已
缩略图的创建也可以使图片文件所占存储空间大大降低
修改图片尺寸
修改图片尺寸使用resize函数,该函数与thumbnail接收类似参数
def image2resize(filename):
im = PIL.Image.open(filename)
size_ = (150,150)
out_ = im.resize(size_)
out_.save('resize-' + filename)
需要注意的是,直接修改图片尺寸可能会导致图片变成畸形的展示效果
另外这里的resize并不是直接影响打开图片的尺寸,而是返回一个新的修改过后的图片对象
大小的修改也可以使图片文件所占存储空间大大降低
图片压缩
图片压缩也可以时图片所占存储空间大大降低
这里比较科学的办法可以使用cv2模块下的imwrite函数对图片的原始质量进行修改
- 安装支持模块
opencv-python
pip3 install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
这里对于图片的压缩非常简单,只需要使用cv2模块下的imwrite函数重新保存图片即可
import cv2
def jpeg2cv(filename):
# 压缩JPEG
im = cv2.imread(filename)
cv2.imwrite('cv2-'+filename,im,[cv2.IMWRITE_JPEG_QUALITY,20])
def png2cv2(filename):
# 压缩PNG
im = cv2.imread(filename)
cv2.imwrite('cv2-'+filename,im,[cv2.IMWRITE_PNG_COMPRESSION,9])
在imwrite函数的参数部分,最后一个参数有如下意义:
对于JPEG格式的图片,这个参数表示从0-100的图片质量
IMWRITE_JPEG_QUALITY
-
默认值是
95 -
对于PNG格式的图片,这个参数表示压缩级别
IMWRITE_PNG_COMPRESSION- 取
0-9,较高的值意味着更小的尺寸和更长的压缩时间而默认值是3
图像转字符
- 构建基本方法,将像素对应灰度值转化为可以代表的字符
- 图像内容解析,转化为字符内容
- 字符内容转化为像素,写入图像
- gif解析为单独的每一帧图片经过以上步骤处理
- 将处理完成的单独图像组合成一个gif
必备概念
- 灰度值:黑白图像中点的颜色深度,范围一般从
0~255,白色为255,黑色为0,所以黑白图片也被成为灰度图像 - alpha通道值:一般用作不透明度参数。如果一个像素的alpha通道数值为0%,那它就是完全透明的(也就是看不见的),而数值为100%则意味着一个完全不透明的像素(传统的数字图像)
灰度值字符映射
每一个灰度值,我们都会有对应的ascii字符与之对应,通过字符所占空间大小,来确定其亮度,越靠前的,那么灰度值越低,也就越暗
灰度值计算公式:0.299 * R + 0.587 * G + 0.144 * B
def gray2char(r, g, b, alpha=256):
'''
根据RGBA值进行灰度值计算,并返回对应亮度的字符
'''
_ = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
char_length = len(_) # 字符序列长度
proportion = 255 / char_length # 总灰度值对应列表索引范围
gray = 0.2126 * r + 0.7152 * g + 0.0722 * b # 灰度值与rgb的计算公式
return _[int(gray / proportion) - 1] # 返回当前灰度值所对应字符
图像解析为字符
通过解析图像,我们可以得处图像每处像素点的rgb颜色值,并且通过这色值对应转化出灰度值,利用灰度值字符函数,将这个像素对应的字符拿到
注意由于字符和像素的宽高是有区别的,一个字符要比一个字符大的多,所以一张图像在处理过程中,宽高首先要进行比例划算,一般一个字符的宽高对于一个像素来说是6:1、11:1
def image2text(path):
'''
对图像进行解析
@path: 图像路径
'''
img = Image.open(path).convert('RGB') # 打开图像
pic_width, pic_height = img.width, img.height # 图像原始宽高
width, height = int(pic_width / 6), int(pic_height / 11) # 字符像素宽高转化
img = img.resize((width, height), Image.NEAREST) # 原始图像进行缩略,适合字符处理
# 像素遍历,进行灰度值字符转化
content = '' # 存储转化结果字符
colors = [] # 对应坐标原始颜色,为了之后给字符上色使用
for h in range(height): # 从高开始
for w in range(width): # 遍历每一行像素
px = img.getpixel((w, h)) # 获取某一点的像素值
char = gray2char(px[0], px[1], px[2], px[3] if len(px) > 3 else 256)
colors.append((px[0], px[1], px[2]))
content += char
content += '\n' # 每一行像素换行追加\n
colors.append((255, 255, 255)) # 给换行未来的颜色就是白色
return content, colors, pic_width, pic_height
灰度字符转图片
通过上一步方法,得到的content返回值,正式接下来需要存储如图片的字符内容,这里需要开启一个新的图像对象
结合对应颜色,将字符写入对应的像素点上,最后存储为图片,格式可以是jpg,这样比较小一些,png质量高,结果会大
def text2image(content, colors, pic_width, pic_height, path):
'''
字符存储为图像
@path: 存储路径
'''
image = Image.new("RGB", (pic_width, pic_height), (255, 255, 255)) # 创建存储图像对象
canvas = ImageDraw.Draw(image) # 创建一个支持绘制的画布
font = ImageFont.load_default().font # 直接使用默认字体对象
x = 0
y = 0
font_w, font_h = font.getsize(content[1]) # 字体的宽高
for i in range(len(content)): # 遍历字符内容对象
if content[i] == '\n': # 遍历到\n那就是下一行的元素了
x = -font_w # 每次初始化横纵坐标
y += font_h
continue
canvas.text((x, y), content[i], colors[i]) # 写入字符,带上颜色
x += font_w # 偏移一个字体的像素
image.save(path)
基本测试
对一张基本图像进行字符转化,并将结果存储为char.jpg
from PIL import Image, ImageFont, ImageDraw
def main():
content, colors, pic_width, pic_height = image2text(path="test.jpg")
text2image(content, colors, pic_width, pic_height, path="char.jpg")
GIF迭代
如果需要处理的是一个gif图像对象,那么首先将gif图像对象中的每一帧图片单独保存下来,接着图像解析为字符
接着将灰度字符转图片,最后再将字符图像组合为一个gif即可
def gif2image(path):
'''
gif图像拆分,并将拆分结果存储当前工作目录下的temp目录中
@path: gif图像位置
'''
img = Image.open(path)
work_path = os.getcwd() # 当前工作路径
cache_dir = os.path.join(work_path, 'gifTemp')
if not os.path.exists(cache_dir): # 如果不存在保存单独每一帧图片的目录,则创建该目录
os.mkdir(cache_dir)
while True:
try:
current = img.tell() # 获取当前帧位置
file_name = os.path.join(cache_dir, str(current)+'.png')
img.save(file_name)
img.seek(current+1) # 向下一帧读取
except EOFError: # GIF读取完毕
break
return current
之后即可通过for循环使用上面的图像处理的两个方法对其进行处理,处理完成的图像可以保存至content目录下
通过遍历content目录下的处理好的字符图片,对其进行gif拼接
import imageio
def image2gif(_id, dir_name='content', duration=15 / 130):
'''
将之前处理好的字符png图片组合成GIF图像
通过imageio模块处理合并
'''
path = os.path.join(os.getcwd(), dir_name)
images = []
for pic_id in range(_id):
# 遍历取出每一张处理后的字符图片id值
images.append(imageio.imread(os.path.join(path, '%d.png' % pic_id)))
# 从文件中读入数据
imageio.mimsave(os.path.join(os.getcwd(), 'fin.gif'),
images, duration=duration)