opencv项目实战(二)——文档扫描OCR识别

2023-11-05

一、项目描述

  • 目的
    将图片中的文档矫正,并识别文档内容

  • 输入与输出
    在这里插入图片描述

  • 方法流程
    核心思想:采用tesseract-ocr进行文档识别。具体流程如下:

    1. 定位图像中文档区域
    2. 对图像中文档区域进行透视变换等操作,凸显文档内容
    3. 采用pytesseract进行文档识别

二、代码详解

2.1 预定义参数

  • 导包

    import os
    import cv2
    import argparse
    import pytesseract
    import numpy as np
    from PIL import Image
    
  • 设置参数

    def parse():
        """设置自己的参数"""
        parser = argparse.ArgumentParser(description="set your identity parameters")
        parser.add_argument("-i", "--image", default="./images/receipt.jpg", type=str,
                            help="Path to the image to be scanned")
    
        opt = parser.parse_args()
        # opt = vars(opt)   # 可用于返回参数的‘字典对’对象
        return opt
    

2.2 辅助函数

  • 绘图

    def cv_show(name, img):
        """绘图,避免重复造轮子"""
        cv2.imshow(name, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    
  • 缩放图像尺寸

    def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
        """根据自定的宽/高进行等比例缩放图像"""
        dim = None              # 缩放后的图像尺寸
        h, w = image.shape[:2]  # 原始图像尺寸
        if width is None and height is None:
            return image
        if width is None:
            r = height / float(h)
            dim = (int(w * r), height)
        else:
            r = width / float(w)
            dim = (width, int(h * r))
    
        resized = cv2.resize(image, dim, interpolation=inter)
        return resized
    
  • 对四边形四个顶点排序:左上,右上,右下,左下

    def order_points(pts):
        """对4个坐标点进行排序:左上,右上,右下,左下"""
        rect = np.zeros((4, 2), dtype="float32")
    
        # 计算左上,右下;左上特点:x+y最小,右下特点:x+y最大
        s = pts.sum(axis=1)             # 计算每一个点的 x+y
        rect[0] = pts[np.argmin(s)]     # 得到左上点
        rect[2] = pts[np.argmax(s)]     # 得到右下点
    
        # 计算右上和左下;右上特点:y-x最小,左下特点:y-x最大
        diff = np.diff(pts, axis=1)
        rect[1] = pts[np.argmin(diff)]  # 得到右上点
        rect[3] = pts[np.argmax(diff)]  # 得到左下点
    
        return rect
    
  • 根据四组对应点进行透视变换

    def four_point_transform(image, pts):
        """根据4组对应点进行透视变换"""
        # 1. 获取输入坐标点
        rect = order_points(pts)    # 对坐标点进行排序
        tl, tr, br, bl = rect       # 依次对应:左上,右上,右下,左下;即A,B,C,D
    
        # 2. 计算输入的w和h值
        widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))   # 计算CD的长度
        widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))   # 计算AB的长度
        maxWidth = max(int(widthA), int(widthB))
    
        heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))  # 计算BC的长度
        heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))  # 计算AD的长度
        maxHeight = max(int(heightA), int(heightB))
    
        # 3. 定义变换后对应坐标位置
        dst = np.array([
            [0, 0],
            [maxWidth - 1, 0],
            [maxWidth - 1, maxHeight - 1],
            [0, maxHeight - 1]], dtype="float32")
    
        # 4. 透视变换
        M = cv2.getPerspectiveTransform(rect, dst)                      # 根据4组对应点,计算投射变换矩阵
        warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))   # 透视变换
    
        return warped
    

2.3 文档矫正

  • 流程:

    1. 读取待识别图像进行预处理(缩放)
    image = cv2.imread(opt.image)       # 读取图像
    ratio = image.shape[0] / 500.0      # 高度调整到500需要的比例
    orig = image.copy()                 # 原始图像
    image = resize(orig, height=500)    # 缩放图像
    

    在这里插入图片描述
    2. 转换为灰度图,进行边缘检测

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 转换为灰度图
    gray = cv2.GaussianBlur(gray, (5, 5), 0)        # 高斯滤波
    edged = cv2.Canny(gray, 75, 200)                # 边缘检测
    

    在这里插入图片描述

    1. 进行轮廓检测,对轮廓按面积大小排序,找到最外层轮廓(包含所有文档区域
    cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)    # 查找轮廓
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5]                                  # 对轮廓按面积从大到小排序
    need = cnts[0]  	
    
    1. 将最外层轮廓近似为四边形
    peri = cv2.arcLength(need, True)                     # 获取最外层轮廓的周长
    approx = cv2.approxPolyDP(cnts[0], 0.02 * peri, True)
    

    在这里插入图片描述
    5. 进行透视变换将文档矫正,二值化凸显内容

    # 3. 透视变换——关键所在
    warped = four_point_transform(orig, approx.reshape(4, 2) * ratio)
    
    # 4. 二值处理——凸显结果
    warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
    ref = cv2.threshold(warped, 100, 255, cv2.THRESH_BINARY)[1]
    cv2.imwrite('scan.jpg', ref)
    

    在这里插入图片描述

