PDF文档广泛用于业务流程。数字创建的 PDF 使用起来非常方便。可以搜索、突出显示和批注文本。不幸的是,许多PDF是通过扫描图像或将图像转换为PDF来创建的。这些 PDF 中没有数字文本,因此无法搜索它们。在这篇博文中,我们演示了如何使用简单易用的代码和 Azure 表单识别器将此类 PDF 转换为可搜索的 PDF。

Azure 表单识别器概述

Azure 表单识别器是一种基于云的 Azure 应用 AI 服务,它使用深度机器学习模型从文档中提取文本、键值对、表和表单字段。在这篇博文中,我们将使用表单识别器提取的文本将其添加到 PDF 中,使其可搜索。

可搜索与不可搜索的 PDF

如果PDF包含文本信息,用户可以选择,复制/粘贴,注释PDF中的文本。在可搜索的PDF(示例)中,可以搜索和选择文本,请参阅下面的文本突出显示:

image

如果 PDF 是基于图像的(示例),则无法搜索或选择文本。图像压缩伪像通常通过放大在文本周围看到:

image2

如何生成可搜索的 PDF

PDF 包含不同类型的元素:文本、图像等。基于图像的 PDF 仅包含图像元素。此博客的目标是将不可见的文本元素添加到 PDF 中,以便用户可以搜索和选择这些元素。它们是不可见的,以确保生成的可搜索PDF看起来与原始PDF相同。在下面的示例中,现在可以使用不可见的文本图层选择单词“Transition”:

image3

预安装要求

请在运行可搜索的 pdf 脚本之前安装以下软件包:

  1. Python 包:
1
pip install azure-ai-formrecognizer pypdf2>=3.0 reportlab pillow pdf2image​​
  1. 软件包 pdf2image 需要 Poppler 安装。请按照说明 https://pypi.org/project/pdf2image/ 根据您的 平台或使用Conda安装:
1
conda install -c conda-forge poppler

如何运行可搜索的 PDF 脚本

  1. 使用以下代码创建一个 Python 文件,并将其作为fr_generate_searchable_pdf.py保存在本地计算机上。

  2. 使用 Azure 门户表单识别器实例中的值更新密钥和终结点变量(有关更多详细信息,请参阅快速入门:表单识别器 SDK)。

  3. 执行脚本并将输入文件(pdf或图像)作为参数传递:

    1
    
    python fr_generate_searchable_pdf.py <input.pdf/jpg>
    

    示例脚本输出如下:

    1
    2
    3
    4
    5
    6
    
    (base) C:\temp>python fr_generate_searchable_pdf_v1.1_with_key.py input.jpg
    Loading input file input.jpg
    Starting Azure Form Recognizer OCR process...
    Azure Form Recognizer finished OCR text for 1 pages.
    Generating searchable PDF...
    Searchable PDF is created: input.jpg.ocr.pdf
    
  4. 脚本生成后缀为 .ocr.pdf 的可搜索 PDF 文件。

可搜索的 PDF Python 脚本

复制下面的代码并在本地计算机上创建 Python 脚本。该脚本将扫描的 PDF 或图像作为输入,并使用表单识别器生成相应的可搜索 PDF 文档,该文档将可搜索图层添加到 PDF,并使您能够搜索、复制、粘贴和访问 PDF 中的文本。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# Script to create searchable PDF from scan PDF or images using Azure Form Recognizer
# Required packages
# pip install azure-ai-formrecognizer pypdf2 reportlab pillow pdf2image
import sys
import io
import math
import argparse
from pdf2image import convert_from_path
from reportlab.pdfgen import canvas
from reportlab.lib import pagesizes
from reportlab import rl_config
from PIL import Image, ImageSequence
from PyPDF2 import PdfWriter, PdfReader
from azure.core.credentials import AzureKeyCredential
from azure.ai.formrecognizer import DocumentAnalysisClient

# Please provide your Azure Form Recognizer endpoint and key
endpoint = YOUR_FORM_RECOGNIZER_ENDPOINT
key = YOUR_FORM_RECOGNIZER_KEY

