(0) 任务需求描述
接到一个需求,要求完成在线测试题目的拼接,这样就可以背题目了(像极了期末考前的我)不过这次不是我要考试 。所以我就帮助他完成考试题目的裁剪、拼接并转换成 PDF 用来打印。

- 完成图片的裁剪(要可以选择裁剪框的大小)。
- 完成图片的拼接(要可以选择一页要拼接多少张图片)。
- 完成拼接后图片向PDF的转化。
同时要注意可视化的要求,保证更好的交互性,所以这里使用到了进度条 tqdm。这里考虑的情况相对比较简单,因此只处理 png 以及 jpg格式的文件,同时待处理文件夹下面仅有这两种格式的图片。
(1) 准备工作
1.导入需要的库
- PyMuPDF:主要用来处理 PDF。
- pillow:主要用来处理图片。
- tqdm:主要用来引入进度条来完成可视化。
- shutil:主要用来进行os的相关操作。
- math:主要进行相关的数学操作。
import os import fitz # fitz 就是导入 PyMuPDF from PIL import Image from tqdm import tqdm import shutil import math
2.准备部分的代码
- 主要有三部分
initial(img_dir, output_name)
:完成初始化,将文件夹下面可能存在的无关文件均删除。set_same(img_dir)
:完成图片文件的统一,将 png 和 jpg 文件统一成为 jpg 文件。clear(img_dir)
:结束时的清理函数,最后将中间产生的文件夹均删除。
# **************************** Part 0 初始化清除函数以及格式统一函数 **************************** # def initial(img_dir, output_name): # 初始化,将文件夹下面可能存在的无关文件均删除 if os.path.exists(img_dir + r"cut\\"): # 删除目录,包括目录下的所有文件 shutil.rmtree(img_dir + r"cut\\") if os.path.exists(img_dir + r"compose\\"): shutil.rmtree(img_dir + r"compose\\") if os.path.exists(img_dir + output_name + ".pdf"): os.remove(img_dir + output_name + ".pdf") time.sleep(0.1) # 将文件夹下面的png和jpg文件统一成为jpg文件 def set_same(img_dir): files = os.listdir(img_dir) for file in files: if file.split('.')[-1] == "png": img = Image.open(img_dir + r"\\" + file) img = img.convert('RGB') img.save(img_dir + r"\\" + file.split('.')[0] + ".jpg", quality=95) os.remove(img_dir + r"\\" + file) print("Updated Successfully!!!") time.sleep(0.1) def clear(img_dir): # 结束时的清理函数,最后将中间产生的文件夹均删除 if os.path.exists(img_dir + r"cut\\"): shutil.rmtree(img_dir + r"cut\\") if os.path.exists(img_dir + r"compose\\"): shutil.rmtree(img_dir + r"compose\\") if(os.path.exists(img_dir + r"temp\\")): shutil.rmtree(img_dir + r"temp\\") time.sleep(0.1)
(2) 要处理的图片形式
- 具体要处理的单一图片形式如下图所示,所有的图片都是相似的,分为两部分,上面是题目描述,下面是答案,需要的就是这两部分,因此在图片裁剪部分需要将剩下不需要的地方裁掉。
(3) 图片的裁剪
image_clip(img_dir)
:完成图像的分割。
有几个注意事项,程序运行中会输出图片的大小(默认所有图片的大小一致),可以选择是否需要对图片进行裁剪,若选择需要对图片进行裁剪则需要输入裁剪框的位置,裁剪框的格式如下left upper right lower
,输入正整数,并且中间需要用空格隔开。# ******************************* Part 1 完成图像的分割 ********************************* # # 完成图片的切割 def image_clip(img_dir): # 完成格式的统一 set_same(img_dir) # 创建一个中间文件夹用来存储裁剪完成的图片 cut_dir = img_dir + r"cut\\" cnt = 0 files = os.listdir(img_dir) os.mkdir(cut_dir) img = Image.open(img_dir + "\\" + files[0]) print("图片大小为:" + str(img.size)) judge = input("是否要进行图片的裁剪(y/n):") if judge == 'n': # 直接将图片拷贝到中间文件夹 for file in tqdm(files, "拷贝到中间文件夹"): cnt = cnt + 1 shutil.copy(img_dir + "\\" + file, cut_dir + str(cnt) + ".jpg") elif judge == 'y': # (30, 200, 690, 900) (left, upper, right, lower) = list(map(eval, input("请输入裁剪框的位置(left, upper, right, lower):").split(" "))) # 完成对图片的裁剪 for file in tqdm(files,"图片的裁剪"): cnt = cnt + 1 img = Image.open(img_dir + "\\" + file) # 这里防止输入超出边界 right = min(right, img.size[0]) lower = min(lower, img.size[1]) cropped = img.crop((left, upper, right, lower)) cropped.save(cut_dir + str(cnt) + ".jpg")
(4) 图片的拼接
- 主要的函数是
merge_images(img_dir, image_size, image_colnum,image_row)
:重点说一下这几个参数的含义。img_dir
是图片的位置,image_size
表示图片的大小,这个参数主要来调控拼接后的清晰度,值越大,拼接后的清晰度也越大,image_colnum
表示一行有几张图,image_row
表示一列有几张图。# ******************************* Part 2 完成多张图像的拼接 ********************************* # # 按照宽度进行所需的比例缩放 def resize_by_width(infile, image_size): """按照宽度进行所需比例缩放""" im = Image.open(infile) (x, y) = im.size lv = round(x / image_size, 2) + 0.01 x_s = int(x // lv) y_s = int(y // lv) out = im.resize((x_s, y_s), Image.ANTIALIAS) return out # 返回一个图片的宽、高像素 def get_new_img_xy(infile, image_size): """返回一个图片的宽、高像素""" im = Image.open(infile) (x, y) = im.size lv = round(x / image_size, 2) + 0.01 x_s = x // lv y_s = y // lv return x_s, y_s # 定义图像拼接函数 def image_compose(image_colnum, image_size, image_rownum, image_names, image_save_path, x_new, y_new): to_image = Image.new('RGB', (image_colnum * x_new, image_rownum * y_new),(255,255,255)) # 创建一个新图 # 循环遍历,把每张图片按顺序粘贴到对应位置上 total_num = 0 for y in range(1, image_rownum + 1): for x in range(1, image_colnum + 1): from_image = resize_by_width(image_names[image_colnum * (y - 1) + x - 1], image_size) to_image.paste(from_image, ((x - 1) * x_new, (y - 1) * y_new)) total_num += 1 if total_num == len(image_names): break return to_image.save(image_save_path) # 保存新图 # 得到 image 的list列表 def get_image_list_fullpath(dir_path): file_name_list = os.listdir(dir_path) image_fullpath_list = [] for file_name_one in file_name_list: file_one_path = os.path.join(dir_path, file_name_one) if os.path.isfile(file_one_path): image_fullpath_list.append(file_one_path) else: img_path_list = get_image_list_fullpath(file_one_path) image_fullpath_list.extend(img_path_list) return image_fullpath_list # 完成图片的融合 def merge_images(img_dir, image_size, image_colnum,image_row): image_src_path = img_dir + r"cut\\" # 新建一个临时目录用来保存一次拼接所需的文件数量 temp_dir = img_dir + r"temp\\" if os.path.exists(temp_dir): shutil.rmtree(temp_dir) os.mkdir(temp_dir) # 新建一个临时目录用来保存拼接后图片 image_save_path = img_dir + r"compose\\" if os.path.exists(image_save_path): shutil.rmtree(image_save_path) os.mkdir(image_save_path) # 计算一张图片要拼接几张小图 n = image_colnum * image_row # 获取图片集地址下的所有图片名称 files = os.listdir(image_src_path) for i in tqdm(range(1, len(files)+1),"图片的拼接"): shutil.copy(image_src_path + str(i) + ".jpg", temp_dir) if(i % n == 0 or i == len(files)): image_fullpath_list = get_image_list_fullpath(temp_dir) if(i == len(files)): image_save = temp_dir + "test" + str(math.ceil(i/n)) + ".jpg" else: image_save = temp_dir + "test" + str(int(i / n)) + ".jpg" image_rownum_yu = len(image_fullpath_list) % image_colnum if image_rownum_yu == 0: image_rownum = len(image_fullpath_list) // image_colnum else: image_rownum = len(image_fullpath_list) // image_colnum + 1 x_list = [] y_list = [] image_fullpath_list.sort(key=lambda x:int(x.split('\\')[-1].split('.')[0])) for img_file in image_fullpath_list: img_x, img_y = get_new_img_xy(img_file, image_size) x_list.append(img_x) y_list.append(img_y) x_new = int(x_list[len(x_list) // 5 * 4]) y_new = int(y_list[len(y_list) // 5 * 4]) image_compose(image_colnum, image_size, image_rownum, image_fullpath_list, image_save, x_new, y_new) if (i == len(files)): shutil.copy(temp_dir + "test" + str(math.ceil(i / n)) + ".jpg", image_save_path) else: shutil.copy(temp_dir + "test" + str(int(i/n)) + ".jpg", image_save_path) shutil.rmtree(temp_dir) os.mkdir(temp_dir)
(5) 图片的合并
- 将拼接完成的图片合并成为 pdf 格式的文件,最后生成的 pdf 文件存在于与图片文件所处文件夹同一级的文件夹中。
# ******************************* Part 3 完成多张图像合并成pdf ********************************* # def pic2pdf(img_dir,output_path,output_name): doc = fitz.open() imgs = os.listdir(img_dir) imgs.sort(key=lambda x:int(x.split('.')[0][4:-1] + x.split('.')[0][-1])) for img in tqdm(imgs,"图片向PDF的转换"): # 读取图片,确保按文件名排序 imgdoc = fitz.open(img_dir + img) # 打开图片 pdfbytes = imgdoc.convertToPDF() # 使用图片创建单页的 PDF imgpdf = fitz.open("pdf", pdfbytes) doc.insertPDF(imgpdf) # 将当前页插入文档 doc.save(output_name + ".pdf") # 保存pdf文件 doc.close()
(6) 示例
-
全部图片文件,可以看到里面有 jpg 和 png 两种格式的文件。
-
程序输入输出以及进度条显示。
-
拼接完成的 pdf 文件。
全部的代码可以从这里下载:
Python 代码