【行驶证识别成表格】批量OCR行驶证识别与Excel自动化处理系统,行驶证扫描件和照片图片识别后保存为Excel表格,基于QT和华为ocr识别的实现教程

发布于:2025-06-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

在车辆管理、物流运输、保险理赔等领域,经常需要处理大量的行驶证信息。传统的人工录入方式效率低、易出错,而使用 OCR 技术可以自动识别行驶证图片中的文字信息,极大提高数据处理效率。该系统可以应用于以下场景: 

保险公司快速录入客户车辆信息,物流公司管理车队行驶证信息,车管所批量处理车辆注册信息,企业车队管理系统数据录入

界面设计

基于 QT 的界面设计应该直观易用,包含以下主要区域:

  1. 菜单栏:包含文件操作、设置和帮助菜单
  2. 工具栏:快速访问常用功能
  3. 图片选择区域:支持拖放和文件选择
  4. OCR 识别按钮:触发识别功能
  5. 预览区域:显示识别结果
  6. Excel 导出按钮:将识别结果导出为 Excel
  7. 状态栏:显示当前操作状态和进度

下面是一个基于 PyQt5 和华为 OCR 的实现方案:

import sys
import os
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QVBoxLayout, 
                            QHBoxLayout, QFileDialog, QLabel, QWidget, QProgressBar,
                            QTextEdit, QListWidget, QMessageBox, QSplitter, QAction)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QPixmap, QIcon
import pandas as pd
import requests
import json
import base64

