用Python处理图像

入门知识

颜色。如果你有使用颜料画画的经历,那么一定知道混合红、黄、蓝三种颜料可以得到其他的颜色,事实上这三种颜色就是美术中的三原色,它们是不能再分解的基本颜色。在计算机中,我们可以将红、绿、蓝三种色光以不同的比例叠加来组合成其他的颜色,因此这三种颜色就是色光三原色。在计算机系统中,我们通常会将一个颜色表示为一个RGB值或RGBA值(其中的A表示Alpha通道,它决定了透过这个图像的像素,也就是透明度)。

名称 RGBA值 名称 RGBA值
White (255, 255, 255, 255) Red (255, 0, 0, 255)
Green (0, 255, 0, 255) Blue (0, 0, 255, 255)
Gray (128, 128, 128, 255) Yellow (255, 255, 0, 255)
Black (0, 0, 0, 255) Purple (128, 0, 128, 255)

像素。对于一个由数字序列表示的图像来说,最小的单位就是图像上单一颜色的小方格,这些小方块都有一个明确的位置和被分配的色彩数值,而这些一小方格的颜色和位置决定了该图像最终呈现出来的样子,它们是不可分割的单位,我们通常称之为像素(pixel)。每一个图像都包含了一定量的像素,这些像素决定图像在屏幕上所呈现的大小。

Pillow库

Pillow是由从著名的Python图像处理库PIL发展出来的一个分支,通过Pillow可以实现图像压缩和图像处理等各种操作。可以使用下面的命令来安装Pillow。

  1. pip install pillow

Pillow中最为重要的是Image类,可以通过Image模块的open函数来读取图像并获得Image类型的对象。

读取和显示图像

  1. from PIL import Image
  2. # 读取图像获得Image对象
  3. image = Image.open('image/guido.png')
  4. # 通过Image对象的format属性获得图像的格式
  5. print(image.format)
  6. # 通过Image对象的size属性获得图像的尺寸
  7. print(image.size)
  8. # 通过Image对象的mode属性获取图像的模式
  9. print(image.mode)
  10. # 通过Image对象的show方法显示图像
  11. image.show()

guido.png

剪裁图像

  1. # 通过Image对象的crop方法指定剪裁区域图像
  2. image.crop((80, 20, 310, 360)).show()

吉多.png

生成缩略图

  1. # 通过Image对象的thumbnail方法生成指定尺寸的缩略图
  2. image.thumbnail((128, 128))
  3. image.show()

image-thumbnail.png

缩放和黏贴图像

  1. from PIL import Image
  2. touxiang_image = Image.open('image/touxiang.png')
  3. guido_image = Image.open('image/guido.png')
  4. # 从吉多的照片上裁剪出吉多的头
  5. head = touxiang_image.crop((140, 70, 495, 495))
  6. width, height = head.size
  7. # 使用Image对象的resize方法修改图像尺寸
  8. # 使用Image对象的paste方法将吉多的头粘贴到头像图片上
  9. guido_image.paste(head.resize((int(width / 1.5), int(height / 1.5))), (172, 40))
  10. guido_image.show()

image.png

旋转和翻转

  1. guido_image = Image.open('image/guido.png')
  2. # 使用Image对象的rotate方法实现图像的旋转
  3. guido_image.rotate(45).show()
  4. # 使用Image对象的transpose方法实现图像翻转
  5. # Image.FLIP_LEFT_RIGHT - 水平翻转
  6. # Image.FLIP_TOP_BOTTOM - 垂直翻转
  7. guido_image.transpose(Image.FLIP_LEFT_RIGHT).show()

image.pngimage.png

操作像素

  1. guido_image = Image.open('image/guido.png')
  2. for x in range(80, 310):
  3. for y in range(20, 360):
  4. # 通过Image对象的putpixel方法修改图像指定像素点
  5. guido_image.putpixel((x, y), (128, 128, 128))
  6. guido_image.show()

image.png

滤镜效果

  1. from PIL import Image, ImageFilter
  2. guido_image = Image.open('image/guido.png')
  3. # 使用Image对象的filter方法对图像进行滤镜处理
  4. # ImageFilter模块包含了诸多预设的滤镜也可以自定义滤镜
  5. guido_image.filter(ImageFilter.CONTOUR).show()

