自定义区域OCR识别文件模型应用与打包实战

项目简介

任务描述:在实际的工作中,有时候需要根据图片中的指定区域进行重命名,为了更好的普及OCR应用,本任务需要将已经写好的代码打包为exe程序,为界面设计UI等实现,并根据导入与导出文件夹目录地址完成对导入文件夹的所有图片进行重命名。

精度指标:提供30张图片,识别正确90%以上为合格。

环境要求:

本项目有软件UI界面:需要下载QT Desinger设计软件,且需要将实例代码下载到电脑中运行。

本项目使用飞桨场景应用开发套件PaddleOCR或PaddleHUB模型的应用程序进行打包。打包可以采用QPT或是Pyinstaller。

#安装依赖包requirements.txt
!pip install -r requirements.txt -i  https://mirror.baidu.com/pypi/simple

一、软件UI界面使用QT Desinger工具设计

使用界面设计工具

在这里插入图片描述

下载地址:QT Desinger https://build-system.fman.io/qt-designer-download

使用QT Desinger原型UI设计:
在这里插入图片描述

主体功能需求:

1.选择图像文件夹,将图像识别出来保存到指定目录的csv文件。图像数据集自定义,文件50张。识别的指标大于90%精度。

2.开发使用场景应用开发套件PaddleOCR或PaddleHUB模型。

3.使用QPT进行进行代码打包为exe程序。

Ui_MainWindow.py文件是绘制UI界面的代码,内容如下。

# -*- coding: utf-8 -*-

import sys
from PyQt5 import  QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(240, 390, 301, 51))
        self.label_4.setObjectName("label_4")
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(160, 150, 101, 16))
        self.label_3.setObjectName("label_3")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(530, 20, 113, 32))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(530, 60, 113, 32))
        self.pushButton_2.setObjectName("pushButton_2")
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(240, 420, 301, 51))
        self.label_5.setObjectName("label_5")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(160, 60, 101, 31))
        font = QtGui.QFont()
        font.setPointSize(13)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(380, 110, 113, 41))
        self.pushButton_4.setObjectName("pushButton_4")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(160, 20, 81, 31))
        font = QtGui.QFont()
        font.setPointSize(13)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(240, 20, 271, 31))
        self.lineEdit.setObjectName("lineEdit")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(160, 170, 551, 221))
        self.textEdit.setObjectName("textEdit")
        self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_2.setGeometry(QtCore.QRect(240, 60, 271, 31))
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(240, 110, 113, 41))
        self.pushButton_3.setObjectName("pushButton_3")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 24))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "自定义区域OCR识别文件"))
        self.label_4.setText(_translate("MainWindow", "软件版权:武汉理工大学-陈冬明所有"))
        self.label_3.setText(_translate("MainWindow", "显示识别状态"))
        self.pushButton.setText(_translate("MainWindow", "选择文件夹"))
        self.pushButton_2.setText(_translate("MainWindow", "选择文件夹"))
        self.label_5.setText(_translate("MainWindow", "技术指导:AI人工智能特训营"))
        self.label_2.setText(_translate("MainWindow", "结果保存到:"))
        self.pushButton_4.setText(_translate("MainWindow", "重置"))
        self.label.setText(_translate("MainWindow", "图像文件夹:"))
        self.pushButton_3.setText(_translate("MainWindow", "识别开始"))


二、PaddleHub关联自定义模型

PaddleHub持载选择用ch_pp-ocrv3模型。识别用的方法写在ppcorehub.py文件。

ppcorehub.py中使用paddlehub方法引用ch_pp-ocrv3模型.方法内容如下

# 定义获取名称函数
import paddlehub as hub
import cv2

#使用飞桨核心识别框架:PaddleHUB 引入 OCR的ch_pp-ocrv3模型
#注意:经过试验:在notebook上运行会报错的,报错原因notebook中运行paddlehub加载模型,需要在电脑上运行
ocr = hub.Module(name="ch_pp-ocrv3")