class OCRThread(QThread):
    """OCR识别线程,处理图片识别任务"""
    progress_updated = pyqtSignal(int)
    ocr_result = pyqtSignal(dict)
    finished = pyqtSignal()
    error = pyqtSignal(str)
    
    def __init__(self, image_paths, app_key, app_secret):
        super().__init__()
        self.image_paths = image_paths
        self.app_key = app_key
        self.app_secret = app_secret
        self.access_token = ""
        self.stop_flag = False
    
    def run(self):
        try:
            # 获取访问令牌
            self.access_token = self.get_access_token()
            if not self.access_token:
                self.error.emit("获取访问令牌失败")
                return
                
            total = len(self.image_paths)
            results = []
            
            for i, image_path in enumerate(self.image_paths):
                if self.stop_flag:
                    break
                    
                result = self.process_image(image_path)
                if result:
                    results.append(result)
                    
                # 更新进度
                progress = int((i + 1) / total * 100)
                self.progress_updated.emit(progress)
                
            self.ocr_result.emit({"results": results, "total": total})
            self.finished.emit()
        except Exception as e:
            self.error.emit(str(e))
            self.finished.emit()
    
    def stop(self):
        self.stop_flag = True
    
    def get_access_token(self):
        """获取华为云OCR服务的访问令牌"""
        token_url = "https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens"
        
        headers = {
            "Content-Type": "application/json"
        }
        
        payload = {
            "auth": {
                "identity": {
                    "methods": ["password"],
                    "password": {
                        "user": {
                            "name": "your_username",
                            "password": "your_password",
                            "domain": {
                                "name": "your_domain"
                            }
                        }
                    }
                },
                "scope": {
                    "project": {
                        "name": "cn-north-4"
                    }
                }
            }
        }
        
        try:
            response = requests.post(token_url, headers=headers, json=payload)
            if response.status_code == 201:
                return response.headers.get("X-Subject-Token", "")
            else:
                print(f"获取令牌失败: {response.text}")
                return ""
        except Exception as e:
            print(f"请求异常: {str(e)}")
            return ""
    
    def process_image(self, image_path):
        """处理单张图片,调用华为云OCR服务"""
        ocr_url = "https://ocr.cn-north-4.myhuaweicloud.com/v2/infers/vehicle-license-plate"
        
        headers = {
            "Content-Type": "application/json",
            "X-Auth-Token": self.access_token
        }
        
        try:
            with open(image_path, "rb") as f:
                image_data = f.read()
            
            # 对图片进行Base64编码
            image_base64 = base64.b64encode(image_data).decode("utf-8")
            
            payload = {
                "image": image_base64,
                "url": "",
                "return_text_location": True,
                "detect_direction": True
            }
            
            response = requests.post(ocr_url, headers=headers, json=payload)
            if response.status_code == 200:
                result = response.json()
                # 提取关键信息
                plate_number = self.extract_plate_number(result)
                vehicle_type = self.extract_vehicle_type(result)
                owner = self.extract_owner(result)
                address = self.extract_address(result)
                use_character = self.extract_use_character(result)
                model = self.extract_model(result)
                vin = self.extract_vin(result)
                engine_number = self.extract_engine_number(result)
                register_date = self.extract_register_date(result)
                issue_date = self.extract_issue_date(result)
                
                return {
                    "image_path": image_path,
                    "plate_number": plate_number,
                    "vehicle_type": vehicle_type,
                    "owner": owner,
                    "address": address,
                    "use_character": use_character,
                    "model": model,
                    "vin": vin,
                    "engine_number": engine_number,
                    "register_date": register_date,
                    "issue_date": issue_date
                }
            else:
                print(f"OCR识别失败: {response.text}")
                return None
        except Exception as e:
            print(f"处理图片异常: {str(e)}")
            return None
    
    def extract_plate_number(self, result):
        """从OCR结果中提取车牌号"""
        # 这里需要根据华为云OCR返回的实际格式进行提取
        # 示例代码,需要根据实际情况调整
        for item in result.get("result", {}).get("items", []):
            if "车牌号码" in item.get("text", ""):
                return item.get("text", "").replace("车牌号码", "").strip()
        return ""
    
    def extract_vehicle_type(self, result):
        """从OCR结果中提取车辆类型"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_owner(self, result):
        """从OCR结果中提取车主"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_address(self, result):
        """从OCR结果中提取住址"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_use_character(self, result):
        """从OCR结果中提取使用性质"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_model(self, result):
        """从OCR结果中提取品牌型号"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_vin(self, result):
        """从OCR结果中提取车辆识别代号"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_engine_number(self, result):
        """从OCR结果中提取发动机号码"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_register_date(self, result):
        """从OCR结果中提取注册日期"""
        # 实现类似extract_plate_number
        return ""
    
    def extract_issue_date(self, result):
        """从OCR结果中提取发证日期"""
        # 实现类似extract_plate_number
        return ""

class MainWindow(QMainWindow):
    """主窗口类"""
    def __init__(self):
        super().__init__()
        self.image_paths = []
        self.ocr_results = []
        self.ocr_thread = None
        
        self.init_ui()
        self.init_menu()
        
    def init_ui(self):
        """初始化用户界面"""
        self.setWindowTitle("行驶证OCR识别与Excel处理系统")
        self.setGeometry(100, 100, 1200, 800)
        
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 创建主布局
        main_layout = QVBoxLayout(central_widget)
        
        # 创建顶部布局 - 文件选择区域
        top_layout = QHBoxLayout()
        
        self.select_files_btn = QPushButton("选择图片")
        self.select_files_btn.clicked.connect(self.select_files)
        
        self.clear_files_btn = QPushButton("清除图片")
        self.clear_files_btn.clicked.connect(self.clear_files)
        self.clear_files_btn.setEnabled(False)
        
        self.ocr_btn = QPushButton("开始OCR识别")
        self.ocr_btn.clicked.connect(self.start_ocr)
        self.ocr_btn.setEnabled(False)
        
        self.export_excel_btn = QPushButton("导出Excel")
        self.export_excel_btn.clicked.connect(self.export_to_excel)
        self.export_excel_btn.setEnabled(False)
        
        top_layout.addWidget(self.select_files_btn)
        top_layout.addWidget(self.clear_files_btn)
        top_layout.addWidget(self.ocr_btn)
        top_layout.addWidget(self.export_excel_btn)
        
        # 创建中部布局 - 图片列表和预览区域
        middle_layout = QHBoxLayout()
        
        # 左侧 - 图片列表
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        
        self.image_list = QListWidget()
        self.image_list.itemClicked.connect(self.on_image_selected)
        
        left_layout.addWidget(QLabel("图片列表:"))
        left_layout.addWidget(self.image_list)
        
        # 右侧 - 预览和结果区域
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)
        
        # 预览区域
        preview_widget = QWidget()
        preview_layout = QVBoxLayout(preview_widget)
        
        self.image_preview = QLabel("图片预览")
        self.image_preview.setAlignment(Qt.AlignCenter)
        self.image_preview.setMinimumSize(400, 300)
        self.image_preview.setStyleSheet("border: 1px solid #cccccc;")
        
        preview_layout.addWidget(QLabel("图片预览:"))
        preview_layout.addWidget(self.image_preview)
        
        # 结果区域
        result_widget = QWidget()
        result_layout = QVBoxLayout(result_widget)
        
        self.result_text = QTextEdit()
        self.result_text.setReadOnly(True)
        
        result_layout.addWidget(QLabel("识别结果:"))
        result_layout.addWidget(self.result_text)
        
        right_layout.addWidget(preview_widget)
        right_layout.addWidget(result_widget)
        
        # 使用分割器允许用户调整左右区域大小
        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(left_widget)
        splitter.addWidget(right_widget)
        splitter.setSizes([300, 900])  # 初始大小
        
        middle_layout.addWidget(splitter)
        
        # 创建底部布局 - 进度条
        bottom_layout = QHBoxLayout()
        
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        
        self.status_label = QLabel("就绪")
        
        bottom_layout.addWidget(self.progress_bar)
        bottom_layout.addWidget(self.status_label)
        
        # 添加所有布局到主布局
        main_layout.addLayout(top_layout)
        main_layout.addLayout(middle_layout)
        main_layout.addLayout(bottom_layout)
    
    def init_menu(self):
        """初始化菜单栏"""
        menubar = self.menuBar()
        
        # 文件菜单
        file_menu = menubar.addMenu("文件")
        
        open_action = QAction("打开图片", self)
        open_action.triggered.connect(self.select_files)
        
        export_action = QAction("导出Excel", self)
        export_action.triggered.connect(self.export_to_excel)
        export_action.setEnabled(False)
        
        exit_action = QAction("退出", self)
        exit_action.triggered.connect(self.close)
        
        file_menu.addAction(open_action)
        file_menu.addAction(export_action)
        file_menu.addSeparator()
        file_menu.addAction(exit_action)
        
        # 设置菜单
        settings_menu = menubar.addMenu("设置")
        
        ocr_settings_action = QAction("OCR设置", self)
        ocr_settings_action.triggered.connect(self.show_ocr_settings)
        
        settings_menu.addAction(ocr_settings_action)
        
        # 帮助菜单
        help_menu = menubar.addMenu("帮助")
        
        about_action = QAction("关于", self)
        about_action.triggered.connect(self.show_about)
        
        help_menu.addAction(about_action)
    
    def select_files(self):
        """选择图片文件"""
        file_paths, _ = QFileDialog.getOpenFileNames(
            self, "选择行驶证图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp)"
        )
        
        if file_paths:
            self.image_paths.extend(file_paths)
            self.update_image_list()
            self.clear_files_btn.setEnabled(True)
            self.ocr_btn.setEnabled(True)
    
    def clear_files(self):
        """清除已选择的图片"""
        self.image_paths = []
        self.image_list.clear()
        self.image_preview.setText("图片预览")
        self.result_text.clear()
        self.ocr_results = []
        self.clear_files_btn.setEnabled(False)
        self.ocr_btn.setEnabled(False)
        self.export_excel_btn.setEnabled(False)
        self.status_label.setText("就绪")
        self.progress_bar.setValue(0)
    
    def update_image_list(self):
        """更新图片列表"""
        self.image_list.clear()
        for path in self.image_paths:
            file_name = os.path.basename(path)
            self.image_list.addItem(file_name)
    
    def on_image_selected(self, item):
        """图片被选中时的处理"""
        index = self.image_list.row(item)
        if 0 <= index < len(self.image_paths):
            image_path = self.image_paths[index]
            self.display_image(image_path)
            
            # 如果已经识别,显示识别结果
            if index < len(self.ocr_results):
                self.display_result(self.ocr_results[index])
            else:
                self.result_text.clear()
    
    def display_image(self, image_path):
        """显示图片预览"""
        pixmap = QPixmap(image_path)
        if not pixmap.isNull():
            scaled_pixmap = pixmap.scaled(
                self.image_preview.size(), 
                Qt.KeepAspectRatio, 
                Qt.SmoothTransformation
            )
            self.image_preview.setPixmap(scaled_pixmap)
        else:
            self.image_preview.setText("无法加载图片")
    
    def display_result(self, result):
        """显示识别结果"""
        text = f"车牌号: {result.get('plate_number', '')}\n"
        text += f"车辆类型: {result.get('vehicle_type', '')}\n"
        text += f"车主: {result.get('owner', '')}\n"
        text += f"住址: {result.get('address', '')}\n"
        text += f"使用性质: {result.get('use_character', '')}\n"
        text += f"品牌型号: {result.get('model', '')}\n"
        text += f"车辆识别代号: {result.get('vin', '')}\n"
        text += f"发动机号码: {result.get('engine_number', '')}\n"
        text += f"注册日期: {result.get('register_date', '')}\n"
        text += f"发证日期: {result.get('issue_date', '')}\n"
        
        self.result_text.setText(text)
    
    def show_ocr_settings(self):
        """显示OCR设置对话框"""
        # 这里可以实现一个设置对话框,让用户输入华为云OCR的API密钥等信息
        # 简化实现,直接使用消息框
        QMessageBox.information(
            self, "OCR设置", 
            "请在代码中修改OCRThread类的get_access_token方法,设置您的华为云账号信息。"
        )
    
    def show_about(self):
        """显示关于对话框"""
        QMessageBox.about(
            self, "关于", 
            "行驶证OCR识别与Excel处理系统\n\n"
            "版本: 1.0.0\n"
            "功能: 批量识别行驶证图片并导出Excel表格\n"
            "技术: 基于PyQt5和华为云OCR服务"
        )
    
    def start_ocr(self):
        """开始OCR识别"""
        if not self.image_paths:
            QMessageBox.warning(self, "警告", "请先选择图片")
            return
        
        # 获取API密钥(实际应用中应该从配置文件或设置对话框获取)
        app_key = "your_app_key"
        app_secret = "your_app_secret"
        
        # 禁用按钮防止重复点击
        self.select_files_btn.setEnabled(False)
        self.clear_files_btn.setEnabled(False)
        self.ocr_btn.setEnabled(False)
        self.export_excel_btn.setEnabled(False)
        
        # 创建并启动OCR线程
        self.ocr_thread = OCRThread(self.image_paths, app_key, app_secret)
        self.ocr_thread.progress_updated.connect(self.update_progress)
        self.ocr_thread.ocr_result.connect(self.on_ocr_result)
        self.ocr_thread.finished.connect(self.on_ocr_finished)
        self.ocr_thread.error.connect(self.on_ocr_error)
        self.ocr_thread.start()
        
        self.status_label.setText("正在识别...")
    
    def update_progress(self, value):
        """更新进度条"""
        self.progress_bar.setValue(value)
    
    def on_ocr_result(self, result):
        """处理OCR识别结果"""
        self.ocr_results = result.get("results", [])
        total = result.get("total", 0)
        success_count = len(self.ocr_results)
        
        self.status_label.setText(f"识别完成: {success_count}/{total}")
        
        # 如果有识别结果,启用导出按钮
        if self.ocr_results:
            self.export_excel_btn.setEnabled(True)
            
            # 如果当前选中了图片,显示第一个结果
            if self.image_list.currentRow() >= 0:
                self.on_image_selected(self.image_list.currentItem())
    
    def on_ocr_finished(self):
        """OCR识别完成后的处理"""
        # 重新启用按钮
        self.select_files_btn.setEnabled(True)
        self.clear_files_btn.setEnabled(True)
        self.ocr_btn.setEnabled(True)
        
        self.ocr_thread = None
    
    def on_ocr_error(self, error_msg):
        """处理OCR识别错误"""
        QMessageBox.critical(self, "错误", f"OCR识别过程中发生错误: {error_msg}")
        self.status_label.setText("识别失败")
        
        # 重新启用按钮
        self.select_files_btn.setEnabled(True)
        self.clear_files_btn.setEnabled(True)
        self.ocr_btn.setEnabled(True)
        
        self.ocr_thread = None
    
    def export_to_excel(self):
        """导出识别结果到Excel"""
        if not self.ocr_results:
            QMessageBox.warning(self, "警告", "没有识别结果可导出")
            return
        
        file_path, _ = QFileDialog.getSaveFileName(
            self, "导出Excel", "", "Excel文件 (*.xlsx)"
        )
        
        if file_path:
            try:
                # 准备数据
                data = []
                for result in self.ocr_results:
                    data.append({
                        "图片路径": result.get("image_path", ""),
                        "车牌号": result.get("plate_number", ""),
                        "车辆类型": result.get("vehicle_type", ""),
                        "车主": result.get("owner", ""),
                        "住址": result.get("address", ""),
                        "使用性质": result.get("use_character", ""),
                        "品牌型号": result.get("model", ""),
                        "车辆识别代号": result.get("vin", ""),
                        "发动机号码": result.get("engine_number", ""),
                        "注册日期": result.get("register_date", ""),
                        "发证日期": result.get("issue_date", "")
                    })
                
                # 创建DataFrame并保存到Excel
                df = pd.DataFrame(data)
                df.to_excel(file_path, index=False)
                
                QMessageBox.information(self, "成功", f"已成功导出到 {file_path}")
                self.status_label.setText(f"已导出到 {file_path}")
            except Exception as e:
                QMessageBox.critical(self, "错误", f"导出Excel失败: {str(e)}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())    

详细代码步骤说明

  1. 环境准备

首先需要安装必要的库:

pip install PyQt5 pandas requests openpyxl
  1. 项目结构

项目主要包含一个 Python 文件,结构如下:

  • OCRThread 类:处理 OCR 识别的后台线程
  • MainWindow 类:主窗口 UI 和交互逻辑
  • 主程序入口
  1. OCR 识别实现

OCRThread 类负责与华为云 OCR 服务通信,主要功能包括:

  • 获取访问令牌(get_access_token 方法)
  • 处理单张图片(process_image 方法)
  • 从 OCR 结果中提取关键信息(extract_* 系列方法)
  1. 用户界面实现

MainWindow 类实现了完整的用户界面,包括:

  • 文件选择和管理功能
  • 图片预览功能
  • 识别结果显示
  • 进度条和状态提示
  • 菜单栏和工具栏
  1. Excel 导出功能

使用 pandas 库将识别结果导出为 Excel 文件,支持用户自定义保存路径。

总结与优化

这个系统实现了基本的行驶证 OCR 识别和 Excel 导出功能,但还有很多可以优化的地方:

  1. 准确性优化

    • 针对行驶证图片特点进行预处理(如旋转、裁剪、增强对比度)
    • 优化 OCR 结果提取逻辑,提高关键信息识别准确率
    • 实现手动校正功能,允许用户修改识别错误的内容
  2. 用户体验优化

    • 添加配置界面,方便用户设置 API 密钥等参数
    • 实现多线程并行处理,提高批量识别速度
    • 添加图片管理功能,如排序、分组、删除等
    • 增加识别结果的可视化展示,如表格形式
  3. 系统稳定性

    • 实现断点续传功能,避免长时间处理中断
    • 添加更完善的错误处理和日志记录
    • 支持更多图片格式和分辨率
  4. 性能优化

    • 对大量图片进行分批处理,减少内存占用
    • 实现识别结果缓存,避免重复识别相同图片

通过以上优化,可以使系统更加实用和高效,满足不同场景下的行驶证信息处理需求。


网站公告

今日签到

点亮在社区的每一天
去签到