image.png

使用Pillow绘图

Pillow中有一个名为ImageDraw的模块,该模块的Draw函数会返回一个ImageDraw对象,通过ImageDraw对象的arclinerectangleellipsepolygon等方法,可以在图像上绘制出圆弧、线条、矩形、椭圆、多边形等形状,也可以通过该对象的text方法在图像上添加文字。

  1. import random
  2. from PIL import Image, ImageDraw, ImageFont
  3. def random_color():
  4. """生成随机颜色"""
  5. r = random.randint(0, 255)
  6. g = random.randint(0, 255)
  7. b = random.randint(0, 255)
  8. return r, g, b
  9. width, height = 800, 600
  10. # 创建一个800*600的图像,背景颜色为白色
  11. image = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
  12. # 创建一个ImageDraw对象
  13. drawer = ImageDraw.Draw(image)
  14. # 通过指定字体和大小获得ImageFont对象
  15. font = ImageFont.truetype('Kongxin.ttf', 32)
  16. # 通过ImageDraw对象的text方法绘制文字
  17. drawer.text((300, 50), 'Hello world!', fill=(255, 0, 0), font=font)
  18. # 通过ImageDraw对象的line方法绘制两条对角线
  19. drawer.line((0, 0, width, height), fill=(0, 0, 255), width=2)
  20. drawer.line((width, 0, 0, height), fill=(0, 0, 255), width=2)
  21. # 通过ImageDraw对象的rectangle方法绘制矩形
  22. xy = width // 2 - 60, height // 2 - 60, width // 2 + 60, height // 2 + 60
  23. drawer.rectangle(xy, outline=(255, 0, 0), width=2)
  24. # 通过ImageDraw对象的ellipse方法绘制椭圆
  25. for i in range(4):
  26. left, top, right, bottom = 150 + i * 120, 220, 310 + i * 120, 380
  27. drawer.ellipse((left, top, right, bottom), outline=random_color(), width=8)
  28. # 显示图像
  29. image.show()
  30. # 保存图像
  31. image.save('image/result.png')

result.png

注意:上面代码中使用的字体文件需要根据自己准备,可以选择自己喜欢的字体文件并放置在代码目录下。

除了可以用Pillow来处理图像外,还可以使用更为强大的OpenCV库来完成图形图像的处理,OpenCV(Open Source Computer Vision Library)是一个跨平台的计算机视觉库,可以用来开发实时图像处理、计算机视觉和模式识别程序。

用Python读写CSV文件

CSV文件介绍

CSV(Comma Separated Values)全称逗号分隔值文件是一种简单、通用的文件格式,被广泛的应用于应用程序(数据库、电子表格等)数据的导入和导出以及异构系统之间的数据交换。因为CSV是纯文本文件,不管是什么操作系统和编程语言都是可以处理纯文本的,而且很多编程语言中都提供了对读写CSV文件的支持,因此CSV格式在数据处理和数据科学中被广泛应用。

CSV文件有以下特点

  1. 纯文本,使用某种字符集(如ASCII、Unicode、GB2312)等);
  2. 由一条条的记录组成(典型的是每行一条记录);
  3. 每条记录被分隔符(如逗号、分号、制表符等)分隔为字段(列);
  4. 每条记录都有同样的字段序列。

CSV文件可以使用文本编辑器或类似于Excel电子表格这类工具打开和编辑,当使用Excel这类电子表格打开CSV文件时,你甚至感觉不到CSV和Excel文件的区别。很多数据库系统都支持将数据导出到CSV文件中,当然也支持从CSV文件中读入数据保存到数据库中。

将数据写入CSV文件

现有五个学生三门课程的考试成绩需要保存到一个CSV文件中,要达成这个目标,可以使用Python标准库中的csv模块,该模块的writer函数会返回一个csvwriter对象,通过该对象的writerowwriterows方法就可以将数据写入到CSV文件中,具体的代码如下所示。

  1. import csv
  2. import random
  3. with open('res/scores.csv', 'w') as file:
  4. writer = csv.writer(file)
  5. writer.writerow(['姓名', '骑马', '射箭', '兵器'])
  6. names = ['关羽', '张飞', '赵云', '马超', '黄忠']
  7. for i in range(len(names)):
  8. riding = random.randint(50, 100)
  9. archery = random.randint(40, 100)
  10. weapon = random.randint(30, 100)
  11. writer.writerow([names[i], riding, archery, weapon])

