python I/O

文件读写

I/O,即Input & Output的缩写,意思是对文件进行的各种读取和写入,在程序编写中是非常常用的一种操作。

在python中,文件读写非常的简洁方便,看下面的伪代码:

文件对象 = 打开(文件路径,执行的操作)

其中,当python代码文件存放路径和被读写的文件路径一致的时候,可以仅填入文件名(包含扩展名),执行的操作通常有读取、写入、读+写。

下面是一段简单的读取txt文件并打印出其内容的代码:

f = open("hello.txt", "r")

print(f.read())

# 将会打印出hello.txt中储存的文本内容

f.close()

对文件执行的操作有如下几种常见模式:

r - 只读

w - 写入

r+w - 读+写

需要注意的是,在每次文件被读取或写入之后,都需要调用文件对象的.close()方法,这是因为文件的读写方法被调用之后,其实并没有立马就被计算机调用,而是被放到了缓存中等待下一步操作(惰性)。因此假如文件没有被确实地关闭,将会导致各种各样的错误。

而众所周知,程序员都是很懒的,一行代码能搞定的事情尽量不会每次都多打好几行。所以就会有下面这种更加优雅的写法:

with open("hello.txt", "r") as f:

print(f.read())

这样能够让python在执行完操作之后自动帮我们将文件正确关闭。

比较进阶的.open()用法:

f.open(文件名,执行操作,缓存,编码...)

其中你可以对被打开的文件指定一个编码格式,加入文件中含有大量中文,那么UTF-8是必须的,否则只包含英文的文件默认不传入编码参数即可。

而文件的读取也有几种不同的方式,通常来说对于体积比较小的文件,.read()能够胜任大部分工作,但有些时候有逐行读需的需要时,就需要用到.readlines()方法,他能够一次读取一行文本,因此能够和循环语句组合使用(事实上文件对象都是可迭代对象,因此都能够被正确的迭代)。但当文件体积非常非常大的时候,就不能简单粗暴的对文件一次读取了,因为那样电脑的缓存将会被挤爆。这时就需要在.read()的括号中填入参数:

f.read(1024)

这行代码的意思是一次读取1kb(1024byte)的文件内容。

(8bit = 1byte, 1024byte = 1kb)

如果想往文件中写入内容,那么就使用.write()方法:

f.write(content, "\n")

# \n为换行符

补充内容,打开文件除了使用with 语句,其实也可以使用try ... except ... finally 语句。

try:

this# 如果this执行失败

except:

that# 那么就执行that

finally:

do this no matter what

# 不管执行了上面的哪条命令,最后强制执行finally下的操作

图片读写

在python中对图片进行读写操作需要引用到外部模块 ---- Pillow(Python Imaging Library)。

由于图片本质上也是一种文件,所以能对其他文件执行的操作同样也普遍适用于图片。

首先要对一张图片进行各种操作,当然是先要打开它:

from PIL import Image

img = Image.open("thispicture.jpg")

x, y = img.size

crop_region = (0, 0, x, y/3)

new_image = img.crop(crop_region)

new_image.save("new_img.jpg")

这里首先需要先从外部导入Image 模块,然后通过Image.open()方法打开图片。打开之后才能对图片进行各种操作,首先定义一个裁剪框的大小,通过ImageObject.size获得图片的原始长宽像素值(一个二元的set),然后再使用.crop()方法,传入裁剪框进行裁剪。

需要注意的是,在PIL 中,图像的原始坐标(0, 0)位于图片的左上角,往下往右走依次是x、y轴,并且裁剪框的四个元素依次定义的是(左,上,右,下)四个边界的位置。

crop_box(0, 0, delta, y)

裁剪动作完成之后,务必要对裁剪后的新的图片对象进行.save()保存,通过传入新的路径和名字来另存为图像。

另外上面说道图片本质上是文件,那么在打开图片之后也一样的,在操作完成之后也需要正确的关闭(保存)。所以偷懒句式with ... as... 在Pillow 中也同样适用;

with Image.open("another_img.jpg") as img:

print(img.format, img.size, img.mode)

# 打印出 JPG (长, 宽) RGB

通过这种方法,我们就能够在打印出图片各种基本信息之后正确的关闭它。

那么,上面的.format用于返回图片的格式,Pillow支持大多数主流图片格式;而.mode则用于返回一个当前图片使用的色彩模式,通常有三种:

L - 代表“Luminance", 灰度图

RGB - ”Red Green and Blue,就是常见的真彩色

CMYK - 代表印刷用色

在Pillow中可以简单地对打开的图片进行色彩模式转换,方法是使用.convert()方法:

with Image.open("colorful.jpg") as img:

grayscale = img.convert("L")

grayscale.save("gray_scene.jpg")

# 将会得到一张黑白版本的图片

通过传入色彩模式到.convert() 中,就可以对一张图片的色彩模式进行随意的切换。

上面介绍了修改图片之后另存为的操作,那么其实和Photoshop一样,我们也可以直接在一张图片上进行修改并直接保存。例如使用.paste()方法:

def roll(image, delta):

xsize, ysize = image.size

delta = delta % xsize

if delta == 0: return image

part1 = image.crop((0, 0, delta, ysize))

part2 = image.crop((delta, 0, xsize, ysize))

part1.load()

part2.load()

image.paste(part2, (0, 0, xsize-delta, ysize))

image.paste(part1, (xsize-delta, 0, xsize, ysize))

image.save("new_scene.jpg")

原始图像

修改后的图像

这里,part1 为delta左边的那块图像,part2为delta右边的那块,当两组切割动作完成之后,我们调用了.load()方法,对两个裁剪的对象进行加载。

这是因为图像切割方法.crop()是一种惰性加载,他只有在被.load() 或者.paste() 被调用的时候才会进行实际的裁剪动作,因此如果不事先进行加载的话,上图的裁剪动作会发生在part2被粘贴之后,将产生我们不想要的结果。

图像调节

学会了裁剪粘贴,再看点更高级的操作。

在Pillow中其实也可以对一张图片进行分通道修改。使用.split()方法:

with Image.open("new_scene.jpg") as img:

r, g, b = img.split()

r.save("scene_R.jpg")

g.save("scene_G.jpg")

b.save("scene_B.jpg")

# 将rgb三个通道分别存储为三张图片

这里的通道概念其实和Photoshop中是一样的,对于RGB图片来说,就有红,绿,蓝三个颜色通道,每个通道以灰度展示每个像素点上该颜色的所占权重。假如一个像素点上,R通道灰度为80%,G通道灰度为20%,B通道灰度为5%,那么对应的RGB值就可以大致转换为R:204 G:51 B:13,即这个像素点将呈现一个偏灰的橙红色。

屏幕色彩工作原理

那么当我们将一张原始图片按照通道分开之后,只要对每个通道进行单独修改,就可以达到简单的调色操作了:

from PIL import Image, ImageFilter, ImageEnhance

with Image.open("colors.png") as img:

r, g, b = img.split()

enhance_r = ImageEnhance.Contrast(r)

r = enhance_r.enhance(0.5)

# 对R通道降低0.5的对比度

enhance_b = ImageEnhance.Brightness(b)

b = enhance_b.enhance(0.2)

# 对B通道降低0.8的亮度

enhanced_img = Image.merge("RGB", (r, g, b))

# 将三个通道重新合并成一个新的图片

enhanced_img = enhanced_img.filter(ImageFilter.DETAIL)

# 将合并通道后的图片进行细微的锐化

enhanced_img.save("enhanced.png")

原始图像

修改后的图像

R通道

G通道

B通道

在上面这段代码中,首先是引入了PIL 中的ImageFilter和ImageEnhance两个子模块,然后通过ImageEnhance中的预置的类ImageEnhance.Brightness()和ImageEnhance.contrast()对图像进行调整,最后使用.merge()方法对三个分开的通道进行合并。

查阅Pillow 官方文档,可得知,在ImageEnhance 类中,有以下几种可调用的子类:

ImageEnhance.Color()- 对颜色的饱和度进行调节

ImageEnhance.Contrast()- 对整张图片的颜色及亮度的对比度进行调节

ImageEnhance.Brightness()- 对图片的亮度进行调节

ImageEnhance.Sharpness()- 调节图片锐化程度

其中,具体用法是,传入一个图片对象使这一次图片调节动作实例化,然后调用父类的.enhance(factor)方法,这个方法中的参数factor代表本次调节的强度,默认值为1.0,即不改变原图,而大于1.0则意味着增强效果,反之。