2.4 文档识别

  • 方法
    利用tesseract-ocr进行文本识别

  • 安装tesseract-ocr

    1. 下载安装包 tesseract-ocr-setup-x.xx.xxdev.exe
    2. 配置环境变量
    • 系统变量
      在这里插入图片描述
    • 路径变量:如 D:\software\pyEnviroment\Tesseract-OCR
    1. 测试
    tesseract -v
    tesseract XXX.png result
    
  • 安装pytesseract

    pip install pytesseract
    

    若后续有相关报错,可尝试:

    修改安装包中的tesseract_cmd 路径:
    \Anaconda3\envs\YOUR_ENVS_NAME\Lib\site-packages\pytesseract\pytesseract.py
    原:tesseract_cmd = ‘tesseract’
    现:tesseract_cmd = ‘D:\software\pyEnviroment\Tesseract-OCR\tesseract.exe’

  • 识别代码

    gray = cv2.medianBlur(ref, 3)           # 对图像进行中值滤波

    filename = "{}.png".format(os.getpid())
    cv2.imwrite(filename, gray)             # 保存滤波结果

    text = pytesseract.image_to_string(Image.open(filename))    # 文档识别
    with open("result.txt", 'w') as f:
        f.write(text)                       # 将结果写入文档
    os.remove(filename)
    cv_show("output", gray)                 # 显示中值滤波后的图片

在这里插入图片描述

三、项目完整代码

import os
import cv2
import argparse
import pytesseract
import numpy as np
from PIL import Image


def parse():
    """设置自己的参数"""
    parser = argparse.ArgumentParser(description="set your identity parameters")
    parser.add_argument("-i", "--image", default="./images/receipt.jpg", type=str,
                        help="Path to the image to be scanned")

    opt = parser.parse_args()
    # opt = vars(opt)   # 可用于返回参数的‘字典对’对象
    return opt


def cv_show(name, img):
    """绘图,避免重复造轮子"""
    cv2.imshow(name, img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def order_points(pts):
    """对4个坐标点进行排序:左上,右上,右下,左下"""
    rect = np.zeros((4, 2), dtype="float32")

    # 计算左上,右下;左上特点:x+y最小,右下特点:x+y最大
    s = pts.sum(axis=1)             # 计算每一个点的 x+y
    rect[0] = pts[np.argmin(s)]     # 得到左上点
    rect[2] = pts[np.argmax(s)]     # 得到右下点

    # 计算右上和左下;右上特点:y-x最小,左下特点:y-x最大
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]  # 得到右上点
    rect[3] = pts[np.argmax(diff)]  # 得到左下点

    return rect


def four_point_transform(image, pts):
    """根据4组对应点进行透视变换"""
    # 1. 获取输入坐标点
    rect = order_points(pts)    # 对坐标点进行排序
    tl, tr, br, bl = rect       # 依次对应:左上,右上,右下,左下;即A,B,C,D

    # 2. 计算输入的w和h值
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))   # 计算CD的长度
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))   # 计算AB的长度
    maxWidth = max(int(widthA), int(widthB))

    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))  # 计算BC的长度
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))  # 计算AD的长度
    maxHeight = max(int(heightA), int(heightB))

    # 3. 定义变换后对应坐标位置
    dst = np.array([
        [0, 0],
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype="float32")

    # 4. 透视变换
    M = cv2.getPerspectiveTransform(rect, dst)                      # 根据4组对应点,计算投射变换矩阵
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))   # 透视变换

    return warped


def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
    """根据自定的宽/高进行等比例缩放图像"""
    dim = None              # 缩放后的图像尺寸
    h, w = image.shape[:2]  # 原始图像尺寸
    if width is None and height is None:
        return image
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))

    resized = cv2.resize(image, dim, interpolation=inter)
    return resized