class PPCoreHUB(object):

    def get_name(fn):
        img = cv2.imread(fn)
        result = ocr.recognize_text(images=[img])

        # 1找到铁塔名称
        indx = 0
        r_data = result[0]['data']
        for i in range(len(r_data)):
            if '塔名称' in r_data[i]['text']:
                # print(r_data[i]['text_box_position'])
                indx = i
                break

        # 2找到铁塔名称
        # 垂直方向加权,铁塔名称答案在一条直线上
        weight_y = 4
        # 找到离铁塔名称最近的识别框
        p_x = (r_data[indx]['text_box_position'][1][0] + r_data[indx]['text_box_position'][2][0]) / 2
        p_y = (r_data[indx]['text_box_position'][1][1] + r_data[indx]['text_box_position'][2][1]) / 2
        min_d = p_x + p_y
        name_idx = 0
        for i in range(len(r_data)):
            # 排除自己
            if i == indx:
                continue
            x = (r_data[i]['text_box_position'][0][0] + r_data[i]['text_box_position'][3][0]) / 2
            y = (r_data[i]['text_box_position'][0][1] + r_data[i]['text_box_position'][3][1]) / 2
            dis = abs(p_x - x) + abs(p_y - y) * weight_y
            # print('{0}:{1}'.format(dis,r_data[i]['text']))
            if dis < min_d:
                min_d = dis
                name_idx = i
        name = r_data[name_idx]['text']

        # 3搜索夸行问题

        weight_x = 1
        # 底部两个点中点
        p_x = (r_data[name_idx]['text_box_position'][2][0] + r_data[name_idx]['text_box_position'][3][0]) / 2
        p_y = (r_data[name_idx]['text_box_position'][2][1] + r_data[name_idx]['text_box_position'][3][1]) / 2

        min_d = 20  # 距离小于20存在跨行

        for i in range(len(r_data)):
            # 排除自己
            if i == name_idx:
                continue
            x = (r_data[i]['text_box_position'][0][0] + r_data[i]['text_box_position'][1][0]) / 2
            y = (r_data[i]['text_box_position'][0][1] + r_data[i]['text_box_position'][1][1]) / 2
            dis = abs(p_x - x) * weight_x + abs(p_y - y)
            # print('{0}:{1}'.format(dis,r_data[i]['text']))
            if dis < min_d:
                name += r_data[i]['text']
                break

        return name

三、数据集准备

这里引用数据集是塔类业务交付验收单

数据集链接地址:https://aistudio.baidu.com/aistudio/datasetdetail/142101

数据集包:30张手写图片,目录:/home/aistudio/data

在这里插入图片描述

在这里插入图片描述

四、运行实例

界面、识别程序、物料数据集都准备好后,开始运行。

程序需下载到你的主机本地,注意路径。

窗口呈现和执行文件mainocr.py,内容如下

import sys
import os

from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import QtWidgets
from PyQt5.QtCore import pyqtSignal, QObject

from threading import Thread

#有UI界面展示的需要电脑运行,不然报错。
import ppcorehub as hub 

import Ui_MainWindow as UI
UI =Ui_MainWindow()
#1.先定义UI端post提交信号
class UipostWay(QObject):
    #图像文件夹 input框
    post_lineEdit = pyqtSignal(str)
    #结果保存指定文件夹 input框
    post_lineEdit_2 = pyqtSignal(str)
    #显示识别状态
    post_label_3 =  pyqtSignal(str)
    #多文本框
    post_textEdit = pyqtSignal(str)

