网上找到的一些课件讲义等资料有时候会是 PPT 多页打印而成的 PDF 文件,比如下图这种:

有些人希望对 PDF 页面进行裁剪,将 PDF 还原为原 slides 那样一页一张演示文稿的形式。(其实我个人觉得没什么必要,因为不影响阅读,而且 PDF 格式读起来还不用频繁翻页了。)
前几天,我在知乎上看到了有这样需求的 一个问题 。
当时想到用 python 和 OpenCV 来做这样的图像处理小任务应该很简单的吧。于是动手撸了段代码,简单的 “边缘检测+轮廓提取” ,最后结果看起来还不错:

下面给出 Python 代码并简单解释下。
1. 边缘检测新建一个文件 pdfcrop.py ,加入如下代码:
importnumpyasnp importargparse importcv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-i","--image", help="path to the image file") args = vars(ap.parse_args()) # 读取图片,进行边缘检测 image = cv2.imread(args["image"]) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5,5),0) edged = cv2.Canny(gray, 75,200) # 显示原图和边缘图像 cv2.imshow("Original Image", image) cv2.imshow("Edged Image", edged) cv2.waitKey() cv2.destroyAllwindows()假设你的图片文件为 test.png ,执行:
pythonpdfcrop.py-itest.png边缘检测结果:

2. 轮廓提取
这里的思路很简单,我们要找的轮廓是矩形的(有四个顶点),而且应该是面积最大的前 N 个(上图N = 6)轮廓。使用 Python 和 OpenCV 实现起来也是非常简单,继续编辑 pdfcrop.py :
# 轮廓提取 (cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) cnts = sorted(cnts, key=cv2.contourArea, reverse=True) poly_contours = [] forcincnts[0:6]: peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02* peri,True) iflen(approx) ==4: poly_contours.append(approx) contours_image = image.copy() forcontoursinpoly_contours: cv2.drawContours(contours_image, [contours], -1, (0,255,0),2) # 显示轮廓图像 cv2.imshow("Contours", contours_image) cv2.waitKey() cv2.destroyAllWindows()轮廓提取的结果:

3. 获取轮廓内的图像
最后只要保存上一步中检测到的矩形轮廓内部的图像就好了,在 pdfcrop.py 中加入:
# 分别显示矩形轮廓内的图像 index = 0 forcontoursinpoly_contours: rect = cv2.boundingRect(contours) cv2.imshow("test"+ str(index), image[rect[1]:rect[1]+rect[3], rect[0]:rect[0]+rect[2]]) index += 1 cv2.waitKey() cv2.destroyAllWindows()上面的代码只能对图片文件进行处理,不能直接处理 PDF ,想要使用的话可以考虑利用其他软件先将 PDF 批量转换成图片文件。(或者找一找 Python 处理 PDF 的库?)
还有一个问题就是裁剪后 slides 的顺序,这里我没有处理,其实做起来也很简单,将提取出的矩形轮廓按照位置坐标进行排序就好了。