if __name__ == '__main__':
    # =================== 参数预处理 ===================
    opt = parse()
    # ================== 图像文档矫正 ===================
    # 0. 图像预处理
    image = cv2.imread(opt.image)       # 读取图像
    ratio = image.shape[0] / 500.0      # 高度调整到500需要的比例
    orig = image.copy()                 # 原始图像
    image = resize(orig, height=500)    # 缩放图像

    # 1. Canny边缘检测
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 转换为灰度图
    gray = cv2.GaussianBlur(gray, (5, 5), 0)        # 高斯滤波
    edged = cv2.Canny(gray, 75, 200)                # 边缘检测

    # 展示预处理结果
    print("STEP 1: 边缘检测")
    cv2.imshow("Image", image)
    cv_show("Edged", edged)

    # 2. 轮廓检测 —— 需要的是最外侧轮廓,其特点:周长/面积最大
    cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)    # 查找轮廓
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5]                                  # 对轮廓按面积从大到小排序
    need = cnts[0]                                                                              # 最外层轮廓

    peri = cv2.arcLength(need, True)                     # 获取最外层轮廓的周长
    # ----------------------------------------------------------
    # cv2.approxPolyDP(): 主要功能是把一个连续光滑曲线折线化,对图像轮廓点进行多边形拟合。
    # 参数:
    #   curve:表示输入的点集
    #   epsilon:表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数
    #   closed:表示是否封闭,True表示封闭的
    # ----------------------------------------------------------
    approx = cv2.approxPolyDP(cnts[0], 0.02 * peri, True)   # 轮廓拟合成四边形

    # 展示轮廓检测结果
    print("STEP 2: 获取轮廓")
    print("轮廓的角点个数:", len(approx))
    cv2.drawContours(image, [approx], -1, (0, 255, 0), 2)
    cv_show("Outline", image)

    # 3. 透视变换——关键所在
    warped = four_point_transform(orig, approx.reshape(4, 2) * ratio)

    # 4. 二值处理——凸显结果
    warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
    ref = cv2.threshold(warped, 100, 255, cv2.THRESH_BINARY)[1]
    cv2.imwrite('scan.jpg', ref)

    # 展示结果
    print("STEP 3: 变换")
    cv2.imshow("Original", resize(orig, height=650))
    cv_show("Scanned", resize(ref, height=650))

    # 5. 文档识别
    gray = cv2.medianBlur(ref, 3)           # 对图像进行中值滤波

    filename = "{}.png".format(os.getpid())
    cv2.imwrite(filename, gray)             # 保存滤波结果

    text = pytesseract.image_to_string(Image.open(filename))    # 文档识别
    with open("result.txt", 'w') as f:
        f.write(text)                       # 将结果写入文档
    os.remove(filename)
    cv_show("output", gray)                 # 显示中值滤波后的图片

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

opencv项目实战(二)——文档扫描OCR识别 的相关文章

