• Feed
  • Explore
  • Ranking
/

    PDF 를 TEXT로 변환하기

    pdf 문서 text로 쉽게 변환하기
    z
    zzoming
    2025.03.20
    ·
    9 min read

    pdf를 text로 변환하기 위해서 marker-pdf , MarkItDown 등 여러가지를 써봤는데 최종적으로는 olmocr을 사용하기로 한다.

    4224

    그 이유는 다음과 같다.

    • marker-pdf

      • PDF를 마크다운형식으로 굉장히 잘 가져온다고 판단했다. 하지만 표에 있는 내용을 모두 들고오지 못하는 문제점을 보였다. (아래 마크다운 참고)

        #### <표 1> 작업대차 사용 중 위험요인 및 안전대책
        | 감전       | 손상에의한감전재해                | 및작동여부확인                           |
        | 재해       |                                  |                                           |
    • markitdown

      • 사용해보신 분들은 알겠지만 속도가 매우 빠르다.

      • 다만, 내가 어떤 부분의 설정을 잘 못한지 모르겠지만 한국어가 인식이 되지 않았다. 이상한 언어들이 출력되는 것을 보여서 일단 보류했다.

    • olmocr

      • 데모페이지로 실험을 해봤는데 표도 잘 들고오고, 깔끔하게 텍스트를 추출함을 보여서 해당 라이브러리를 사용하기로 했다.

      • 심지어 해당 라이브러리는 코드가 완전히 공개되어 있다는 사실.. !!

      | 발생 형태 | 재해발생 위험요인 | 안전 대책 |
      |----------|-----------------|----------|
      | 공통 | 근로자의 불안전한 행동으로 인한 재해발생 | 관리감독자의 선임, 근로자의 안전보건에 관한 교육 실시, 안전수칙 준수 |
      | 추락 | 작업발판에서 해체작업 중 추락 | 작업발판 주위에 안전난간 설치 |
      | | 작업발판에서 작업 중 불시 이동에 의한 추락 | 작업자가 작업발판에 탑승한 채로 이동금지 |
      | | 작업대차(캔틸레버슬래브 해체형)의 불시 이동에 의한 추락 | 작업발판 바퀴에 구름방지장치 설치 |
      | | 하부작업대로 이동 중 추락 | 작업대차 바퀴에 구름방지장치 설치 |
      | 낙하 | 해체된 거푸집 인양 중 자재 낙하 | 작업자가 대차에 탑승한 채로 이동금지 |
      | | 거푸집해체중 거푸집, 작업 공구 등의 낙하 | 작업대차의 안전통로 확보 (등반이 형 수직사다리, 안전난간 설치 등) |
      | 붕괴 | 부재의 결함, 누락으로 인한 붕괴 | 작업시작 전 작업대차의 주요 구조부위, 연결 부위 등 점검실시 |
      | | 작업발판의 과부하로 인한 작업대 붕괴 | 작업 전 과부하 방지장치의 이상유무 확인 및 작업발판 상승 시 구조물에 걸림 여부 확인 |
      | 감전 | 이동용 전동공구 및 전선의 손상에 의한 감전재해 | 전원 인출 분전반에 누전차단기 부착 및 작동여부 확인 |

    toolkit를 이용하면 편리하게 사용할 수 있다고 하는데 로컬PC에 GPU가 없는 관계로 Google Colab에서 진행했다.

    allenai/olmOCR-7B-0225-preview · Hugging Face
    We’re on a journey to advance and democratize artificial intelligence through open source and open science.
    https://huggingface.co/allenai/olmOCR-7B-0225-preview
    allenai/olmOCR-7B-0225-preview · Hugging Face

    패키지 설치

    !pip install olmocr PyPDF2
    !sudo apt-get update
    !sudo apt-get install poppler-utils

    필요한 라이브러리 import

    import torch
    import base64
    import urllib.request
    
    from io import BytesIO
    from PIL import Image
    from transformers import AutoProcessor, Qwen2VLForConditionalGeneration
    
    from olmocr.data.renderpdf import render_pdf_to_base64png
    from olmocr.prompts import build_finetuning_prompt
    from olmocr.prompts.anchor import get_anchor_text

    Model Load

    # Initialize the model
    model = Qwen2VLForConditionalGeneration.from_pretrained("allenai/olmOCR-7B-0225-preview", torch_dtype=torch.bfloat16).eval()
    processor = AutoProcessor.from_pretrained("Qwen/Qwen2-VL-7B-Instruct")
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model.to(device)

    OCR → TEXT

    이제 PDF 한 페이지를 OCR 처리하여 텍스트를 추출하는 파이프라인을 구현한다.

    1. PDF의 특정 페이지를 이미지로 변환

      • render_pdf_to_base64png() 함수를 사용하여 PDF 페이지를 이미지(.png)로 변환한 후, Base64로 인코딩함.

      • target_longest_image_dim=1024 옵션을 사용하여 최대 크기를 1024px로 설정.

    2. PDF 문서의 메타데이터 활용하여 프롬프트 생성

      • get_anchor_text() 함수로 PDF의 특정 부분(예: 첫 번째 페이지)에서 최대 4000자까지 텍스트를 추출.

      • build_finetuning_prompt() 함수를 이용해 OCR 모델에 입력할 프롬프트를 생성.

    3. OCR 모델 입력 데이터 생성

      • OCR 모델이 처리할 입력 데이터를 messages 리스트에 저장.

      • 프롬프트(prompt)와 변환된 이미지(image_base64)를 포함.

      • apply_chat_template() 함수로 채팅 형식의 템플릿을 적용.

      • Base64로 인코딩된 이미지를 디코딩하여 PIL.Image 객체로 변환.

    4. 모델 입력 데이터 변환 및 GPU 적용

      • processor() 함수를 사용해 OCR 모델이 이해할 수 있는 형식으로 변환.

    5. 텍스트 생성 및 디코딩

    def ocr_pdf_page(pdf_path, page_number):
        
        # Render page 1 to an image 
        image_base64 = render_pdf_to_base64png(pdf_path, page_number, target_longest_image_dim=1024)
    
        # Build the prompt, using document metadata 
        anchor_text = get_anchor_text(pdf_path, 1, pdf_engine="pdfreport", target_length=4000)
        prompt = build_finetuning_prompt(anchor_text)
    
        # Build the full prompt
        messages = [
                    {
                        "role": "user",
                        "content": [
                            {"type": "text", "text": prompt},
                            {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}},
                        ],
                    }
                ]
    
        # Apply the chat template and processor
        text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
        main_image = Image.open(BytesIO(base64.b64decode(image_base64)))
    
        inputs = processor(
            text=[text],
            images=[main_image],
            padding=True,
            return_tensors="pt",
        )
        inputs = {key: value.to(device) for (key, value) in inputs.items()}
    
    
        # Generate the output
        output = model.generate(
                    **inputs,
                    temperature=0.8,
                    max_new_tokens=2048, # 생성 토큰 설정 
                    num_return_sequences=1,
                    do_sample=True,
                )
    
        # Decode the output
        prompt_length = inputs["input_ids"].shape[1]
        new_tokens = output[:, prompt_length:]
        text_output = processor.tokenizer.batch_decode(
            new_tokens, skip_special_tokens=True
        )
    
        print(text_output)
        return text_output[0]

    신경써야할 부분은 max_new_token이 허깅페이스 코드에는 50으로 설정되어있는데 PDF 문서 내 테텍스트 숫자가 50 이상이면 텍스트 출력이 잘리게 된다. 그러니 넉넉하게 주도록 할 것!

    allenai/olmOCR-7B-0225-preview · Hugging Face
    We’re on a journey to advance and democratize artificial intelligence through open source and open science.
    https://huggingface.co/allenai/olmOCR-7B-0225-preview
    allenai/olmOCR-7B-0225-preview · Hugging Face
    
    from PyPDF2 import PdfReader
    
    def get_pdf_page_count(pdf_path):
        """Gets the number of pages in a PDF file using PyPDF2.
    
        Args:
            pdf_path: The path to the PDF file.
    
        Returns:
            The number of pages in the PDF, or None if an error occurs.
        """
        try:
            pdf = PdfReader(pdf_path)
            return len(pdf.pages)
        except Exception as e:
            print(f"Error using PyPDF2: {e}")
            return None
    
    page_count = get_pdf_page_count(pdf_list[0])
    
    if page_count:
      print(f"The PDF has {page_count} pages.")
    
    # output
    The PDF has 13 pages.

    해당 PDF 가 몇 페이지로 구성되어 있는 출력하는 코드이다.

    for idx , pdf_file in enumerate(pdf_list) : 
        
        FILE_PATH = pdf_file
        NUM_PAGES = get_pdf_page_count(FILE_PATH)
    
        pages = []
    
        for page_idx in range(NUM_PAGES):
            print(f"Processing page {page_idx+1} of {NUM_PAGES}")
            page_json = ocr_pdf_page(FILE_PATH, page_idx+1)
            pages.append(page_json)
    
        file_name = os.path.splitext(os.path.basename(FILE_PATH))[0] # 파일 이름 
        save_path = './pdf2txt/' # 마크다운 파일 저장할 경로 설정 
        output_file = os.path.join(save_path, file_name + ".md") #전체 경로 
    
        # 파일 열기 (쓰기 모드)
        with open(output_file, "w", encoding="utf-8") as f:
            
            for page_json in pages:
                try:
                    data = json.loads(page_json)
                    f.write(f"{data['natural_text']}\n\n") 
                except Exception as e:
                    f.write(f"Error processing page_json: {str(e)}\n\n") 
            
        print(f"{idx+1}/{len(pdf_list)} 문서 변환 완료 : Data saved to {output_file}")

    pdf_list에 pdf 을 넣어놓고, 하나씩 꺼내면서 md 확장자로 저장하는 코드이다. 근데 사실 텍스트만 추출되서 txt 확장자로 저장하는 것이 더 좋을 거 같다.

    4171

    마크다운이 잘 저장되는 것을 확인할 수 있다.

    Reference

    https://www.youtube.com/watch?v=38loqDtlLok

    https://olmocr.allenai.org/papers/olmocr.pdf