欢迎光临
我们一直在努力

GLM-OCR本地部署与Python调用

GLM-OCR是智谱AI于2026年2月开源的专业级OCR模型,以0.9B小参数实现高精度文档解析,在多项权威评测中刷新SOTA记录。以下是核心结论及关键信息:

核心优势

  • 性能领先

    • 登顶文档解析榜单OmniDocBench v1.5(94.6分),超越PaddleOCR-VL-1.5(94.5分)、DeepSeek-OCR2(91.1分)等竞品。
    • 细分场景优势突出:
      • 表格识别(复杂结构91.5分)
      • 印章识别(90.5分)
      • 手写体识别(87.0分)
      • 代码文档解析(84.7分)
  • 场景优化深度
    针对真实业务痛点优化,在以下场景表现卓越:

    • 合并单元格/跨页表格 → 直接输出HTML代码
    • 印章遮挡文字 → 精准分离重叠内容
    • 多语言混排(含竖排中文、日韩文)→ 准确率达69.3%
    • 票据/卡证字段 → 结构化JSON输出(如报关单关键字段提取)
  • 高效推理

    • 吞吐量达 1.86页/秒(PDF)、0.67张/秒(图片),速度达PaddleOCR-VL-1.5的1.5倍。
    • 显存占用<3GB,支持边缘设备部署。
  • 技术突破

    • 架构设计
      采用“CogViT视觉编码器(400M)+跨模态连接层+GLM-0.5B解码器”三级架构,通过4倍下采样压缩视觉Token,提升信息传递效率。
    • 训练创新
      • 引入多Tokens预测损失(MTP),增强长文本依赖学习。
      • 全任务强化学习优化复杂场景鲁棒性(如潦草手写体)。
    • 两阶段流程
      先通过PP-DocLayoutV3分析版面,再并行识别各区域,提升复杂文档处理效率。

    使用方式对比

    部署方式适用场景注意事项
    云端API 快速验证/小规模应用 0.2元/百万Tokens,需Base64编码本地文件
    VLLM/SGLang 高并发生产环境 需配置Python环境,支持投机解码加速
    Ollama MacOS轻量部署(MLX优化) 当前版本(v0.15.5)需拖拽图片路径

    我们使用的方式是通过ollama进行本地部署,通过python与pyq5构建gui界面调用ollama的api接口。

    import sys
    import subprocess
    import re
    import base64
    import json
    import requests
    import io
    from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton,
    QLabel, QFileDialog, QLineEdit, QTextEdit, QHBoxLayout, QSplitter, QMessageBox
    )
    from PyQt5.QtGui import QPixmap, QPalette, QColor
    from PyQt5.QtCore import Qt, QThread, pyqtSignal

    try:
    from PIL import Image
    except ImportError:
    print("错误: 需要安装Pillow库来处理图片")
    print("请运行: pip install Pillow")
    sys.exit(1)

    class OCRThread(QThread):
    """后台线程执行OCR识别,避免界面卡顿"""
    finished = pyqtSignal(str, str) # 结果, 状态
    error = pyqtSignal(str)

    def __init__(self, prompt, image_path):
    super().__init__()
    self.prompt = prompt
    self.image_path = image_path

    def image_to_base64(self, image_path):
    """将图片转换为base64编码,并进行压缩优化"""
    try:
    from PIL import Image

    # 打开图片
    with Image.open(image_path) as img:
    # 转换为RGB模式(移除alpha通道)
    if img.mode in ('RGBA', 'LA', 'P'):
    background = Image.new('RGB', img.size, (255, 255, 255))
    if img.mode == 'P':
    img = img.convert('RGBA')
    if img.mode == 'RGBA':
    background.paste(img, mask=img.split()[-1])
    else:
    background.paste(img)
    img = background
    elif img.mode != 'RGB':
    img = img.convert('RGB')

    # 调整图片尺寸(如果太大)
    max_width = 1024
    max_height = 1024

    if img.width > max_width or img.height > max_height:
    # 计算缩放比例
    scale = min(max_width / img.width, max_height / img.height)
    new_width = int(img.width * scale)
    new_height = int(img.height * scale)

    print(f"图片尺寸过大 ({img.width}x{img.height}),调整为 ({new_width}x{new_height})")
    img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)

    # 调整图片质量以减小文件大小
    buffer = io.BytesIO()
    # 使用JPEG格式(比PNG更小)
    img.save(buffer, format='JPEG', quality=85, optimize=True)
    buffer.seek(0)

    # 转换为base64
    return base64.b64encode(buffer.read()).decode('utf-8')
    except Exception as e:
    raise Exception(f"处理图片失败: {str(e)}")

    def run(self):
    try:
    # 检查Ollama服务是否可用
    try:
    response = requests.get("http://localhost:11434/api/tags", timeout=5)
    if response.status_code != 200:
    self.error.emit("Ollama服务未响应")
    return
    except requests.exceptions.ConnectionError:
    self.error.emit("无法连接到Ollama服务\\n请确保Ollama正在运行(端口11434)")
    return
    except Exception as e:
    self.error.emit(f"连接Ollama失败: {str(e)}")
    return

    # 将图片转为base64
    try:
    image_base64 = self.image_to_base64(self.image_path)
    # 检查base64长度(调试信息)
    print(f"图片base64长度: {len(image_base64)} 字符")
    except Exception as e:
    self.error.emit(f"图片处理失败: {str(e)}")
    return

    # 构建API请求数据
    payload = {
    "model": "glm-ocr",
    "prompt": self.prompt,
    "images": [image_base64],
    "stream": False,
    "options": {
    "temperature": 0.1 # 降低随机性,提高准确性
    }
    }

    # 发送API请求
    print(f"发送请求到: http://localhost:11434/api/generate")
    print(f"请求payload大小: {len(json.dumps(payload))} 字节")

    response = requests.post(
    "http://localhost:11434/api/generate",
    json=payload,
    timeout=300 # 5分钟超时
    )

    print(f"响应状态码: {response.status_code}")

    if response.status_code == 200:
    result = response.json()
    if "response" in result:
    self.finished.emit(result["response"], "success")
    else:
    error_msg = "API返回数据格式错误:缺少response字段"
    if "error" in result:
    error_msg += f"\\n错误: {result['error']}"
    self.error.emit(error_msg)
    else:
    error_msg = f"API请求失败 (状态码: {response.status_code})"
    try:
    error_detail = response.json()
    if "error" in error_detail:
    error_msg += f"\\n{error_detail['error']}"
    except:
    error_msg += f"\\n{response.text}"
    self.error.emit(error_msg)

    except requests.exceptions.Timeout:
    self.error.emit("请求超时(超过5分钟)")
    except Exception as e:
    self.error.emit(f"执行错误: {str(e)}")
    print(f"异常详情: {str(e)}")

    class GLMOCRApp(QMainWindow):
    def __init__(self):
    super().__init__()
    self.setWindowTitle("GLM-OCR 识别工具")
    self.setMinimumSize(1000, 600)
    self.resize(1200, 700)

    # 初始化OCR线程
    self.ocr_thread = None

    # ui风格
    self.set_apple_style()

    # 主布局
    central_widget = QWidget()
    main_layout = QVBoxLayout(central_widget)
    main_layout.setSpacing(0)
    main_layout.setContentsMargins(0, 0, 0, 0)

    # 顶部工具栏
    toolbar = self.create_toolbar()
    toolbar.setFixedHeight(50)
    main_layout.addWidget(toolbar)

    # 主体内容 – 水平三分布局
    splitter = QSplitter(Qt.Horizontal)
    splitter.setStyleSheet("QSplitter::handle { background-color: #E5E5EA; }")

    # 左侧 – 图片预览
    left_widget = QWidget()
    left_layout = QVBoxLayout(left_widget)
    left_layout.setSpacing(0)
    left_layout.setContentsMargins(0, 0, 0, 0)

    self.image_label = QLabel("图片预览区域\\n\\n点击上方 '选择图片' 按钮加载图片")
    self.image_label.setAlignment(Qt.AlignCenter)
    self.image_label.setStyleSheet("background-color: white; color: #999999; font-size: 16px;")
    left_layout.addWidget(self.image_label)

    splitter.addWidget(left_widget)

    # 中间 – 提示词输入(放大区域)
    middle_widget = QWidget()
    middle_layout = QVBoxLayout(middle_widget)
    middle_layout.setSpacing(0)
    middle_layout.setContentsMargins(0, 0, 0, 0)

    # 标签区域(固定高度)
    self.prompt_label = QLabel("提示词")
    self.prompt_label.setStyleSheet("padding: 15px; font-size: 14px; font-weight: 500; border-bottom: 1px solid #E5E5EA;")
    self.prompt_label.setFixedHeight(45)
    middle_layout.addWidget(self.prompt_label)

    # 提示词输入框(占据更多空间)
    self.prompt_input = QTextEdit()
    self.prompt_input.setPlaceholderText("输入提示词,如:\\nText Recognition:\\n\\n或JSON Schema:\\n{'id_number': '', 'name': ''}")
    self.prompt_input.setStyleSheet("""
    background-color: white;
    border: none;
    padding: 15px;
    font-size: 14px;
    font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
    """)
    self.prompt_input.setAcceptRichText(False)
    self.prompt_input.setAlignment(Qt.AlignLeft)
    # 设置最小高度,让输入框有足够空间
    self.prompt_input.setMinimumHeight(250)
    middle_layout.addWidget(self.prompt_input)

    # 按钮区域(固定高度)
    button_container = QWidget()
    button_layout = QHBoxLayout(button_container)
    button_layout.setContentsMargins(15, 10, 15, 10)

    execute_button = QPushButton("执行识别")
    execute_button.setStyleSheet("""
    background-color: #34C759;
    color: white;
    border: none;
    padding: 12px;
    border-radius: 6px;
    font-size: 14px;
    font-weight: 500;
    """)
    execute_button.clicked.connect(self.run_ocr)
    button_layout.addWidget(execute_button)

    middle_layout.addWidget(button_container)

    splitter.addWidget(middle_widget)

    # 右侧 – 结果显示
    right_widget = QWidget()
    right_layout = QVBoxLayout(right_widget)
    right_layout.setSpacing(0)
    right_layout.setContentsMargins(0, 0, 0, 0)

    self.result_label = QLabel("识别结果")
    self.result_label.setStyleSheet("padding: 15px; font-size: 14px; font-weight: 500; border-bottom: 1px solid #E5E5EA;")
    self.result_label.setFixedHeight(45)
    right_layout.addWidget(self.result_label)

    self.result_display = QTextEdit("识别结果将显示在这里…")
    self.result_display.setReadOnly(True)
    self.result_display.setStyleSheet("background-color: white; border: none; padding: 15px; font-size: 13px; color: #999999;")
    right_layout.addWidget(self.result_display)

    splitter.addWidget(right_widget)

    splitter.setStretchFactor(0, 4)
    splitter.setStretchFactor(1, 2)
    splitter.setStretchFactor(2, 4)

    main_layout.addWidget(splitter)
    self.setCentralWidget(central_widget)

    # 初始化变量
    self.selected_file = None

    def create_toolbar(self):
    toolbar = QWidget()
    toolbar.setStyleSheet("background-color: #F2F2F7; border-bottom: 1px solid #E5E5EA;")
    layout = QHBoxLayout(toolbar)
    layout.setSpacing(15)
    layout.setContentsMargins(20, 0, 20, 0)

    # 左侧 – 文件选择
    self.file_label = QLabel("未选择文件")
    self.file_label.setStyleSheet("font-size: 13px; color: #666666;")
    layout.addWidget(self.file_label)

    self.file_button = QPushButton("选择图片")
    self.file_button.setFixedHeight(32)
    self.file_button.clicked.connect(self.select_file) # 连接按钮点击事件
    layout.addWidget(self.file_button)

    layout.addStretch()

    # 右侧 – 状态显示
    self.status_label = QLabel("等待连接")
    self.status_label.setStyleSheet("font-size: 13px; color: #666666;")
    layout.addWidget(self.status_label)

    return toolbar

    def set_apple_style(self):
    palette = QPalette()
    palette.setColor(QPalette.Window, QColor(242, 242, 247))
    palette.setColor(QPalette.WindowText, QColor(0, 0, 0))
    palette.setColor(QPalette.Base, QColor(255, 255, 255))
    palette.setColor(QPalette.Text, QColor(0, 0, 0))
    self.setPalette(palette)

    self.setStyleSheet("""
    QMainWindow {
    background-color: #f2f2f7;
    }
    QPushButton {
    background-color: #007AFF;
    color: white;
    border: none;
    padding: 0px 16px;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 500;
    }
    QPushButton:hover {
    background-color: #0066CC;
    }
    QLabel {
    color: #000000;
    }
    QLineEdit {
    color: #000000;
    }
    QTextEdit {
    color: #000000;
    }
    """)

    def select_file(self):
    """选择图片文件并显示预览"""
    file_path, _ = QFileDialog.getOpenFileName(
    self, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.tiff);;所有文件 (*.*)"
    )

    if file_path:
    try:
    self.selected_file = file_path
    self.file_label.setText(f"已选择: {file_path}")

    # 显示图片预览
    pixmap = QPixmap(file_path)
    if not pixmap.isNull():
    # 清除文字,显示图片
    self.image_label.setText("")
    # 自适应缩放
    scaled_pixmap = pixmap.scaled(
    self.image_label.width(),
    self.image_label.height(),
    Qt.KeepAspectRatio,
    Qt.SmoothTransformation
    )
    self.image_label.setPixmap(scaled_pixmap)
    self.update_status("图片已加载", "normal")
    else:
    self.image_label.setText("无法加载图片\\n\\n请检查文件格式")
    self.update_status("加载失败", "error")
    self.selected_file = None
    except Exception as e:
    self.image_label.setText(f"加载错误\\n\\n{str(e)}")
    self.update_status("加载错误", "error")
    self.selected_file = None
    print(f"加载图片错误: {e}") # 调试信息

    def resizeEvent(self, event):
    super().resizeEvent(event)
    # 窗口大小改变时更新图片显示
    if self.selected_file and self.image_label.pixmap():
    pixmap = QPixmap(self.selected_file)
    if not pixmap.isNull():
    scaled_pixmap = pixmap.scaled(
    self.image_label.width(),
    self.image_label.height(),
    Qt.KeepAspectRatio,
    Qt.SmoothTransformation
    )
    self.image_label.setPixmap(scaled_pixmap)

    def run_ocr(self):
    """执行OCR识别(使用后台线程)"""
    if not self.selected_file:
    self.update_status("请先选择图片文件", "error")
    QMessageBox.warning(self, "警告", "请先选择图片文件!")
    return

    prompt = self.prompt_input.toPlainText().strip()
    if not prompt:
    self.update_status("请输入提示词", "error")
    QMessageBox.warning(self, "警告", "请输入提示词!")
    return

    # 更新UI状态
    self.update_status("识别中…", "processing")
    self.result_display.clear()
    self.result_display.setText("正在识别,请稍候…")
    self.result_display.setStyleSheet("background-color: white; border: none; padding: 15px; font-size: 13px; color: #007AFF;")

    # 禁用执行按钮,防止重复点击
    self.set_buttons_enabled(False)

    # 创建并启动OCR线程
    self.ocr_thread = OCRThread(prompt, self.selected_file)
    self.ocr_thread.finished.connect(self.on_ocr_finished)
    self.ocr_thread.error.connect(self.on_ocr_error)
    self.ocr_thread.start()

    def on_ocr_finished(self, result, status):
    """OCR识别完成的回调"""
    self.result_display.setText(result)
    self.result_display.setStyleSheet("background-color: white; border: none; padding: 15px; font-size: 13px; color: #000000;")
    self.update_status("识别完成", "success")
    self.set_buttons_enabled(True)

    # 自动滚动到顶部
    self.result_display.verticalScrollBar().setValue(0)

    def on_ocr_error(self, error_msg):
    """OCR识别出错的回调"""
    self.result_display.setText(f"错误: {error_msg}")
    self.result_display.setStyleSheet("background-color: white; border: none; padding: 15px; font-size: 13px; color: #FF3B30;")
    self.update_status("识别失败", "error")
    self.set_buttons_enabled(True)

    # 显示错误对话框
    QMessageBox.critical(self, "错误", error_msg)

    def set_buttons_enabled(self, enabled):
    """启用/禁用所有按钮"""
    self.file_button.setEnabled(enabled)
    # 禁用提示词区域的执行按钮
    middle_widget = self.findChild(QWidget)
    if middle_widget:
    for child in middle_widget.findChildren(QPushButton):
    if child.text() == "执行识别":
    child.setEnabled(enabled)

    def update_status(self, text, status_type):
    """统一更新状态标签"""
    self.status_label.setText(text)
    if status_type == "success":
    self.status_label.setStyleSheet("font-size: 13px; color: #34C759; font-weight: 500;")
    elif status_type == "processing":
    self.status_label.setStyleSheet("font-size: 13px; color: #007AFF; font-weight: 500;")
    elif status_type == "error":
    self.status_label.setStyleSheet("font-size: 13px; color: #FF3B30; font-weight: 500;")
    else:
    self.status_label.setStyleSheet("font-size: 13px; color: #666666; font-weight: 500;")

    if __name__ == "__main__":
    # 检查依赖
    try:
    import requests
    from PIL import Image
    except ImportError as e:
    missing = str(e).split()[-1]
    print(f"错误: 缺少依赖库 '{missing}'")
    print("请运行以下命令安装:")
    print(" pip install requests Pillow")
    sys.exit(1)

    # 检查PIL版本
    try:
    pil_version = Image.__version__
    print(f"Pillow版本: {pil_version}")
    except:
    print("警告: 无法检查Pillow版本")

    app = QApplication(sys.argv)

    # 检查Ollama是否可用
    try:
    result = subprocess.run(["ollama", "list"], capture_output=True, text=True, timeout=10)
    if result.returncode != 0:
    QMessageBox.warning(None, "警告", "Ollama未安装或未启动!\\n\\n请先安装并启动Ollama服务,然后下载glm-ocr模型:\\nollama run glm-ocr")
    except Exception as e:
    QMessageBox.critical(None, "错误", f"无法连接到Ollama服务:\\n{str(e)}\\n\\n请确保Ollama已安装并运行。\\n\\n安装步骤:\\n1. 访问 https://ollama.ai 下载安装Ollama\\n2. 运行命令: ollama run glm-ocr")
    sys.exit(1)

    window = GLMOCRApp()
    window.show()
    sys.exit(app.exec_())

    赞(0)
    未经允许不得转载:171主机测评 » GLM-OCR本地部署与Python调用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址