<think>
我们将扩展之前的OCR应用程序,添加批量处理功能。主要思路是:
1. 允许用户选择一个包含多张图片的文件夹
2. 遍历文件夹中的所有支持格式的图片
3. 对每张图片进行OCR识别
4. 将识别结果保存到指定文件中(如每张图片对应一个txt文件,或者合并成一个文件)
以下是改进后的代码,添加了批量处理功能:
</think>
# 批量图片文字识别解决方案
下面我将实现一个完整的批量图片文字识别工具,支持处理多张图片并将识别结果保存为结构化文档(如CSV、TXT等格式)。
```python
import os
import cv2
import pytesseract
import pandas as pd
from PIL import Image
from pdf2image import convert_from_path
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import time
# 设置Tesseract路径(根据实际情况修改)
# pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
class BatchOCRProcessor:
def __init__(self, root):
self.root = root
self.root.title("批量图片文字识别工具")
self.root.geometry("900x700")
self.root.configure(bg="#f0f2f5")
# 创建主框架
main_frame = tk.Frame(root, bg="#f0f2f5")
main_frame.pack(fill="both", expand=True, padx=20, pady=20)
# 标题
title_label = tk.Label(
main_frame,
text="批量图片文字识别工具",
font=("Arial", 20, "bold"),
bg="#f0f2f5",
fg="#2c3e50"
)
title_label.pack(pady=(0, 20))
# 输入区域
input_frame = tk.LabelFrame(main_frame, text="输入设置", bg="#f0f2f5", fg="#2c3e50", font=("Arial", 11))
input_frame.pack(fill="x", pady=10)
# 图片输入路径
img_frame = tk.Frame(input_frame, bg="#f0f2f5")
img_frame.pack(fill="x", pady=5)
tk.Label(img_frame, text="图片/PDF文件夹:", bg="#f0f2f5", font=("Arial", 10)).pack(side="left", padx=5)
self.img_path_entry = tk.Entry(img_frame, width=50, font=("Arial", 10))
self.img_path_entry.pack(side="left", padx=5, fill="x", expand=True)
tk.Button(
img_frame,
text="浏览...",
command=lambda: self.select_path(self.img_path_entry, is_folder=True),
bg="#3498db",
fg="white",
font=("Arial", 9),
width=8
).pack(side="left", padx=5)
# 输出路径
output_frame = tk.Frame(input_frame, bg="#f0f2f5")
output_frame.pack(fill="x", pady=5)
tk.Label(output_frame, text="输出文件夹:", bg="#f0f2f5", font=("Arial", 10)).pack(side="left", padx=5)
self.output_path_entry = tk.Entry(output_frame, width=50, font=("Arial", 10))
self.output_path_entry.pack(side="left", padx=5, fill="x", expand=True)
tk.Button(
output_frame,
text="浏览...",
command=lambda: self.select_path(self.output_path_entry, is_folder=True),
bg="#3498db",
fg="white",
font=("Arial", 9),
width=8
).pack(side="left", padx=5)
# 处理选项
options_frame = tk.LabelFrame(main_frame, text="处理选项", bg="#f0f2f5", fg="#2c3e50", font=("Arial", 11))
options_frame.pack(fill="x", pady=10)
# 语言选择
lang_frame = tk.Frame(options_frame, bg="#f0f2f5")
lang_frame.pack(fill="x", pady=5)
tk.Label(lang_frame, text="OCR语言:", bg="#f0f2f5", font=("Arial", 10)).pack(side="left", padx=5)
self.lang_var = tk.StringVar(value="eng+chi_sim")
lang_options = ["eng", "chi_sim", "eng+chi_sim", "jpn", "kor", "fra", "deu", "rus"]
lang_dropdown = ttk.Combobox(lang_frame, textvariable=self.lang_var, values=lang_options, width=15)
lang_dropdown.pack(side="left", padx=5)
# 输出格式
format_frame = tk.Frame(options_frame, bg="#f0f2f5")
format_frame.pack(fill="x", pady=5)
tk.Label(format_frame, text="输出格式:", bg="#f0f2f5", font=("Arial", 10)).pack(side="left", padx=5)
self.format_var = tk.StringVar(value="CSV")
format_options = ["CSV", "TXT", "Excel", "JSON"]
format_dropdown = ttk.Combobox(format_frame, textvariable=self.format_var, values=format_options, width=10)
format_dropdown.pack(side="left", padx=5)
# 预处理选项
preprocess_frame = tk.Frame(options_frame, bg="#f0f2f5")
preprocess_frame.pack(fill="x", pady=5)
self.grayscale_var = tk.BooleanVar(value=True)
tk.Checkbutton(
preprocess_frame,
text="灰度化",
variable=self.grayscale_var,
bg="#f0f2f5",
font=("Arial", 10)
).pack(side="left", padx=5)
self.threshold_var = tk.BooleanVar(value=True)
tk.Checkbutton(
preprocess_frame,
text="二值化",
variable=self.threshold_var,
bg="#f0f2f5",
font=("Arial", 10)
).pack(side="left", padx=5)
self.deskew_var = tk.BooleanVar(value=False)
tk.Checkbutton(
preprocess_frame,
text="自动校正倾斜",
variable=self.deskew_var,
bg="#f0f2f5",
font=("Arial", 10)
).pack(side="left", padx=5)
# 执行按钮
button_frame = tk.Frame(main_frame, bg="#f0f2f5")
button_frame.pack(fill="x", pady=15)
self.process_button = tk.Button(
button_frame,
text="开始批量识别",
command=self.process_images,
font=("Arial", 12, "bold"),
bg="#2ecc71",
fg="white",
padx=20,
pady=10
)
self.process_button.pack()
# 进度条
progress_frame = tk.Frame(main_frame, bg="#f0f2f5")
progress_frame.pack(fill="x", pady=10)
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(
progress_frame,
variable=self.progress_var,
maximum=100,
length=800,
mode="determinate"
)
self.progress_bar.pack()
# 状态信息
status_frame = tk.Frame(main_frame, bg="#f0f2f5")
status_frame.pack(fill="x", pady=5)
self.status_var = tk.StringVar(value="就绪")
status_label = tk.Label(
status_frame,
textvariable=self.status_var,
bg="#f0f2f5",
fg="#2c3e50",
font=("Arial", 10)
)
status_label.pack()
# 日志区域
log_frame = tk.LabelFrame(main_frame, text="处理日志", bg="#f0f2f5", fg="#2c3e50", font=("Arial", 11))
log_frame.pack(fill="both", expand=True, pady=10)
self.log_text = tk.Text(
log_frame,
height=8,
bg="white",
fg="#2c3e50",
font=("Consolas", 9)
)
self.log_text.pack(fill="both", expand=True, padx=5, pady=5)
self.log_text.config(state="disabled")
# 初始化变量
self.output_path = ""
self.img_path = ""
def select_path(self, entry, is_folder=False):
"""选择文件夹路径"""
if is_folder:
path = filedialog.askdirectory(title="选择文件夹")
else:
path = filedialog.askopenfilename()
if path:
entry.delete(0, tk.END)
entry.insert(0, path)
def log_message(self, message):
"""记录日志消息"""
self.log_text.config(state="normal")
self.log_text.insert(tk.END, f"{time.strftime('%H:%M:%S')} - {message}\n")
self.log_text.see(tk.END) # 滚动到底部
self.log_text.config(state="disabled")
self.root.update()
def preprocess_image(self, image):
"""图像预处理"""
# 转换为OpenCV格式
img = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# 灰度化
if self.grayscale_var.get():
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
if self.threshold_var.get():
img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 倾斜校正
if self.deskew_var.get():
# 检测倾斜角度
coords = np.column_stack(np.where(img > 0))
angle = cv2.minAreaRect(coords)[-1]
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
# 旋转图像
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
img = cv2.warpAffine(img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
def process_images(self):
"""批量处理图片"""
self.img_path = self.img_path_entry.get()
self.output_path = self.output_path_entry.get()
if not self.img_path or not self.output_path:
messagebox.showwarning("警告", "请选择输入和输出路径")
return
# 检查输出路径是否存在
if not os.path.exists(self.output_path):
os.makedirs(self.output_path)
# 收集所有图片文件
image_files = []
for root, dirs, files in os.walk(self.img_path):
for file in files:
if file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
image_files.append(os.path.join(root, file))
elif file.lower().endswith('.pdf'):
# 处理PDF文件
pdf_path = os.path.join(root, file)
try:
images = convert_from_path(pdf_path)
pdf_dir = os.path.join(self.output_path, os.path.splitext(file)[0])
if not os.path.exists(pdf_dir):
os.makedirs(pdf_dir)
for i, img in enumerate(images):
img_path = os.path.join(pdf_dir, f"{i+1}.jpg")
img.save(img_path, "JPEG")
image_files.append(img_path)
except Exception as e:
self.log_message(f"PDF处理错误: {file} - {str(e)}")
if not image_files:
messagebox.showwarning("警告", "未找到图片文件")
return
self.log_message(f"找到 {len(image_files)} 个图片文件")
self.status_var.set(f"开始处理 {len(image_files)} 个图片...")
self.progress_var.set(0)
self.process_button.config(state="disabled")
# 创建结果列表
results = []
processed = 0
try:
for i, img_path in enumerate(image_files):
# 更新进度
progress = (i + 1) / len(image_files) * 100
self.progress_var.set(progress)
self.status_var.set(f"处理中: {i+1}/{len(image_files)} - {os.path.basename(img_path)}")
self.log_message(f"处理: {os.path.basename(img_path)}")
self.root.update()
try:
# 打开并预处理图像
img = Image.open(img_path)
if self.grayscale_var.get() or self.threshold_var.get() or self.deskew_var.get():
img = self.preprocess_image(img)
# 识别文字
text = pytesseract.image_to_string(
img,
lang=self.lang_var.get(),
config='--psm 6' # 假设为统一块文本
)
# 保存结果
results.append({
"文件名": os.path.basename(img_path),
"路径": img_path,
"识别文本": text.strip(),
"处理时间": time.strftime("%Y-%m-%d %H:%M:%S")
})
processed += 1
except Exception as e:
self.log_message(f"处理失败: {img_path} - {str(e)}")
# 保存结果到文件
output_format = self.format_var.get().lower()
df = pd.DataFrame(results)
output_file = os.path.join(self.output_path, f"ocr_results.{output_format}")
if output_format == "csv":
df.to_csv(output_file, index=False)
elif output_format == "excel":
df.to_excel(output_file, index=False)
elif output_format == "json":
df.to_json(output_file, orient="records", force_ascii=False)
else: # TXT
with open(output_file, "w", encoding="utf-8") as f:
for _, row in df.iterrows():
f.write(f"文件: {row['文件名']}\n")
f.write(f"路径: {row['路径']}\n")
f.write(f"识别结果:\n{row['识别文本']}\n")
f.write("-" * 50 + "\n")
self.log_message(f"处理完成! 成功处理 {processed}/{len(image_files)} 个文件")
self.log_message(f"结果已保存到: {output_file}")
self.status_var.set(f"完成! 成功处理 {processed}/{len(image_files)}")
finally:
self.process_button.config(state="normal")
self.progress_var.set(100)
if __name__ == "__main__":
root = tk.Tk()
app = BatchOCRProcessor(root)
root.mainloop()
```
## 代码功能说明
这个批量图片文字识别工具实现了以下核心功能:
### 1. 批量处理功能
- 支持处理文件夹中的所有图片文件(PNG, JPG, BMP, TIFF)
- 自动提取PDF文件中的每一页并转换为图片处理
- 递归扫描子文件夹中的图片文件
### 2. 图像预处理增强
```python
def preprocess_image(self, image):
# 转换为OpenCV格式
img = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
# 灰度化
if self.grayscale_var.get():
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
if self.threshold_var.get():
img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 倾斜校正
if self.deskew_var.get():
# 检测倾斜角度
coords = np.column_stack(np.where(img > 0))
angle = cv2.minAreaRect(coords)[-1]
if angle < -45:
angle = -(90 + angle)
else:
angle = -angle
# 旋转图像
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
img = cv2.warpAffine(img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
return Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
```
- **灰度化**:提高对比度,减少颜色干扰
- **二值化**:使用OTSU算法自动确定阈值
- **倾斜校正**:自动检测并校正图像倾斜角度
### 3. 多语言支持
```python
# OCR语言选择
self.lang_var = tk.StringVar(value="eng+chi_sim")
lang_options = ["eng", "chi_sim", "eng+chi_sim", "jpn", "kor", "fra", "deu", "rus"]
```
- 支持多种语言识别
- 支持语言组合(如中英文混合识别)
### 4. 多种输出格式
```python
# 输出格式选择
self.format_var = tk.StringVar(value="CSV")
format_options = ["CSV", "TXT", "Excel", "JSON"]
```
- CSV:适合数据分析
- Excel:便于人工查看
- JSON:适合程序处理
- TXT:通用文本格式
### 5. PDF处理功能
```python
elif file.lower().endswith('.pdf'):
# 处理PDF文件
pdf_path = os.path.join(root, file)
try:
images = convert_from_path(pdf_path)
# ...将每页转换为图片...
```
- 使用pdf2image库将PDF转换为图片
- 每页保存为单独图片文件
- 支持多页PDF文档处理
### 6. 进度监控与日志
- 实时进度条显示处理进度
- 日志窗口记录处理详情
- 状态栏显示当前操作
## 使用说明
1. **安装依赖**:
```bash
pip install opencv-python pillow pytesseract pdf2image pandas pyperclip
```
2. **配置Tesseract**:
- 安装[Tesseract OCR](https://github.com/tesseract-ocr/tesseract)
- 下载所需语言包(如中文:chi_sim)
3. **运行程序**:
```bash
python batch_ocr.py
```
4. **操作流程**:
- 选择图片/PDF文件夹
- 选择输出文件夹
- 设置OCR语言和输出格式
- 选择预处理选项
- 点击"开始批量识别"
## 应用场景
1. **文档数字化**:批量处理扫描的纸质文档
2. **数据提取**:从大量图片中提取结构化数据
3. **档案管理**:将历史档案图片转换为可搜索文本
4. **多语言翻译**:识别外文文档内容