需要说明的是上面的writer函数,该函数除了传入要写入数据的文件对象外,还可以dialect参数,它表示CSV文件的方言,默认值是excel。除此之外,还可以通过delimiterquotecharquoting参数来指定分隔符(默认是逗号)、包围值的字符(默认是双引号)以及包围的方式。其中,包围值的字符主要用于当字段中有特殊符号时,通过添加包围值的字符可以避免二义性。大家可以尝试将上面第5行代码修改为下面的代码,看看生成的CSV文件到底有什么区别。

  1. writer = csv.writer(file, delimiter='|', quoting=csv.QUOTE_ALL)

从CSV文件读取数据

如果要读取刚才创建的CSV文件,可以使用下面的代码,通过csv模块的reader函数可以创建出csvreader对象,该对象是一个迭代器,可以通过next函数或for-in循环读取到文件中的数据。

  1. import csv
  2. with open('res/scores.csv', 'r') as file:
  3. reader = csv.reader(file, delimiter=',')
  4. for line in reader:
  5. print(reader.line_num, end='\t')
  6. for elem in line:
  7. print(elem, end='\t')
  8. print()

注意:上面的代码对csvreader对象做for循环时,每次会取出一个列表对象,该列表对象包含了一行中所有的字段。

将来如果大家使用Python做数据分析,很有可能会用到名为pandas的三方库,它是Python数据分析的神器之一。pandas中封装了名为read_csvto_csv的函数用来读写CSV文件,其中read_CSV会将读取到的数据变成一个DataFrame对象,而这个对象就是pandas库中最重要的类,它封装了一系列的方法用于对数据进行处理(清洗、转换、聚合等);而to_csv会将DataFrame对象中的数据写入CSV文件,完成数据的持久化。

用Python读写Excel文件

Excel简介

Excel是Microsoft(微软)为使用Windows和macOS操作系统开发的一款电子表格软件。Excel凭借其直观的界面、出色的计算功能和图表工具,再加上成功的市场营销,一直以来都是最为流行的个人计算机数据处理软件。当然,Excel也有很多竞品,例如Google Sheets、LibreOffice Calc、Numbers等,这些竞品基本上也能够兼容Excel,至少能够读写较新版本的Excel文件,当然这些不是我们讨论的重点。掌握用Python程序操作Excel文件,可以让日常办公自动化的工作更加轻松愉快,而且在很多商业项目中,导入导出Excel文件都是特别常见的功能。

Python操作Excel需要三方库的支持,如果要兼容Excel 2007以前的版本,也就是xls格式的Excel文件,可以使用三方库xlrdxlwt,前者用于读Excel文件,后者用于写Excel文件。如果使用较新版本的Excel,即操作xlsx格式的Excel文件,也可以使用openpyxl库,当然这个库不仅仅可以操作Excel,还可以操作其他基于Office Open XML的电子表格文件。

下面我们以xlwtxlrd为例讲解如何读写Excel文件,大家可以先使用下面的命令安装这两个三方库文件。

  1. pip install xlwt xlrd -i https://pypi.doubanio.com/simple

使用xlwt和xlrd

读Excel文件

