• 倾斜文档扫描与字符识别(opencv,坐标变换分析)


    项目源码

    可在github下载:

    https://github.com/chenshunpeng/Doc-scan

    图像预处理

    首先导入工具包

    import numpy as np
    import argparse
    import cv2
    
    • 1
    • 2
    • 3

    设置命令行参数

    # 构造参数解析并解析参数
    # we instantiate the ArgumentParser object as ap(实例化)
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--image", default='./images/pic.jpg'
    				, required = False, help = "Path to the image to be scanned")
    args = vars(ap.parse_args())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    读取输入,对图像进行缩放

    图像:

    在这里插入图片描述

    # 读取输入
    image = cv2.imread(args["image"])
    # 图像缩放,坐标也会相同变化
    ratio = image.shape[0] / 500.0
    orig = image.copy()
    image = resize(orig, height = 500)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里的ratio是4.896

    在这里插入图片描述

    在此给出resize函数定义:

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对图像进行预处理,并展示预处理结果

    # 转灰度图
    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)
    cv2.imshow("Edged", edged)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果:

    在这里插入图片描述

    对于一些陌生的知识(如Canny边缘检测),可以在w3cschool看OpenCV的文档的翻译进行学习(https://www.w3cschool.cn/opencv

    在这里插入图片描述

    推荐有一定英语水平的直接看OpenCV官方文档(https://docs.opencv.org/3.4/index.html):

    在这里插入图片描述

    获取最优轮廓

    轮廓提取

    # cv.findContours()函数中有三个参数,第一个是源图像,第二个是轮廓检索模式,第三个是轮廓近似方法。
    # 它输出轮廓和层次结构。Contours是图像中所有轮廓的Python列表。每个单独的轮廓都是对象边界点的 (x,y) 坐标的 Numpy 数组
    cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0]
    # 对轮廓按照面积从大到小排序,取前5个(先从小到大排序,之后取reverse翻转)
    cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在 Opencv4中,cv2.findContour()仅返回 2 个值:contours, hierachy,所以在这里用[0]得到第一个值

    具体阅读这个网址:https://docs.opencv.org/4.x/d4/d73/tutorial_py_contours_begin.html

    排序前有493个轮廓:

    在这里插入图片描述

    排序后取面积最大的5个轮廓(最外边的框架面积一定最大,所以周长也较大,点数也较多):

    在这里插入图片描述

    筛选轮廓

    进行遍历轮廓,寻找最优轮廓

    # 对screenCnt初始化,不然可能会有警告
    screenCnt = [[0,0], [255,0], [255,255], [0,255]]
    
    for c in cnts:
    	# 计算轮廓近似
    	peri = cv2.arcLength(c, True)
    
    	# cv2.approxPolyDP()的主要功能是把一个连续光滑曲线折线化,之后多边形逼近
    	# c表示输入的点集
    	# 其中第二个参数epsilon的作用:double epsilon:判断点到相对应的line segment的距离的阈值
    	# (距离大于此阈值则舍弃,小于此阈值则保留,epsilon越小,折线的形状越“接近”曲线。)
    	# True表示封闭的
    	approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    	# 因为是文本行,返回的框至少应该是个四边形,只要找到那个最大的四边形,就可以退出了
    	if len(approx) == 4:
    		screenCnt = approx
    		break
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    v2.approxPolyDP函数了解可看:

    Google:Contour Features(OpenCV官网教程)

    在这里插入图片描述

    总之,这种函数类的知识推荐 Google 搜索,可以直接搜索到官方文档,权威一些

    最优轮廓如下:

    在这里插入图片描述

    我们发现最优轮廓的确是那个面积最大的轮廓,但点数最不是最多的(1009<1042)

    在这里插入图片描述

    screenCnt的值:

    在这里插入图片描述

    原始与变换坐标计算

    通过screenCnt.reshape(4, 2)将其变为4x2的矩阵,之后* ratio得到的pts矩阵为:

    在这里插入图片描述

    即:
    { t 1 ( 2222.784 , 151.776 ) t 1 ( 93.024 , 876.384 ) t 1 ( 685.44 , 2291.328 ) t 1 ( 2624.256 , 1439.424 )

    {t1(2222.784,151.776)t1(93.024,876.384)t1(685.44,2291.328)t1(2624.256,1439.424)" role="presentation" style="position: relative;">{t1(2222.784,151.776)t1(93.024,876.384)t1(685.44,2291.328)t1(2624.256,1439.424)
    t1(2222.784,151.776)t1(93.024,876.384)t1(685.44,2291.328)t1(2624.256,1439.424)

    这里4个点用 t 1 − 4 t_{1-4} t14 表示是因为这4个点的前后顺序不能代表其相对位置关系,两者没有任何关联

    之后通过order_points函数获取输入坐标点

    def order_points(pts):
    	# 一共4个坐标点
    	rect = np.zeros((4, 2), dtype = "float32")
    
    	# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
    	# 计算左上,右下
    	s = pts.sum(axis = 1)
    	rect[0] = pts[np.argmin(s)]
    	rect[2] = pts[np.argmax(s)]
    
    	# 计算右上和左下
    	diff = np.diff(pts, axis = 1)
    	rect[1] = pts[np.argmin(diff)]
    	rect[3] = pts[np.argmax(diff)]
    
    	return rect
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    过程如下:

    在这里插入图片描述

    之后进行位置变换,给出函数four_point_transform

    def four_point_transform(image, pts):
    	# 获取输入坐标点
    	rect = order_points(pts)
    	(tl, tr, br, bl) = rect
    
    	# 计算输入的w和h值
    	widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    	widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    	maxWidth = max(int(widthA), int(widthB))
    
    	heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    	heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    	maxHeight = max(int(heightA), int(heightB))
    
    	# 变换后对应坐标位置
    	dst = np.array([
    		[0, 0],
    		[maxWidth - 1, 0],
    		[maxWidth - 1, maxHeight - 1],
    		[0, maxHeight - 1]], dtype = "float32")
    
    	# 计算变换矩阵
    	M = cv2.getPerspectiveTransform(rect, dst)
    	warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
    
    	# 返回变换后结果
    	return warped
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    中间变量如下:

    在这里插入图片描述
    给出(tl, tr, br, bl)变换前)的表达:
    { A t l ( 93.024 , 876.384 ) B t r ( 2222.784 , 151.776 ) C b r ( 2624.256 , 1439.424 ) D b l ( 685.44 , 2291.328 )

    {Atl(93.024,876.384)Btr(2222.784,151.776)Cbr(2624.256,1439.424)Dbl(685.44,2291.328)" role="presentation" style="position: relative;">{Atl(93.024,876.384)Btr(2222.784,151.776)Cbr(2624.256,1439.424)Dbl(685.44,2291.328)
    Atl(93.024,876.384)Btr(2222.784,151.776)Cbr(2624.256,1439.424)Dbl(685.44,2291.328)

    给出dst变换后)的表达:
    { A ( 0. , 0. ) B ( 2248. , 0. ) C ( 2248. , 1532. ) D ( 0. , 1532. )

    {A(0.,0.)B(2248.,0.)C(2248.,1532.)D(0.,1532.)" role="presentation" style="position: relative;">{A(0.,0.)B(2248.,0.)C(2248.,1532.)D(0.,1532.)
    A(0.,0.)B(2248.,0.)C(2248.,1532.)D(0.,1532.)

    画个图大概是这样:
    在这里插入图片描述

    其中通过cv2.getPerspectiveTransform(rect, dst)函数求解变换矩阵

    原理可看b站视频:透视变换矩阵求解推导(通俗易懂)

    显示识别结果

    图像处理

    之后依次灰度化,二值化处理:

    # 灰度,二值处理
    warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
    ref = cv2.threshold(warped, 100, 255, cv2.THRESH_BINARY)[1]
    
    # 把ref写入scan.jpg
    cv2.imwrite('scan.jpg', ref)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    修改图片大小,并逆时针旋转90度

    借鉴博客:

    opencv-python 图片旋转90度

    cv2.getRotationMatrix2D()

    # 修改图片大小,同时图像逆时针旋转90度
    
    # 获取图片,修改一下图片的大小
    img = cv2.imread("scan.jpg")
    # 注意需要制定返回值为img2,不能没有返回值
    img2 = cv2.resize(img, (900, 600))
    cv2.imshow("temp", img2)
    cv2.waitKey(0)
    # 对图片进行旋转
    # 方法一
    # img90 = np.rot90(img2)
    # 方法二
    # 绕任意点旋转
    # 第一个参数旋转中心,第二个参数旋转角度,第三个参数:缩放比例
    M = cv2.getRotationMatrix2D((450, 450), 90, 1)
    # 仿射变化
    # 第三个参数:输入图像的大小
    img90 = cv2.warpAffine(img2, M, img2.shape[:2])
    # (600, 900)与img2.shape[:2]等价
    # img90 = cv2.warpAffine(img2, M, (600, 900))
    cv2.imwrite('scan.jpg', img90)
    # cv2.imshow("rotate", img90)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    显示图像

    print("STEP 3: 变换")
    # cv2.imshow("Original", resize(orig, height = 650))
    cv2.imshow("Scanned", resize(img90, height = 650))
    cv2.waitKey(0)
    
    • 1
    • 2
    • 3
    • 4

    旋转前:

    在这里插入图片描述
    旋转后:

    在这里插入图片描述

    OCR识别

    在这里用github开源OCR软件tesseract,可看:

    https://github.com/tesseract-ocr/tesseract

    下载地址:

    https://digi.bib.uni-mannheim.de/tesseract/

    我的安装路径是:F:\soft_f\Tesseract-OCR,安装完记得配一下环境变量(我只配了用户变量),之后输入tesseract -v有(版本可高可低,我安装的是高版本):

    在这里插入图片描述

    在python中pytesseract与安装在本地的tesseract-ocr.exe文件是一起使用的,因此需要pip install pytesseract,我的python环境在F:\F_software\Anaconda,安装如下:

    在这里插入图片描述

    在执行text = pytesseract.image_to_string(Image.open(filename))时发生了报错:

    在这里插入图片描述

    发现即使是配置了环境变量,也找不到tesseract的路径(原因未知QAQ),解决办法:打开F:\F_software\Anaconda\Lib\site-packages\pytesseract

    在这里插入图片描述

    修改一下tesseract_cmd = 'F:/soft_f/Tesseract-OCR/tesseract.exe'

    在这里插入图片描述

    如果还是报错就重启一下电脑(有时候有些配置需要重启电脑才能生效)

    这样就可以识别了,代码如下:

    from PIL import Image
    import pytesseract
    import cv2
    import os
    
    preprocess = 'blur' #thresh
    
    # 读入scan.py的输出结果图像:scan.jpg
    image = cv2.imread('scan.jpg')
    # 转灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    # 将一幅灰度图二值化
    if preprocess == "thresh":
        gray = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    # 实现中值滤波
    if preprocess == "blur":
        gray = cv2.medianBlur(gray, 3)
        
    filename = "{}.png".format(os.getpid())
    cv2.imwrite(filename, gray)
        
    text = pytesseract.image_to_string(Image.open(filename))
    print(text)
    os.remove(filename)
    
    cv2.imshow("Image", image)
    cv2.imshow("Output", gray)
    cv2.waitKey(0)                                   
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    识别结果:

    =
    
    Ce ee ee ee ee
    
    WHOLE FOODS MARKE)
    399 POST RD WEST - (203) 227-6858
    
    WHOLE
    FOODS
    (mM AR KE T)
    
    WESTPORT.CT 06880
    
    Seb BALUN LS NP 4
    $65 BACUN LS NP 499
    
    $65 BREON LS NP 4
    
    305 BACUN iS fi 4
    BROTH CHIL ae é 19
    HLQUR ALMUND NP ol 99
    
    CHKN BRST BNLSS SK 8 18 80
    HEAVY CREAM $.39
    BALSMC REODUCT 6.49
    
    BEEF GRND 85/15 5.04
    
    JUICE COF CASHEW 8
    DOCS PINI ORGAile NP 14.49
    HNY ALMOND bulithk NP 9
    
    eee TAX 00 Rat 101.33
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    2023中国物流系统集成商百强榜研究报告(附下载)
    MySql分库分表
    [Flask]Pycharm+Flask零基础项目搭建入门
    离线数仓搭建_07_数仓建模理论
    【3D游戏建模全流程教学】使用3dsmax与UE4制作世界末日地铁场景
    tiup dm deploy
    [数据可视化] 南丁格尔玫瑰图
    054_末晨曦Vue技术_处理边界情况之组件之间的循环引用
    Gradle-04:BOM管理依赖版本
    运维 Windows cmd 操作
  • 原文地址:https://blog.csdn.net/qq_45550375/article/details/125966520