Batch Extraction of Specified Fields from Images Using Python and Local Large Models to Enhance Audit Efficiency

Applying AI in Audit Work, Starting with Small Scenarios. There are many methods to achieve the same goal, including direct Q&A, using Coze, n8n, Yingdao, or calling locally deployed or online large models with Python.

Today, I will demonstrate the method of extracting specified information from images in audit work using Python and Ollama.

1. Addressing Pain Points: The “Nightmare of Image Information Extraction” in Audit Work

Those involved in auditing, finance, or project management will surely understand this pain:

  • A pile of acceptance forms, scanned contracts, and reimbursement vouchers require manual entry of fields such as “contract number, acceptor, quantity”.

  • Images are blurry and inconsistent in format, OCR recognition misses characters and makes errors, checking each image is time-consuming and labor-intensive.

  • Urgent projects require speed, manually processing dozens or hundreds of images often leads to working late into the night.

I have tried various tools before:

  • Online OCR tools: Limited upload per session, sensitive data risks exposure.

  • Paid software: Costly per use, weak batch processing capabilities.

  • Traditional Python scripts: Dependence on third-party APIs, unstable and costly.

Until the explosion of local large models, deploying qwen3-vl:8b multimodal large model with Ollama, combined with Python to achieve local batch extraction of image fields, running entirely offline, ensuring data security and improving efficiency!

2. Technology Selection: Why Ollama + qwen3-vl:8b?

1. Core Tool Combination

Tool / Model Function Core Advantages
Ollama Local large model deployment platform Lightweight, one-click deployment, supports visual models
qwen3-vl:8b Qwen multimodal visual and language large model 8 billion parameters, strong image understanding, supports Chinese OCR
Python Batch processing + Excel output Flexible expansion, adaptable to various folders/formats
Pillow Image preprocessing Improves recognition accuracy of blurry images
Pandas Data structuring Quickly generates standard Excel reports

2. Why Abandon Traditional Solutions?

  • Online APIs: Dependence on the internet, risk of sensitive audit data exposure.

  • Commercial software: High costs, weak customization capabilities.

  • Small parameter models: Local deployment is stress-free, even a laptop can run (8GB RAM is sufficient).

3. Core Logic: 3 Steps to Achieve Batch Extraction of Image Fields

1. Overall Process Breakdown

Batch Extraction of Specified Fields from Images Using Python and Local Large Models to Enhance Audit Efficiency

2. Key Technical Details (Core Highlights of the Code)

(1) Image Preprocessing: Accurate Recognition of Blurry Images

To address common issues in audit images such as “scanned blur, insufficient brightness”, enhancement logic is added:

  • Automatically convert image mode (RGBA→RGB) to solve transparent background recognition issues.

  • Contrast + 50%, Brightness + 20%, to enhance text clarity.

  • Lanczos algorithm Resize, maintaining aspect ratio without distortion.

  • Supports various formats such as JPG/PNG/BMP/GIF.

(2) Dual-Prompt Strategy: Doubling Accuracy
  • First round: Full OCR extraction, leaving no text behind (temperature 0.0, ensuring objectivity).

  • Second round: Structured parsing, strictly outputting target fields in JSON format.

  • Fallback backup plan: Directly ask questions about the image to avoid OCR failure.

(3) Fault Tolerance Mechanism: Maximizing Stability
  • Automatically detect if the Ollama service is running.

  • Automatically pull missing models (qwen3-vl:8b).

  • Automatically clean up extra characters on JSON parsing failure.

  • Return empty strings for missing fields, ensuring Excel generation is unaffected.

4. Practical Steps

1. Environment Preparation (3 Steps to Complete)

(1) Install Ollama
  • Download from the official website: https://ollama.com/

  • Start the service: Enter the command <span>ollama serve</span> (keep the window open).

(2) Deploy the Visual Model

Execute in the command line:

ollama pull qwen3-vl:8b  # Recommended 8G RAM, 16G RAM can try qwen3-vl:14b
(3) Install Python Dependencies
pip install pandas pillow openpyxl ollama

2. Code Configuration (2 Modifications Required)

# Only need to change these 2 lines!

IMAGE_FOLDER = "path_to_your_image_folder"  # e.g., "D:/audit_images/2024_acceptance_forms"

OUTPUT_FILE = "extraction_results.xlsx"  # Output Excel file name

3. Running Effect Demonstration

(1) Processing Log
Testing Ollama connection...

✓ Successfully connected to Ollama service

✓ Found model: qwen3-vl:8b

Validating model capabilities...

✓ Model visual capability validation passed

Found 28 images, starting processing...

=== Processing image 1/28: acceptance_form_001.jpg ===

Text content in the image:

Project: XX Park Renovation Project

Name: LED Display