#2.定义post对像传值和对应的方法
class RunnerStart(QMainWindow, UI.Ui_MainWindow):
     def __init__(self):
         super().__init__()

         self.setupUi(self) #加载原始定义界面
         self.init_ui() #再根据实际进行初化赋值

         # 实例化界面中的值
         self.post_lineEdit_val    = UipostWay()
         self.post_lineEdit_2_val  = UipostWay()
         self.post_label_3_val     = UipostWay()
         self.post_textEdit_val    = UipostWay()

         # 将值 和 界面控件进行关联,并且使用connect要指明使用的方法
         self.post_lineEdit_val.post_lineEdit.connect(self.update_lineEdit) #图像文件夹的input
         self.post_lineEdit_2_val.post_lineEdit_2.connect(self.update_lineEdit_2)#结果保存的input
         self.post_label_3_val.post_label_3.connect(self.update_label_3) #状态标签
         self.post_textEdit_val.post_textEdit.connect(self.update_textEdit) #多文本框内容


     #UI界面初始化
     def init_ui(self):
         UI.Ui_MainWindow.setupUi(self, self)
         self.lineEdit.setPlaceholderText("需要选择识别图片文件夹全路径")
         self.lineEdit.setText("")
         self.lineEdit_2.setPlaceholderText("需要选择保存结果文件夹全路径")
         self.lineEdit_2.setText("")
         self.label_3.setText("显示识别状态")

         #定义按钮的点击事件
         self.pushButton.clicked.connect(self.open_images_folder)
         self.pushButton_2.clicked.connect(self.open_save_folder)
         self.pushButton_3.clicked.connect(self.start_rec_runner)
         self.pushButton_4.clicked.connect(self.redo)

     # 图像文件夹的input
     def update_lineEdit(self,txt):
         self.lineEdit.setText(txt.replace('\\', '/'))

     # 结果保存的input
     def update_lineEdit_2(self,txt):
        self.lineEdit_2.setText(txt.replace('\\', '/'))

     # 状态标签
     def update_label_3(self,txt):
         self.label_3.setText(txt.replace('/', '\\'))

     # 多文本框内容
     def update_textEdit(self,txt):
        get_textEdit = self.textEdit.toPlainText()
        str_content = get_textEdit + txt.replace('\\', '/')
        self.textEdit.setText(str_content)

     # 选择图像文件夹的按钮点击事件
     def open_images_folder(self,txt):
         m = QtWidgets.QFileDialog.getExistingDirectory(None, "选取文件夹", "") 
         self.post_lineEdit_val.post_lineEdit.emit(m)

     # 选择保存文件夹的按钮点击事件
     def open_save_folder(self,txt):
         m = QtWidgets.QFileDialog.getExistingDirectory(None, "选取文件夹", "")  
         self.post_lineEdit_2_val.post_lineEdit_2.emit(m)

     # 选择开始识别的按钮点击事件
     def start_rec_runner(self,txt):
         self.textEdit.setText("")

         img_path = self.lineEdit.text()
         csv_path = self.lineEdit_2.text()

         img_dir = img_path
         output_dir = csv_path

         def run_start():
             self.post_label_3_val.post_label_3.emit("正在进行识别...")

         t0 = Thread(target=run_start)
         t0.start()

         def run_rec():
             save_txt = ""
             k = 1
             for pic in os.listdir(img_dir):
                 fn = os.path.join(img_dir, pic)
                 #引用封装过的paddlehub方法
                 name = hub.PPCoreHUB.get_name(fn)
              
                 import shutil
                 shutil.copy(fn, os.path.join(output_dir, name + fn[-9:]))
                 txt_str = '{0}.{1}:{2}'.format(k,os.path.basename(fn), name) + "\r\n"
                 save_txt = save_txt + txt_str
                 self.post_textEdit_val.post_textEdit.emit(txt_str)
                 k = k + 1

             self.post_label_3_val.post_label_3.emit("识别完成")
             #识别完成后-将文本保存到指定的文件夹
             rec_result_str = ''.join(save_txt)
             os.chdir(csv_path)
             fs = open('rec_result_file.txt', 'w', encoding='utf-8-sig')
             fs.write(rec_result_str)

         t1 = Thread(target=run_rec)
         t1.start()


     # 选择重置按钮点击事件
     def redo(self):
         self.lineEdit.setText("")
         self.lineEdit_2.setText("")
         self.textEdit.setText("")
         self.label_3.setText("显示识别状态")

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

执行命令后将会出现窗口,就可以进行识别过程了:

python3.8 /home/aistudio/mainocr.py

五、PYQT生成界面-识别过程如下

5.1选择要识别的文件夹目录

在这里插入图片描述

5.2点击识别开始,可以看到运行状态有:识别进行中、识别完成

在这里插入图片描述

在这里插入图片描述

5.3识别完成后,输出到output文件夹,其中包含重命名图片文件和rec_result_file.txt标识文件。

如图1:输出了重命名好的图片文件格式组合:“重命名文件_原文件最后四数字.jpg”
在这里插入图片描述

如图2:底部输出了rec_result_file.txt标记文件
在这里插入图片描述

如图3:打开rec_result_file.txt标记文件,原文件名和识别重命名标记一一对应
在这里插入图片描述

六、将OCR应用程序进行打包

#安装pyinstaller 需要到本机安装
!pip install pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple 

执行打包任务

#在自己的电脑上进行打包
!pyinstaller -F -w mainocr.py  -p Ui_MainWindow.py -p ppcorehub.py  --hidden-import PPCoreHUB  --hidden-import  Ui_MainWindow 

执行完上边命令后,查看打包结果

1.生成了mainocr.pkg文件

在这里插入图片描述

2.生成目录位置:返回了succesfully等等信息

在这里插入图片描述

3.打开disth目录会发现mainocr和mainocr.app文件

在这里插入图片描述

4.打开build目录,会发现mainocr.pkg执行文件。若你是windows系统则是mainocr.exe文件.

在这里插入图片描述

总结

1.整个工作流程,难点在界面逻辑,因为用QT Desinger实现的界面,QT本身就是跨平台的。

2.模型策略用PPaddleHUB引入ch_pp-ocrv3模型。

3.打包原本是要用QPT的,发现它支持的windows,所以遵从导师的建议改用了pyinstaller来实现打包。

最后,感谢我的导师高睿,班长,班主任给予的建议和帮助。

开源链接:原项目地址https://aistudio.baidu.com/aistudio/projectdetail/4292245

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