Python 实现大量图片裁剪拼接并生成PDF

该博客介绍了如何使用Python处理图片,包括批量裁剪、拼接和转换为PDF。首先,通过初始化删除无关文件,然后统一图片格式。接着,根据用户输入裁剪图片,再按照指定数量拼接图片。最后,将拼接后的图片合并成PDF。整个过程使用了PyMuPDF、Pillow和tqdm等库,实现了进度条的可视化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(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 代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值