随机推荐

  • 17_分布式文档系统_document的全量替换、强制创建以及lazy delete机制

    课程大纲 1 document的全量替换 2 document的强制创建 3 document的删除 1 document的全量替换 1 语法与创建文档是一样的 如果document id不存在 那么就是创建 如果document id已经
  • Swagger

    第一节 Swagger 简介 1 企业开发所面临的问题 在前后端分离开发的情况下 前端开发人员经常抱怨后端开发人员给的接口文档与实际情况不一致 后端开发人员觉得编写接口文档太过于消耗精力 而且更新也不及时 以至于前后端开发人员经常出现争吵的
  • 【kickstart 2021 round C】前三题python题解

    第一题 题目 给定长度为N的字符串S 它是由字母表上的前K个字母构成 问字典序小于S且长度为N的回文字符串 由字母表上的前K个字母构成 有多少个 解释 参考官方题解 计算多少个长度为N 2的字符串的字典序小于S math ceil N 2
  • 动态动态规划(DDP)

    1 Problem E Codeforces 一 题目大意 给你一个无向图 第i和i 1条边的权值是w i 问你每个点不在自己原本的点的代价是多少 会有q组询问 表示修改第i条边的权值 二 解题思路 可以观察到 完成这个操作需要每条边经过两
  • [LeetCode]初级算法-字符串- 实现strStr()

    标题 实现strStr 实现 strStr 函数 给定一个 haystack 字符串和一个 needle 字符串 在 haystack 字符串中找出 needle 字符串出现的第一个位置 从0开始 如果不存在 则返回 1 示例 1 输入 h
  • linux防火墙启动、停止、查看

    停止 防火墙 service iptables stop 启动防火墙 service iptables start 查看防火墙配置 iptables L n 修改的内容只是暂时保存在内存中 如果重启后还要生效 则要保存一下 service
  • Web Storage是什么?Web Storage详解

    Web Storag是HTML5引入的一个非常重要的功能 可以将数据存储在本地 如保存用户的偏好设置 复选框的选中状态 文本框默认填写的值等 用户在浏览器中刷新网页时 网页通过Web Storage就可以知道用户之前所做的一些修改 而不需要
  • Autoware 1.14(WSL2) 与LG SVL Simulator(Win11)联合仿真

    参考Couldn t find executable named rqt lgsvl simulator configurator below home autoware Autoware ros in li4692625的博客 CSDN博
  • Hyperledger Fabric核心配置文件(1)

    1 core yaml core yaml配置文件是Peer节点的示例配置文件 具体路径在fabric samples config目 录下 该core yaml示例配置文件共指定了如下六大部分内容 1 日志部分 日志记录级别有6种 CRI
  • JDBC访问数据库

    一 简介 JDBC 全称 Java DataBase Connection 数据库连接技术 可以根据驱动包连接不同类型的数据库 二 JDBC API JDBC API是java中位于java sql包下的一个数据库访问统一接口 通过它来跟数
  • 无监督学习KMeans学习笔记和实例

    KMeans算法是一种简单的算法 能够快速 高效的对数据集进行聚类 一般只要通过几次迭代即可 KMeans可以作为一种聚类工具 同时也可以作为一种降维的方式进行特征降维 KMeans可以通sklearn cluster kmeans中进行调
  • 测试 开发 5 年从外包 18K 跳槽去字节 28K+12,啃完这份笔记你也可以

    软件测试是一个付出就有回报的工作 可能很多人会说软件测试就是吃青春饭 然而其他工作又何尝不是 没有哪一家公司养尸位素餐之人 大龄员工有被辞退的 也有没被辞退的 干任何职业 抱着一劳永逸的心态 在岗位上开始混的中青年 早就该辞了 粉丝小王转行
  • 测试方法——边界值法

    边界值测试方法 边界值方法是一种比较常用的测试方法 在很多软件测试中都会应用到 一 应用条件 只要有输入框输入数据的地方 就可以用边界值这一方法来测试 一般与等价类划分共同使用 找到有效数值和无效数值之间的分界点及其两边的点进行测试 二 测
  • Jmeter进阶使用指南-使用参数化

    Apache JMeter是一个广泛使用的开源负载和性能测试工具 在进行性能测试时 我们经常需要模拟不同的用户行为和数据 这时候 参数化就显得尤为重要 此文主要介绍如何在JMeter中使用参数化 什么是参数化 参数化是一种将静态值替换为动态
  • 深入理解HashMap和LinkedHashMap的区别

    简介 我们知道HashMap的变量顺序是不可预测的 这意味着便利的输出顺序并不一定和HashMap的插入顺序是一致的 这个特性通常会对我们的工作造成一定的困扰 为了实现这个功能 我们可以使用LinkedHashMap LinkedHashM
  • 【配电变电站的最佳位置和容量】基于遗传算法的最优配电变电站放置(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现 1 概述 基于遗传算法的最优配电变电站放置 为了实现
  • iseacms1.0漏洞复现

    iseacms1 0漏洞复现 作者 admin 时间 2021 06 29 分类 漏洞复现 Index php源码 文件包含 参数转义了 0 利用方式有限在网站目录有phpinfo php文件的前提下 payload为 index r te
  • 【十三】Nacos 服务注册和配置中心

    目录 Nacos 初识 Nacos 服务部署 注册中心服务部署 服务提供者注册到Nacos 服务消费者从Nacos获取服务 负载均衡 Nacos 服务详解 实列服务详情详解 Nacos 初识 Nacos Dynamic Naming and
  • Android 输入框的输入提示效果(AutoCompleteTextView)

    在一些体验较好的APP中 输入框输入时会有相应的提示 让人能够很快的通过点击提示进入下一步 这里 我就通过自己构思 实现了一个通过 SharedPreferences 保存的输入提示 demo 实现 1 实现一个 SharedPrefere
  • opencv项目实战(二)——文档扫描OCR识别

    一 项目描述 二 代码详解 2 1 预定义参数 2 2 辅助函数 2 3 文档矫正 2 4 文档识别 三 项目完整代码 一 项目描述 目的 将图片中的文档矫正 并识别文档内容 输入与输出 方法流程 核心思想 采用tesseract ocr进