def dist(p1, p2):
    return math.sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y))

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('input_file', type=str, help="Input PDF or image (jpg, jpeg, tif, tiff, bmp, png) file name")
    parser.add_argument('-o', '--output', type=str, required=False, default="", help="Output PDF file name. Default: input_file + .ocr.pdf")
    args = parser.parse_args()

    input_file = args.input_file
    if args.output:
        output_file = args.output
    else:
        output_file = input_file + ".ocr.pdf"

    # Loading input file
    print(f"Loading input file {input_file}")
    if input_file.lower().endswith('.pdf'):
        # read existing PDF as images
        image_pages = convert_from_path(input_file)
    elif input_file.lower().endswith(('.tif', '.tiff', '.jpg', '.jpeg', '.png', '.bmp')):
        # read input image (potential multi page Tiff)
        image_pages = ImageSequence.Iterator(Image.open(input_file))
    else:
        sys.exit(f"Error: Unsupported input file extension {input_file}. Supported extensions: PDF, TIF, TIFF, JPG, JPEG, PNG, BMP.")

    # Running OCR using Azure Form Recognizer Read API 
    print(f"Starting Azure Form Recognizer OCR process...")
    document_analysis_client = DocumentAnalysisClient(endpoint=endpoint, 
    credential=AzureKeyCredential(key), headers={"x-ms-useragent": "searchable-pdf-blog/1.0.0"})

    with open(input_file, "rb") as f:
        poller = document_analysis_client.begin_analyze_document("prebuilt-read", document = f)

    ocr_results = poller.result()
    print(f"Azure Form Recognizer finished OCR text for {len(ocr_results.pages)} pages.")

    # Generate OCR overlay layer
    print(f"Generating searchable PDF...")
    output = PdfWriter()
    default_font = "Times-Roman"
    for page_id, page in enumerate(ocr_results.pages):
        ocr_overlay = io.BytesIO()

        # Calculate overlay PDF page size
        if image_pages[page_id].height > image_pages[page_id].width:
            page_scale = float(image_pages[page_id].height) / pagesizes.letter[1]
        else:
            page_scale = float(image_pages[page_id].width) / pagesizes.letter[1]

        page_width = float(image_pages[page_id].width) / page_scale
        page_height = float(image_pages[page_id].height) / page_scale

        scale = (page_width / page.width + page_height / page.height) / 2.0
        pdf_canvas = canvas.Canvas(ocr_overlay, pagesize=(page_width, page_height))

        # Add image into PDF page
        pdf_canvas.drawInlineImage(image_pages[page_id], 0, 0, width=page_width, 
        height=page_height, preserveAspectRatio=True)

        text = pdf_canvas.beginText()
        # Set text rendering mode to invisible
        text.setTextRenderMode(3)
        for word in page.words:
            # Calculate optimal font size
            desired_text_width = max(dist(word.polygon[0], word.polygon[1]), 
            dist(word.polygon[3], word.polygon[2])) * scale
            desired_text_height = max(dist(word.polygon[1], word.polygon[2]), 
            dist(word.polygon[0], word.polygon[3])) * scale
            font_size = desired_text_height
            actual_text_width = pdf_canvas.stringWidth(word.content, default_font, font_size)
            
            # Calculate text rotation angle
            text_angle = math.atan2((word.polygon[1].y - word.polygon[0].y 
            + word.polygon[2].y - word.polygon[3].y) / 2.0, 
                                    (word.polygon[1].x - word.polygon[0].x 
                                    + word.polygon[2].x - word.polygon[3].x) / 2.0)
            text.setFont(default_font, font_size)
            text.setTextTransform(math.cos(text_angle), -math.sin(text_angle), 
            math.sin(text_angle), math.cos(text_angle), word.polygon[3].x * scale, 
            page_height - word.polygon[3].y * scale)
            text.setHorizScale(desired_text_width / actual_text_width * 100)
            text.textOut(word.content + " ")

        pdf_canvas.drawText(text)
        pdf_canvas.save()

        # Move to the beginning of the buffer
        ocr_overlay.seek(0)

        # Create a new PDF page
        new_pdf_page = PdfReader(ocr_overlay)
        output.add_page(new_pdf_page.pages[0])

    # Save output searchable PDF file
    with open(output_file, "wb") as outputStream:
        output.write(outputStream)

    print(f"Searchable PDF is created: {output_file}")