Contract Number: JY-2024-0328

Model Specification: P2.55m×3m

Quantity: 2 units

Situation Description: Equipment operates normally, meets acceptance standards

Acceptance Unit: XX Construction Engineering Co., Ltd.

Acceptor: Zhang San

Acceptance Date: May 18, 2024

Structured extraction result: {"Project":"XX Park Renovation Project","Name":"LED Display","Contract Number":"JY-2024-0328","Model Specification":"P2.55m×3m","Quantity":"2 units","Situation Description":"Equipment operates normally, meets acceptance standards","Acceptance Unit":"XX Construction Engineering Co., Ltd.","Acceptor":"Zhang San","Acceptance Date":"May 18, 2024"}

Processing complete! Results saved to extraction_results.xlsx

Successfully extracted information from images: 28/28
(2) Excel Output Effect
Image File Name Project Name Contract Number Model Specification Quantity Acceptor Acceptance Date
acceptance_form_001.jpg XX Park Renovation Project LED Display JY-2024-0328 P2.55m×3m 2 units Zhang San May 18, 2024
acceptance_form_002.jpg Office Equipment Procurement Laptop CG-2024-0415 ThinkPad X1 10 units Li Si June 2, 2024

5. Code Optimization Highlights (Advanced Techniques)

1. Image Enhancement: Solving Blurry Image Recognition Issues

def enhance_image(image):

   # Contrast +50%, Brightness +20%, slight sharpening

   enhancer = ImageEnhance.Contrast(image)
   image = enhancer.enhance(1.5)

   enhancer = ImageEnhance.Brightness(image)
   image = enhancer.enhance(1.2)

   image = image.filter(ImageFilter.SHARPEN)

   return image

2. Dual-Prompt: Improving Structured Extraction Accuracy

  • First round: Full OCR, leaving no text behind.

  • Second round: Based on OCR results, enforce JSON format output.

  • Fault tolerance: Automatically clean up extra characters in JSON to avoid parsing failures.

3. Batch Processing + Retry on Failure

  • Automatically traverse all images in the folder (supports multiple formats).

  • Main strategy failure automatically switches to backup plan.

  • Detailed log output for easy troubleshooting.

4. Data Security: Entirely Local Operation

  • No image uploads to the cloud, sensitive data remains secure.

  • Supports offline use, can work without internet.

6. Common Problem Troubleshooting

1. Ollama Connection Failed?

  • Check if the service is running: Enter the command <span>ollama serve</span>

  • Ensure the model has been pulled:<span>ollama pull qwen3-vl:8b</span>

  • Disable VPN, ensure local network is normal.

2. Low Recognition Accuracy?

  • Image preprocessing: Ensure images are clear, manually crop irrelevant areas if necessary.

  • Adjust prompts: Clearly specify field formats in structured prompts (e.g., acceptance date must be in YYYY-MM-DD format).

  • Upgrade model: 16GB RAM can try qwen3-vl:14b.

3. Excel Save Failed?

  • Install dependencies:<span>pip install openpyxl</span>

  • Close any open Excel files.

  • Check if the output path has write permissions.

7. Extended Scenarios: Beyond Auditing

This solution can also be used for:

  • Financial reimbursements: Extract invoice information (amount, invoice date, seller).

  • Human resources: Extract key information from resumes (name, phone, work experience).

  • Education: Extract exam questions, student scores.

  • Administrative office: Extract key contract terms (party A, party B, validity period).

Simply modify the <span>structured_prompt</span><span> fields to adapt to different scenarios!</span>

8. Complete Code

import os
import json
import pandas as pd
from PIL import Image, ImageEnhance, ImageFilter
import io
import ollama
import traceback

def enhance_image(image):
    """Enhance image clarity, improve recognition effect"""
    # Adjust contrast and brightness
    enhancer = ImageEnhance.Contrast(image)
    image = enhancer.enhance(1.5)
    
    enhancer = ImageEnhance.Brightness(image)
    image = enhancer.enhance(1.2)
    
    # Slight sharpening
    image = image.filter(ImageFilter.SHARPEN)
    
    return image

def process_image_for_model(image_path):
    """Preprocess image and return binary data"""
    try:
        with Image.open(image_path) as img:
            # Convert to RGB mode
            if img.mode in ('RGBA', 'LA'):
                background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
                background.paste(img, mask=img.split()[-1])
                img = background
            elif img.mode == 'P':
                img = img.convert('RGB')
            else:
                img = img.convert('RGB')
            
            # Image enhancement
            img = enhance_image(img)
            
            # Resize (maintaining aspect ratio, fixed width of 1200px)
            width, height = img.size
            new_width = 1200
            new_height = int(height * (new_width / width))
            img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
            
            # Save as high-quality JPEG
            buffer = io.BytesIO()
            img.save(buffer, format='JPEG', quality=95, optimize=True)
            return buffer.getvalue()
    except Exception as e:
        print(f"Image preprocessing failed {image_path}: {str(e)}")
        return None

