python(59) : 多线程调用大模型ocr提取图片文本

1.说明

基于阿里云百炼 API 的批量图片 OCR 识别工具,用于自动提取图片中的题目和答案选项文本。

功能特性

  • 🔍 批量处理 :自动扫描 resource 目录下所有 imgs 文件夹中的图片
  • 🚀 并发处理:使用多线程并发处理,提高识别效率
  • 💾 智能跳过:自动跳过已存在对应文本文件的图片,避免重复处理
  • 📝 自动保存 :识别结果自动保存到同级的 texts 文件夹中
  • 🖼️ 图像优化:自动调整图像像素大小,支持图像自动转正

环境要求

  • Python 3.7+
  • 阿里云百炼 API Key

安装步骤

  1. 克隆或下载项目到本地

  2. 安装依赖包:

    pip install dashscope

或者使用 requirements.txt(如果已更新):

复制代码
pip install -r requirements.txt

配置说明

API Key 配置

有两种方式配置 API Key:

方式一:设置环境变量(推荐)

复制代码
# Windows PowerShell
$env:ALIYUN_BAI_LIAN_API_KEY="sk-xxx"

# Windows CMD
set ALIYUN_BAI_LIAN_API_KEY=sk-xxx

# Linux/Mac
export ALIYUN_BAI_LIAN_API_KEY="sk-xxx"

方式二:直接修改代码llm_ocr.py 文件的第 11 行,直接赋值:

复制代码
api_key = "sk-xxx"  # 替换为你的 API Key

⚠️ 注意:如果使用环境变量方式,修改后需要重启 IDE 或终端才能生效。

使用方法

目录结构

确保你的项目目录结构如下:

复制代码
考试助手/
├── llm_ocr.py
├── resource/
│   ├── 文件夹1/
│   │   ├── imgs/          # 存放图片文件
│   │   │   ├── image1.jpg
│   │   │   ├── image2.png
│   │   │   └── ...
│   │   └── texts/         # OCR 结果自动保存到这里
│   │       ├── image1.txt
│   │       ├── image2.txt
│   │       └── ...
│   └── 文件夹2/
│       ├── imgs/
│       └── texts/
└── README.md

运行脚本

复制代码
python llm_ocr.py

运行示例

复制代码
找到 2 个imgs文件夹
共 5 个图片需要处理
跳过 resource/文件夹1/imgs/image1.jpg (已存在对应的文本文件)
resource/文件夹1/imgs/image2.png 耗时:2.35秒
resource/文件夹2/imgs/image3.jpg 耗时:2.18秒
...
==> 处理完成, 总耗时:12.45秒, 总图片数: 5, 处理: 4 个图片, 跳过: 1 个图片 <==

支持的图片格式

脚本会处理 imgs 文件夹中的所有文件。建议使用以下格式:

  • .jpg / .jpeg
  • .png
  • .bmp
  • 其他常见图片格式

OCR 识别说明

  • 模型 :使用 qwen-vl-ocr-latest 模型
  • 识别内容:提取图片中的题目和答案选项
  • 图像处理
    • 最小像素:28 × 28 × 4
    • 最大像素:28 × 28 × 8192
    • 自动转正:已开启

注意事项

  1. API 限制:请注意阿里云百炼 API 的调用频率和配额限制
  2. 网络连接:确保网络连接正常,能够访问阿里云 API
  3. 文件编码:输出的文本文件使用 UTF-8 编码
  4. 并发控制:默认使用系统推荐的线程数,可根据实际情况调整
  5. 错误处理:如果某个图片处理失败,会打印错误信息但不会中断整个流程

常见问题

Q: 提示 "api_key为空" 错误?

A: 请检查是否已正确设置环境变量或直接在代码中赋值 API Key。

Q: 如何修改识别的 Prompt?

A: 在 llm_ocr.py 文件的第 50 行,修改 text 字段的内容即可。

Q: 如何调整并发线程数?

A: 在 llm_ocr.py 文件的第 101 行,修改 ThreadPoolExecutor()ThreadPoolExecutor(max_workers=数量)

2.python代码(llm_ocr.py)

python 复制代码
import os
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

from dashscope import MultiModalConversation

st0 = time.time()
resource_path = os.path.join(os.getcwd(), 'resource')

# 需要修改或者设置环境变量(修改环境变量需要重启IDE)
api_key=os.getenv('ALIYUN_BAI_LIAN_API_KEY')
if not api_key:
    raise ValueError('api_key为空, 请直接赋值或者设置 ALIYUN_BAI_LIAN_API_KEY 环境变量')