例如在当前文件夹下有一个名为“HistoricalData.xls”的Excel文件,如果想读取并显示该文件的内容,可以通过如下所示的代码来完成。

  1. import xlrd
  2. # 使用xlrd模块的open_workbook函数打开指定Excel文件并获得Book对象(工作簿)
  3. wb = xlrd.open_workbook('res/HistoricalData.xls')
  4. # 通过Book对象的sheet_names方法可以获取所有表单名称
  5. sheetname = wb.sheet_names()[0] # [0]第一个表单
  6. # 通过指定的表单名称获取sheet对象(工作表)
  7. sheet = wb.sheet_by_name(sheetname)
  8. # 通过sheet对象的nrows和ncols属性获取表单的行数和列数
  9. print(sheetname)
  10. print(sheet.nrows, sheet.ncols)
  11. for row in range(sheet.nrows):
  12. for col in range(sheet.ncols):
  13. # 通过Sheet对象的cell方法获取指定Cell对象(单元格)
  14. # 通过Cell对象的value属性获取单元格中的值
  15. value = sheet.cell(row, col).value
  16. # 对除首行外的欺负他行进行数据格式化处理
  17. if row > 0:
  18. # 第一列的xldate类型先转成元组再格式化为"年月日"的格式
  19. if col == 0:
  20. # xldate_as_tuple函数的第二个参数只有0和1两个取值
  21. # 其中0代表以1900-01-01为基准的日期,1代表以1904-01-01为基准的日期
  22. # value = xlrd.xldate_as_tuple(value, 1)
  23. value = f'{value[-4:]}年{value[:2]}月{value[3:5]}日'
  24. # 其他列的number类型处理成小数点后保留两位有效数字的浮点数
  25. else:
  26. value = f'{value:.2f}'
  27. print(value, end='\t')
  28. print()
  29. # 获取最后一个单元格的数据类型
  30. # 0 - 空值,1 - 字符串,2 - 数字,3 - 日期,4 - 布尔,5 - 错误
  31. last_cell_type = sheet.cell_type(sheet.nrows - 1, sheet.ncols - 1)
  32. print(last_cell_type)
  33. # 获取第一行的值(列表)
  34. print(sheet.row_values(0))
  35. # 获取指定行指定列范围的数据(列表)
  36. # 第一个参数代表行索引,第二个和第三个参数代表列的开始(含)和结束(不含)索引
  37. print(sheet.row_slice(3, 0, 5))

注意:xlrd现已更新到了2.0.1版本,只支持.xls文件,不支持.xlsx文件。下面命令可以安装旧版 pip uninstall xlrd pip install xlrd==1.2.0 更多关于xlrd模块的知识,可以阅读它的官方文档

写Excel文件

写入Excel文件可以通过xlwt 模块的Workbook类创建工作簿对象,通过工作簿对象的add_sheet方法可以添加工作表,通过工作表对象的write方法可以向指定单元格中写入数据,最后通过工作簿对象的save方法将工作簿写入到指定的文件或内存中。下面的代码实现了将5个学生3门课程的考试成绩写入Excel文件的操作。

  1. import random
  2. import xlwt
  3. student_names = ['关羽', '张飞', '赵云', '马超', '黄忠']
  4. scores = [[random.randint(40, 100) for _ in range(3)] for _ in range(5)]
  5. # 创建工作簿对象(Workbook)
  6. wb = xlwt.Workbook()
  7. # 创建工作表对象(Worksheet)
  8. sheet = wb.add_sheet('蜀国')
  9. # 添加表头数据
  10. titles = ('姓名', '骑马', '射箭', '兵器')
  11. for index, title in enumerate(titles):
  12. # write(行, 列, '内容')
  13. sheet.write(0, index, title)
  14. # 将学生姓名和考试成绩写入单元格
  15. for row in range(len(scores)):
  16. sheet.write(row + 1, 0, student_names[row])
  17. for col in range(len(scores[row])):
  18. sheet.write(row + 1, col + 1, scores[row][col])
  19. # 保存Excel工作簿
  20. wb.save('res/蜀国成绩表.xls')

调整单元格样式

在写Excel文件时,我们还可以为单元格设置样式,主要包括字体(Font)、对齐方式(Alignment)、边框(Border)和背景(Background)的设置,xlwt对这几项设置都封装了对应的类来支持。要设置单元格样式需要首先创建一个XFStyle对象,再通过该对象的属性对字体、对齐方式、边框等进行设定,例如在上面的例子中,如果希望将表头单元格的背景色修改为黄色,可以按照如下的方式进行操作。

  1. header_style = xlwt.XFStyle()
  2. pattern = xlwt.Pattern()
  3. pattern.pattern = xlwt.Pattern.SOLID_PATTERN
  4. # 0 - 黑色、1 - 白色、2 - 红色、3 - 绿色、4 - 蓝色、5 - 黄色、6 - 粉色、7 - 青色、
  5. pattern.pattern_fore_colour = 5
  6. header_style.pattern = pattern
  7. # 添加表头数据
  8. titles = ('姓名', '骑马', '射箭', '兵器')
  9. for index, title in enumerate(titles):
  10. # write(行, 列, '内容')
  11. sheet.write(0, index, title, header_style)