而在最后执行的ImageFilter,则类似于Photoshop 中的各种自带滤镜。在Pillow 中也有一些自带的滤镜效果:

BLUR

CONTOUR

DETAIL

EDGE_ENHANCE

EDGE_ENHANCE_MORE

EMBOSS

FIND_EDGE

SHARPEN

SMOOTH

SMOOTH_MORE

上面的一些是不提供参数值修改的预置滤镜,所以效果是固定的。

而下面的一些则提供了一个可选的参数:

ImageFilter.GaussianBlur(radius)

ImageFilter.BoxBlur(radius)

ImageFilter.UnsharpMask(radius, percent, threshold)

ImageFilter.Kernel(size, kernel, scale, offset)

ImageFilter.RankFilter(size, rank)

ImageFilter.MinFilter(size)

ImageFilter.MaxFilter(size)

ImageFilter.MedianFilter(size)

ImageFilter.ModeFilter(size)

从命名基本可以看出每个滤镜分别负责做什么工作,由于滤镜比较多,还是在需要使用的时候再逐一查阅文档比较节省时间,这里就不加赘述。

使用这些滤镜的方式为,直接在原始图片对象上调用.filter()方法,并传入需要的滤镜类名即可。

图像中的文字处理

要在Pillow 中处理文字,需要引入两个子模块ImageFont 和ImageDraw,前者用于获取字体而后者用于将字体绘制到图片上。

from PIL import Image, ImageDraw, ImageFont

with Image.open("solid.png") as img:

draw_img = ImageDraw.Draw(img)

my_font = ImageFont.truetype("Sanchez-Regular.ttf", 65)

draw_img.text((img.size[0]/2, img.size[1]/2), "Bonjour!", font = my_font)

img.save("texted.png")

# 由于是在原图上进行绘制,所以保存的时候也是在原图的对象上进行保存,而并非上面其他的例子那样是另存为新文件

输出图片

上面这段代码中,首先是对ImageDraw.Draw()这个类创建了一个实例draw_img,然后通过ImageFont.truetype("FontName.ttf", size)获取字体以及字的大小。其中,第一个参数为所选字体的文件名(为了方便调用,字体文件放在和python 代码文件同一级目录下),第二个参数则是字体的大小。

稍后通过.text((x, y), "Content", font, fill)直接在图片上开始绘制,其中第一个参数为代表字体在图片上的位置的元组。需要注意的是,一串字体位置的定义以这串字体的最小外边界框(Bounding box)左上角那个点为准,如下图:

draw_img.text((x/2, y/2), "some texts", font=font)

因此,要想让字体被正好绘制到图片的正中间,需要用.textsize()做一些计算:

with Image.open("solid.png") as img:

draw_img = ImageDraw.Draw(img)

my_font = ImageFont.truetype("Sanchez-Regular.ttf", 65)

tx, ty = draw.textsize("Bonjour!", font=my_font)

position = (img.size[0]/2 - tx/2, img.size[1]/2 - ty/2)

draw_img.text(position, "Bonjour!", font = my_font)

img.save("texted.png")

.textsize() 返回一个二元set,即字体的bounding box长宽值。

而.text()的第三个参数用于接收定义字体的对象,第四个参数用于接收颜色。对颜色的定义可以采用RGB值,也可以直接将HEX色值作为字符串传入。

draw_img.text(position, "Hello", font=my_font, fill="#ff0000")

# 以HEX值传入

draw_img.text(position, "Hello", font=my_font, fill=(255, 0, 0))

# 以RGB set 传入

以上就是python中文件读写以及图片操作的基本知识,Pillow其实还涉及到更加深入的功能,篇幅限制这里就不全部写到,Pillow模块的全部文档都可以在下面的references中找到。

References

有关屏幕显像原理的深入阅读:

http://www.xueui.cn/tutorials/color-tutorial/rgb-color-system-xiangjie-preexistence-series-of-color-thirteen.html

https://zh.wikipedia.org/wiki/三原色光模式

https://www.zhihu.com/question/21849710

关于ImageDraw模块的字体Bounding box 大小的判定:

http://forums.devshed.com/python-programming-11/centering-graphical-text-pil-409129.html

Pillow PyPi 文档:

https://pillow.readthedocs.io/en/latest/handbook/tutorial.html

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180617G1445G00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券