def extract_info_with_multiple_prompts(image_path, model="qwen3-vl:8b"):
    """Multi-round prompt strategy for information extraction"""
    image_data = process_image_for_model(image_path)
    if not image_data:
        return None

    # First round: Extract all text content (OCR mode)
    ocr_prompt = """Please recognize all text content in the image, output line by line, do not omit any information."""
    
    try:
        # Step 1: Get all text in the image
        ocr_response = ollama.generate(
            model=model,
            prompt=ocr_prompt,
            images=[image_data],
            stream=False,
            options={"temperature": 0.0}
        )
        
        all_text = ocr_response.get("response", "").strip()
        print(f"\nText content in the image:\n{all_text}\n")
        
        # Second round: Structured parsing based on extracted text
        structured_prompt = f"""The following is the text content in the image:\n{all_text}\n\nPlease extract the following field information from the above text, strictly return in JSON format:\n- Project\n- Name\n- Contract Number\n- Model Specification\n- Quantity\n- Situation Description\n- Acceptance Unit\n- Acceptor\n- Acceptance Date\n\nIf a field does not exist or cannot be recognized, return an empty string for the corresponding value. Only return JSON, do not add any other content.\nExample output: {{"Project":"","Name":"","Contract Number":"","Model Specification":"","Quantity":"","Situation Description":"","Acceptance Unit":"","Acceptor":"","Acceptance Date":""}}"""
        
        structured_response = ollama.generate(
            model=model,
            prompt=structured_prompt,
            stream=False,
            options={"temperature": 0.0}
        )
        
        raw_result = structured_response.get("response", "").strip()
        print(f"Structured extraction result: {raw_result}")
        
        # Parse JSON
        if raw_result:
            # Clean up possible extra characters
            if not raw_result.startswith('{'):
                raw_result = '{' + raw_result.split('{')[-1]
            if not raw_result.endswith('}'): 
                raw_result = raw_result.split('}')[0] + '}'
            
            result = json.loads(raw_result)
            result["Image File Name"] = os.path.basename(image_path)
            result["Original Text"] = all_text[:500]  # Save part of the original text for debugging
            return result
            
    except json.JSONDecodeError as e:
        print(f"JSON parsing failed: {str(e)}")
        print(f"Original response: {raw_result}")
    except Exception as e:
        print(f"Extraction failed: {str(e)}")
        traceback.print_exc()
    
    return None

def fallback_extract(image_path, model="qwen3-vl:8b"):
    """Backup extraction strategy: directly use detailed prompts"""
    image_data = process_image_for_model(image_path)
    if not image_data:
        return None
    
    detailed_prompt = """Please carefully examine the image and extract the following structured information:\n\n1. Project: The project name or number displayed in the image\n2. Name: Product/service name\n3. Contract Number: Contract number in letter+number combination, usually contains keywords like "contract", "number", "No."\n4. Model Specification: Model and specification parameters of the product\n5. Quantity: Quantity information in numerical form, may include units like "units", "pieces", "sets"\n6. Situation Description: Description of the acceptance situation\n7. Acceptance Unit: Name of the unit conducting the acceptance\n8. Acceptor: Name of the acceptor\n9. Acceptance Date: Date format (e.g., YYYY-MM-DD, YYYY年MM月DD日, etc.)\n\nPlease strictly return in JSON format, use empty strings for field values when empty, do not add any explanations:\n{"Project":"","Name":"","Contract Number":"","Model Specification":"","Quantity":"","Situation Description":"","Acceptance Unit":"","Acceptor":"","Acceptance Date":""}"""
    
    try:
        response = ollama.generate(
            model=model,
            prompt=detailed_prompt,
            images=[image_data],
            format="json",
            stream=False,
            options={"temperature": 0.0}
        )
        
        result = json.loads(response["response"].strip())
        result["Image File Name"] = os.path.basename(image_path)
        result["Extraction Strategy"] = "Backup Strategy"
        return result
    except:
        return None

