用python将一个扫描pdf文件改成二值图片构成的pdf文件

[复制链接]
发表于 2024-11-27 00:22:26 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
使用墨水屏读书如今似乎越来越盛行,这确实有一定的利益,例如根本不发热,电池续航时间超长,根本不能游戏以是有利于沉浸式阅读,还有不知道是不是真的有用的所谓防蓝光伤害。但是,如果阅读的书籍是扫描图片构成的pdf,如果扫描的时候用的彩色模式,那么这种书籍在墨水屏上有点灰蒙蒙的,如果转换为256级灰度图片时最高灰度值太低,更加难以看清,这时候就可以思量将这个pdf文件转换成二值图片(即每个像素不是白色就是纯黑的玄色)构成的pdf,这样效果就很好了。
先看看在PC上两种不同pdf文件的效果对比:
转换后的二值图片pdf效果:

转换前的效果:

只管在非黑白墨水屏的装备上彩色pdf文件读起来更舒适,但是在黑白墨水屏上却刚好相反。下面的python程序就可以实现上述效果的转换(程序注释中标明的库的版本是本人测试情况中的版本,并非必须。其他版本大概也能够成功运行):
  1. ###############################################################
  2. # 将彩色或灰度扫描pdf文件转换为二值的黑白pdf文件,在墨水屏上阅读时更为清晰 #
  3. ###############################################################
  4. import fitz # pip install pymupdf==1.24.14
  5. import numpy as np # pip install numpy==2.1.1
  6. from PIL import Image # pip install pillow==10.4.0
  7. file = 'test.pdf'
  8. pdf_pages = fitz.open(file)
  9. img_list = []
  10. # 二值化阈值,可根据实际情况调整
  11. threshold = 200
  12. try:
  13.     for page in pdf_pages:
  14.         # 获取页面的图片数据,类型为pymupdf.Pixmap
  15.         pixmap = page.get_pixmap()
  16.         # 解码为 np.uint8类型的numpy.ndarray
  17.         image_array = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(
  18.             pixmap.height, pixmap.width, pixmap.n)
  19.         # 转换为PIL.Image.Image,通过三行代码将pymupdf.Pixmap转换成了PIL.Image.Image
  20.         image = Image.fromarray(image_array)
  21.         # 将彩色图片转换为黑白图片
  22.         image = image.convert('L')
  23.         # 获取图片的像素数据
  24.         pixels = image.load()
  25.         # 获取图片的宽度和高度
  26.         width, height = image.size
  27.         # 遍历每个像素点进行二值化处理
  28.         for y in range(height):
  29.             for x in range(width):
  30.                 # 获取当前像素的灰度值
  31.                 gray_value = pixels[x, y]
  32.                 # 小于阈值的像素点改成黑色,大于阈值的像素点改成白色
  33.                 if gray_value < threshold:
  34.                     pixels[x, y] = 0
  35.                 else:
  36.                     pixels[x, y] = 255
  37.         # 将转换的二值图片加入列表
  38.         img_list.append(image)
  39.     # 将图片列表合并为一个pdf文件,resolution取值越大,pdf文件页面就可以放大更多倍数而不出现锯齿
  40.     img_list[0].save(f'test_{threshold}.pdf','PDF', resolution=100.0,
  41.                      save_all=True, append_images=img_list[1:])
  42. except Exception as e:
  43.     print(e)
  44. pdf_pages.close()
复制代码
从本文图1看以上程序转换所得的页面效果照旧有较大瑕疵,主要表如今有些文字笔画残缺,有些笔画互相粘黏,看起来挤成一团。如果在转换成二值图片前现对原图片进行自适应对比度增强,虽然会导致图像中增长一些噪点,但是整体阅读效果更好,如下图:

