1.说明
基于阿里云百炼 API 的批量图片 OCR 识别工具,用于自动提取图片中的题目和答案选项文本。
功能特性
- 🔍 批量处理 :自动扫描
resource目录下所有imgs文件夹中的图片 - 🚀 并发处理:使用多线程并发处理,提高识别效率
- 💾 智能跳过:自动跳过已存在对应文本文件的图片,避免重复处理
- 📝 自动保存 :识别结果自动保存到同级的
texts文件夹中 - 🖼️ 图像优化:自动调整图像像素大小,支持图像自动转正
环境要求
- Python 3.7+
- 阿里云百炼 API Key
安装步骤
-
克隆或下载项目到本地
-
安装依赖包:
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
- 自动转正:已开启
注意事项
- API 限制:请注意阿里云百炼 API 的调用频率和配额限制
- 网络连接:确保网络连接正常,能够访问阿里云 API
- 文件编码:输出的文本文件使用 UTF-8 编码
- 并发控制:默认使用系统推荐的线程数,可根据实际情况调整
- 错误处理:如果某个图片处理失败,会打印错误信息但不会中断整个流程
常见问题
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} 个图片 <==')