def batch_process_with_fallback(folder_path, output_excel="extracted_info.xlsx"):
    """Batch processing with backup strategy"""
    supported_formats = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.gif')
    image_files = []
    
    # Collect all image files
    for filename in os.listdir(folder_path):
        if filename.lower().endswith(supported_formats):
            image_files.append(os.path.join(folder_path, filename))
    
    if not image_files:
        print("No image files found!")
        return
    
    print(f"Found {len(image_files)} images, starting processing...")
    results = []
    
    for i, image_path in enumerate(image_files):
        filename = os.path.basename(image_path)
        print(f"\n=== Processing image {i+1}/{len(image_files)}: {filename} ===")
        
        # Main strategy extraction
        info = extract_info_with_multiple_prompts(image_path)
        
        # If main strategy fails, use backup strategy
        if not info or all(v == "" for k, v in info.items() if k not in ["Image File Name", "Original Text", "Extraction Strategy"]):
            print("Main strategy extraction failed, trying backup strategy...")
            info = fallback_extract(image_path)
        
        # If both fail, fill with default values
        if not info:
            info = {
                "Project": "", "Name": "", "Contract Number": "", "Model Specification": "", 
                "Quantity": "", "Situation Description": "", "Acceptance Unit": "", "Acceptor": "", 
                "Acceptance Date": "", "Image File Name": filename, 
                "Extraction Strategy": "Extraction Failed", "Original Text": ""
            }
        
        results.append(info)
    
    # Save results
    if results:
        df = pd.DataFrame(results)
        
        # Organize column order
        columns = ["Image File Name", "Project", "Name", "Contract Number", "Model Specification", 
                  "Quantity", "Situation Description", "Acceptance Unit", "Acceptor", "Acceptance Date", 
                  "Extraction Strategy", "Original Text"]
        df = df.reindex(columns=columns, fill_value="")
        
        # Save to Excel
        df.to_excel(output_excel, index=False, engine="openpyxl")
        print(f"\nProcessing complete! Results saved to {output_excel}")
        
        # Count results
        success = sum(1 for r in results if r.get("Project") or r.get("Name") or r.get("Contract Number"))
        print(f"Successfully extracted information from images: {success}/{len(results)}")

def verify_model_capability():
    """Verify the model's visual capabilities (not dependent on test images)"""
    print("Verifying model capabilities...")
    try:
        # Create a simple test image (pure text)
        from PIL import ImageDraw, ImageFont
        
        # Create test image
        test_img = Image.new('RGB', (500, 200), color='white')
        d = ImageDraw.Draw(test_img)
        
        # Try to load font
        try:
            font = ImageFont.truetype("arial.ttf", 20)
        except:
            font = ImageFont.load_default(size=20)
        
        # Draw test text
        test_text = """Project: Test Project\nName: Test Product\nContract Number: TEST-2024-001\nModel Specification: V1.0\nQuantity: 10\nAcceptance Date: 2024-01-01"""
        
        d.text((20, 20), test_text, fill='black', font=font)
        
        # Save to memory
        buffer = io.BytesIO()
        test_img.save(buffer, format='JPEG')
        buffer.seek(0)
        
        # Test model
        prompt = "Please recognize the text content in the image"
        response = ollama.generate(
            model="qwen3-vl:8b",
            prompt=prompt,
            images=[buffer.getvalue()],
            stream=False
        )
        
        if "Test Project" in response.get("response", ""):
            print("✓ Model visual capability validation passed")
            return True
        else:
            print("⚠️ Model response may not meet expectations")
            print(f"Response content: {response.get('response', 'No response')}")
            return True  # Continue execution even if recognition is inaccurate
        
    except Exception as e:
        print(f"Model capability verification warning: {str(e)}")
        print("Continuing with image processing...")
        return True

def test_ollama_connection():
    """Test Ollama connection and model"""
    print("Testing Ollama connection...")
    try:
        # Check service connection
        models = ollama.list()
        print("✓ Successfully connected to Ollama service")
        
        # Check qwen3-vl model
        model_found = False
        for model in models.get('models', []):
            if 'qwen3-vl' in model.get('name', ''):
                model_found = True
                print(f"✓ Found model: {model.get('name')}")
                break
        
        if not model_found:
            print("⚠️ qwen3-vl model not found, pulling...")
            for progress in ollama.pull("qwen3-vl:8b", stream=True):
                if 'status' in progress:
                    print(f"  {progress['status']} ({progress.get('completed', 0)}/{progress.get('total', 0)})", end='\r')
            print("\n✓ Model pull complete")
        
        return True
        
    except Exception as e:
        print(f"✗ Ollama connection failed: {str(e)}")
        print("\nPlease ensure:")
        print("1. Ollama service is running (ollama serve)")
        print("2. qwen3-vl model is installed (ollama pull qwen3-vl:8b)")
        return False

if __name__ == "__main__":
    # Configuration
    IMAGE_FOLDER = "your_folder_path"  # Replace with your image folder path
    OUTPUT_FILE =  "extraction_results.xlsx"  # Replace with your result output path
    
    # First test connection
    if test_ollama_connection():
        # Verify model capability
        verify_model_capability()
        
        # Batch process images
        batch_process_with_fallback(IMAGE_FOLDER, OUTPUT_FILE)
This concludes today's sharing, thank you for your attention and reading.

Leave a Comment