上图与图1比较文字显然更清晰,虽然多了一些噪点,也不影响阅读(利用OpenCV的中值滤波、高斯滤波或双边滤波对图片除噪点处理后效果反而变差)。用pdf编辑工具(如pdf24)将pdf文件逐页输出为图片保存到某个文件夹中,下面的程序可以将图片进行自适应对比度增强,然后转换成二值图片保存到另一文件夹中:
  1. import cv2
  2. import os
  3. from PIL import Image
  4. import numpy as np
  5. def get_variance_mean(src_img, win_size):
  6.     if src_img is None or win_size is None:
  7.         print("函数参数错误。")
  8.         return -1
  9.     if win_size % 2 == 0:
  10.         print("win_size参数应传入奇数。")
  11.         return -1
  12.     copyBorder_map = cv2.copyMakeBorder(src_img, win_size // 2, win_size // 2,
  13.             win_size // 2, win_size // 2, cv2.BORDER_REPLICATE)
  14.     shape = np.shape(src_img)
  15.     local_mean = np.zeros_like(src_img)
  16.     local_std = np.zeros_like(src_img)
  17.     for i in range(shape[0]):
  18.         for j in range(shape[1]):
  19.             temp = copyBorder_map[i:i + win_size, j:j + win_size]
  20.             # 计算均值和标准差
  21.             mean_val, std_dev_val = cv2.meanStdDev(temp)
  22.             # 提取均值和标准差的标量值
  23.             local_mean[i, j] = mean_val[0, 0]
  24.             local_std[i, j] = std_dev_val[0, 0]
  25.     return local_mean, local_std
  26. # 自适应对比度增强
  27. def adapt_contrast_enhancement(src_img, win_size, max_cg, rgb=True):
  28.     if src_img is None or win_size is None or max_cg is None:
  29.         print("函数参数错误。")
  30.         return -1
  31.     # 转换通道
  32.     YUV_img = cv2.cvtColor(src_img, cv2.COLOR_BGR2YUV)  
  33.     Y_Channel = YUV_img[:, :, 0]
  34.     shape = np.shape(Y_Channel)
  35.     meansGlobal = cv2.mean(Y_Channel)[0]
  36.     localMean_map, localStd_map = get_variance_mean(Y_Channel, win_size)
  37.     for i in range(shape[0]):
  38.         for j in range(shape[1]):
  39.             is_zero = localStd_map[i, j] == 0
  40.             # 当分母localStd_map[i, j]为0时加上极小值1e-8,防止除以0错误
  41.             cg = 0.2 * meansGlobal / (localStd_map[i, j]+(1e-8)*is_zero)
  42.             if cg > max_cg:
  43.                 cg = max_cg
  44.             elif cg < 1:
  45.                 cg = 1
  46.             temp = Y_Channel[i, j].astype(float)
  47.             temp = max(0, min(localMean_map[i, j] + cg * (temp - localMean_map[i, j]), 255))
  48.             Y_Channel[i, j] = temp
  49.     YUV_img[:, :, 0] = Y_Channel
  50.     if rgb:
  51.         return cv2.cvtColor(YUV_img, cv2.COLOR_YUV2RGB)
  52.     else:
  53.         return cv2.cvtColor(YUV_img, cv2.COLOR_YUV2BGR)
  54. if __name__ == '__main__':
  55.     folder_path = "H:\\Download\\huanmie"
  56.     output_path = "H:\\Download\\huanmie\\output"
  57.     # 遍历文件夹中的所有文件
  58.     for filename in os.listdir(folder_path):
  59.         if filename.endswith(('.jpg', '.jpeg', '.png')):
  60.             img = cv2.imread(os.path.join(folder_path, filename))
  61.             # 自适应对比度增强
  62.             dest_img = adapt_contrast_enhancement(img, 5, 10, False)
  63.             # 转换为灰度图片
  64.             gray_image = cv2.cvtColor(dest_img, cv2.COLOR_BGR2GRAY)
  65.             # 设定阈值,可根据实际情况调整
  66.             threshold_value = 188
  67.             max_value = 255
  68.             # 图片二值化
  69.             _, binary_image = cv2.threshold(gray_image, threshold_value,
  70.                         max_value, cv2.THRESH_BINARY)
  71.             # 将色彩模式转换成pillow的Image的色彩模式
  72.             # binary_image_rgb = cv2.cvtColor(binary_image,cv2.COLOR_GRAY2RGB)
  73.             # 构造pillow的Image
  74.             image = Image.fromarray(binary_image)
  75.             # 转换成灰度图片
  76.             image = image.convert('L')
  77.             pixels = image.load()
  78.             # 获取图片的宽度和高度
  79.             width, height = image.size
  80.             # 消除ksize个像素大小的噪点及灰色噪点
  81.             ksize = 1
  82.             for y in range(ksize,height-ksize):
  83.                 for x in range(ksize,width -ksize):
  84.                     if ((pixels[x-ksize,y] == 255 and pixels[x+ksize,y] == 255 and
  85.                         pixels[x,y-ksize] == 255 and pixels[x,y+ksize] == 255)
  86.                       or pixels[x, y] > 100):
  87.                         pixels[x, y] = 255
  88.             # 中值滤波消除噪点,容易导致文字笔画残缺
  89.             # image = cv2.medianBlur(np.array(image),3)
  90.             # image = Image.fromarray(image)
  91.             # 保存转换后的图片
  92.             image.save(os.path.join(output_path, filename))
  93.             print(f"已完成 {filename}的 转换。")
  94.     # 设置响铃频率(赫兹)和持续时间(毫秒),转换完成后响铃提示
  95.     frequency = 1000
  96.     duration = 500
  97.     os.system(f'Beep {frequency} {duration}')
  98.     print('Done!')
复制代码
代码中自适应对比度调解算法详情请参阅CSDN博主不消先生的文章:【图像处理】彩色图像自适应对比度增强(OpenCV实现)-CSDN博客,本文仅对该文的算法代码做了少量修改,消除了numpy版本升级后关于多维数组直接转换为标量引发的操纵弃用警告以及计算cg时的除0错误警告。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表