如果希望为表头设置指定的字体,可以使用Font类并添加如下所示的代码。

  1. font = xlwt.Font()
  2. font.name = '华文楷体' # 字体名称
  3. font.height = 20 * 18 # 字体大小(20是基准单位,18表示18px)
  4. font.bold = True # 是否使用粗体
  5. font.italic = False # 是否使用斜体
  6. font.colour_index = 2 # 字体颜色
  7. header_style.font = font

如果希望表头垂直居中对齐,可以使用下面的代码进行设置。

  1. align = xlwt.Alignment()
  2. # 垂直方向的对齐方式
  3. align.vert = xlwt.Alignment.VERT_CENTER
  4. # 水平方向的对齐方式
  5. align.horz = xlwt.Alignment.HORZ_CENTER
  6. header_style.alignment = align

如果希望给表头加上黄色的虚线边框,可以使用下面的代码来设置。

  1. borders = xlwt.Borders()
  2. props = (
  3. ('top', 'top_colour'), ('right', 'right_colour'),
  4. ('bottom', 'bottom_colour'), ('left', 'left_colour')
  5. )
  6. # 通过循环对四个方向的边框样式及颜色进行设定
  7. for position, color in props:
  8. setattr(borders, position, xlwt.Borders.DASHED)
  9. setattr(borders, color, 5)
  10. header_style.borders = borders

如果要调整单元格的宽度(列宽)和表头的高度(行高),可以按照下面的代码进行操作。

  1. # 设置行高为40px
  2. sheet.row(0).set_style(xlwt.easyxf(f'font:height {20 * 40}'))
  3. titles = ('姓名', '语文', '数学', '英语')
  4. for index, title in enumerate(titles):
  5. # 设置列宽为200px
  6. sheet.col(index).width = 20 * 200
  7. # 设置单元格的数据和样式
  8. sheet.write(0, index, title, header_style)

其他操作Excel文件的三方库(如openpyxl)大家有兴趣可以自行了解。掌握了Python程序操作Excel的方法,可以解决日常办公中很多繁琐的处理Excel电子表格工作,最常见就是将多个数据格式相同的Excel文件合并到一个文件以及从多个Excel文件或表单中提取指定的数据。当然,如果要对表格数据进行处理,使用Python数据分析神器之一的pandas库可能更为方便,因为pandas库封装的函数以及DataFrame类可以完成大多数数据处理的任务。

用Python操作PDF文件

PDF是Portable Document Format的缩写,这类文件通常使用.pdf作为其扩展名。在日常开发工作中,最容易遇到的就是从PDF中读取文本内容以及用已有的内容生成PDF文档这两个任务。

从PDF中提取文本

在Python中,可以使用名为PyPDF2的三方库来读取PDF文件,可以使用下面的命令来安装它。

  1. pip install PyPDF2 -i https://pypi.doubanio.com/simple

PyPDF2没有办法从PDF文档中提取图像、图表或其他媒体,但它可以提取文本,并将其返回为Python字符串。

  1. import PyPDF2
  2. reader = PyPDF2.PdfFileReader('res/test.pdf')
  3. page = reader.getPage(0)
  4. print(page.extractText())

当然,PyPDF2并不是什么样的PDF文档都能提取出文字来,尤其是在提取中文的时候。网上有很多讲解从PDF中提取文字的文章,推荐阅读《三大神器助力Python提取pdf文档信息》一文进行了解。

要从PDF文件中提取文本也可以使用一个三方的命令行工具,具体的做法如下所示。

  1. pip install pdfminer.six
  2. pdf2text.py test.pdf

旋转和叠加页面

上面的代码中通过创建PdfFileReader对象的方式来读取PDF文档,该对象的getPage方法可以获得PDF文档的指定页并得到一个Page对象,利用Page对象的rotateClockwiserotateCounterClockwise方法可以实现页面的顺时针和逆时针方向旋转,代码如下所示。

  1. import PyPDF2
  2. reader = PyPDF2.PdfFileReader('test.pdf')
  3. page = reader.getPage(0)
  4. page.rotateClockwise(90)
  5. writer = PyPDF2.PdfFileWriter()
  6. writer.addPage(page)
  7. with open('test-rotated.pdf', 'wb') as file:
  8. writer.write(file)