def find_all_imgs_folders(root_path):
    """查找resource下所有imgs文件夹"""
    imgs_folders = []
    for root, dirs, files in os.walk(root_path):
        if 'imgs' in dirs:
            imgs_path = os.path.join(root, 'imgs')
            # 检查同级目录是否有texts文件夹
            parent_dir = os.path.dirname(imgs_path)
            texts_path = os.path.join(parent_dir, 'texts')
            imgs_folders.append((imgs_path, texts_path))
    return imgs_folders


def process_image(fil, imgs_path, texts_path):
    """处理单张图片的OCR识别"""
    st = time.time()
    fp = os.path.join(imgs_path, fil)
    image_path = f"file://{fp}"
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "image": image_path,
                    # 输入图像的最小像素阈值,小于该值图像会按原比例放大,直到总像素大于min_pixels
                    "min_pixels": 28 * 28 * 4,
                    # 输入图像的最大像素阈值,超过该值图像会按原比例缩小,直到总像素低于max_pixels
                    "max_pixels": 28 * 28 * 8192,
                    # 开启图像自动转正功能
                    "enable_rotate": True,
                },
                # qwen-vl-ocr-latest未设置内置任务时,支持在以下text字段中传入Prompt,若未传入则使用默认的Prompt:Please output only the text content from the image without any additional descriptions or formatting.
                # 如调用qwen-vl-ocr-1028,模型会使用固定Prompt:Read all the text in the image.,不支持用户在text中传入自定义Prompt
                {
                    "text": "请提取图中的题目和答案选项"
                },
            ],
        }
    ]

    response = MultiModalConversation.call(
        # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx"
        api_key=api_key,
        model="qwen-vl-ocr-latest",
        messages=messages,
    )
    result = response["output"]["choices"][0]["message"].content[0]["text"]
    # 确保texts文件夹存在
    os.makedirs(texts_path, exist_ok=True)
    # 写入字符串(覆盖模式)
    txt_filename = os.path.splitext(fil)[0] + '.txt'
    txt_path = os.path.join(texts_path, txt_filename)
    with open(txt_path, 'w', encoding='utf-8') as f:
        f.write(result)
    print(f'{fp} 耗时:{round(time.time() - st, 2)}秒')
    return fil


# 查找所有imgs文件夹
imgs_folders = find_all_imgs_folders(resource_path)
print(f'找到 {len(imgs_folders)} 个imgs文件夹')

# 收集所有需要处理的图片文件
tasks = []
skip_count = 0
for imgs_path, texts_path in imgs_folders:
    # 确保texts文件夹存在
    os.makedirs(texts_path, exist_ok=True)
    
    # 获取所有图片文件
    for fil in os.listdir(imgs_path):
        img_path = os.path.join(imgs_path, fil)
        if os.path.isfile(img_path):
            # 检查对应的文本文件是否已存在
            txt_filename = os.path.splitext(fil)[0] + '.txt'
            txt_path = os.path.join(texts_path, txt_filename)
            if os.path.exists(txt_path):
                print(f'跳过 {img_path} (已存在对应的文本文件)')
                skip_count += 1
                continue
            tasks.append((fil, imgs_path, texts_path))

print(f'共 {len(tasks)} 个图片需要处理')

# 使用线程池并发处理图片
with ThreadPoolExecutor() as executor:
    futures = [executor.submit(process_image, fil, imgs_path, texts_path) 
               for fil, imgs_path, texts_path in tasks]
    for future in as_completed(futures):
        try:
            future.result()
        except Exception as e:
            print(f'处理文件时出错: {e}')

print(f'==> 处理完成, 总耗时:{round(time.time() - st0, 2)}秒, 总图片数: {len(tasks)}, 处理: {len(tasks) - skip_count} 个图片, 跳过: {skip_count} 个图片 <==')
相关推荐
梁辰兴1 小时前
PyCharm使用了Conda的虚拟环境创建的的Python项目,下载库(包)到该项目的虚拟环境中
python·pycharm·conda·错误·异常·异常报错
自由日记1 小时前
python简单线性回归
开发语言·python·线性回归
程序员-周李斌1 小时前
Java NIO [非阻塞 + 多路复用解]
java·开发语言·开源软件·nio
猪八戒1.01 小时前
onenet接口
开发语言·前端·javascript·嵌入式硬件
h***83931 小时前
JavaScript开源
开发语言·javascript·ecmascript
Z***25801 小时前
JavaScript虚拟现实案例
开发语言·javascript·vr
Halo_tjn2 小时前
Set集合专项实验
java·开发语言·前端·python
席万里2 小时前
关于Go的init函数执行顺序#黑魔法
开发语言·网络·golang
橘子真甜~2 小时前
C/C++ Linux网络编程6 - poll解决客户端并发连接问题
服务器·c语言·开发语言·网络·c++·poll