Page对象还有一个名为mergePage的方法,可以将一个页面和另一个页面进行叠加,对于叠加后的页面,我们还是使用PdfFileWriter对象的addPage将其添加到一个新的PDF文档中。

加密PDF文件

使用PyPDF2中的PdfFileWrite对象可以为PDF文档加密,如果需要给一系列的PDF文档设置统一的访问口令,使用Python程序来处理就会非常的方便。

  1. import PyPDF2
  2. with open('res/test.pdf', 'rb') as file:
  3. # 通过PdfReader读取未加密的PDF文档
  4. reader = PyPDF2.PdfFileReader(file)
  5. writer = PyPDF2.PdfFileWriter()
  6. for page_num in range(reader.numPages):
  7. # 通过PdfReader的getPage方法获取指定页码的页
  8. # 通过PdfWriter方法的addPage将添加读取到的页
  9. writer.addPage(reader.getPage(page_num))
  10. # 通过PdfWriter的encrypt方法加密PDF文档
  11. writer.encrypt('foobared')
  12. # 将加密后的PDF文档写入指定的文件中
  13. with open('res/test-encrypted.pdf', 'wb') as file2:
  14. writer.write(file2)

提示:按照上面的方法也可以读取多个PDF文件的多个页面并将其合并到一个PDF文件中。

创建PDF文件

创建PDF文档需要三方库reportlab的支持,该库的使用方法可以参考它的官方文档,安装的方法如下所示。

  1. pip install reportlab

下面通过一个例子展示了reportlab的用法。

  1. import random
  2. from reportlab.lib import colors
  3. from reportlab.lib.pagesizes import A4
  4. from reportlab.pdfgen import canvas
  5. from reportlab.platypus import Table, TableStyle
  6. # 创建Canvas对象(PDF文档对象)
  7. doc = canvas.Canvas('demo.pdf', pagesize=A4)
  8. # 获取A4纸的尺寸
  9. width, height = A4
  10. # 读取图像
  11. image = canvas.ImageReader('guido.jpg')
  12. # 通过PDF文档对象的drawImage绘制图像内容
  13. doc.drawImage(image, (width - 250) // 2, height - 475, 250, 375)
  14. # 设置字体和颜色
  15. doc.setFont('Helvetica', 32)
  16. doc.setFillColorRGB(0.8, 0.4, 0.2)
  17. # 通过PDF文档对象的drawString输出字符串内容
  18. doc.drawString(10, height - 50, "Life is short, I use Python!")
  19. # 保存当前页创建新页面
  20. doc.showPage()
  21. # 准备表格需要的数据
  22. scores = [[random.randint(60, 100) for _ in range(3)] for _ in range(5)]
  23. names = ('Alice', 'Bob', 'Jack', 'Lily', 'Tom')
  24. for row, name in enumerate(names):
  25. scores[row].insert(0, name)
  26. scores.insert(0, ['Name', 'Verbal', 'Math', 'Logic'])
  27. # 创建一个Table对象(第一个参数是数据,第二个和第三个参数是列宽和行高)
  28. table = Table(scores, 50, 20)
  29. # 设置表格样式(对齐方式和内外边框粗细颜色)
  30. table.setStyle(TableStyle([
  31. ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
  32. ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.red),
  33. ('BOX', (0, 0), (-1, -1), 0.25, colors.black)
  34. ]))
  35. table.split(0, 0)
  36. # 通过Table对象的drawOn在PDF文档上绘制表格
  37. table.drawOn(doc, (width - 200) // 2, height - 150)
  38. # 保存当前页创建新页面
  39. doc.showPage()
  40. # 保存PDF文档
  41. doc.save()

说明:上面的代码使用了很多字面常量来指定位置和尺寸,在商业项目开发中应该避免这样的硬代码(hard code),因为这样的代码不仅可读性差,而且维护起来也是一场噩梦。如果项目中需要动态生成PDF文档且PDF文档的格式是相对固定的,可以将上面的字面常量处理成符号常量。记住:符号常量优于字面常量。Python语言没有内置定义常量的语法,但是可以约定变量名使用全大写字母的变量就是常量。