opencv实际案例(二) 文件扫描以及OCR识别

一、目标:

将图像中我们需要的部分提取出,进行扫描,提取出其中的文字。
opencv实际案例(二) 文件扫描以及OCR识别_第1张图片

二、思路:

  • 首先我们要定位我们在图像中需要的部分,将其轮廓提取出。
    - 1将图像变换大小
    - 2灰度化,高斯滤波,边缘检测
    - 3轮廓提取
    - 4筛选第三步中的轮廓,选择其中较大的
    - 5绘制轮廓的近似,返回其中有四个点的轮廓
image = cv2.imread(args["image"])
ratio = image.shape[0] / 500.0#这里记住变换的比例
orgi = image.copy()
image = resize(orgi,height=500)#这里我们只传入height参数,剩下的width函数会帮我们计算出来
#将图像调整好大小后,我们要进行预处理
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) #灰度化
gray = cv2.GaussianBlur(gray,(5,5),0) #高斯滤波,剔除干扰项
edges = cv2.Canny(gray,75,200) #边缘检测
print("STEP 1:边缘检测",image)
cv2.imshow("image",image)
cv2.imshow("edges",edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
#至此我已经得到了边缘检测的结果,也就是该图像中内容的大致轮廓,现在在该结果中提取轮廓的话会精确很多
cnts = cv2.findContours(edges.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
# 轮廓检测:输入为边缘检测的结果,但是cnts中有好多的轮廓,我只想保留最大的轮廓,按照外接矩形的面积排序,得到前五个最大的轮廓
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:5] #这里的面积不用传入参数,否则会报错,也不知道为啥
for c in cnts:
    peri = cv2.arcLength(c,True)
    # C表示输入的点集
    # epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数,越小就越接近原始轮廓,越大就越接近规则图像(比如说矩形等等)
    # True表示封闭的
    approx = cv2.approxPolyDP(c,0.02*peri,True)
    if len(approx)==4:
        screenCnt = approx
        break
        # 如果我得到近似的结果有四个点,是一个矩形,就代表是我想要的,就break掉
        # 这里的原因有二:因为我扫描的图像是一张方形的白纸,我希望将他完整的提取出
        #             二是稍后会进行透视变换提取出文字,需要四个点的输入
print("STEP 2:获取轮廓")
cv2.drawContours(image,[screenCnt],-1,(0,0,255),2)
cv2.imshow("outline",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

边缘检测后的结果
opencv实际案例(二) 文件扫描以及OCR识别_第2张图片
筛选出的较大轮廓中的近似轮廓结果为四点者
opencv实际案例(二) 文件扫描以及OCR识别_第3张图片
cv2.GuassianBlur

高斯滤波:cv2.GuassianBlur(img, ksize,sigmaX,sigmaY)
sigmaX,sigmaY分别表示X,Y方向的标准偏差。如果仅指定了sigmaX,则sigmaY与sigmaX相同.如果两者都为零,则根据内核大小计算它们。
特征:核中区域贡献率与距离区域中心成正比,权重与高斯分布相关。作用:高斯模糊在从图像中去除高斯噪声方面非常有用。

边缘检测
边缘检测的目的就是找到图像中亮度变化剧烈的像素点构成的集合,表现出来往往是轮廓。如果图像中边缘能够精确的测量和定位,那么,就意味着实际的物体能够被定位和测量,包括物体的面积、物体的直径、物体的形状等就能被测量。

cv2.canny(img,minval,maxval)

使用高斯滤波器,以平滑图像,滤除噪声。
计算图像中每个像素点的梯度强度和方向。
应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
通过抑制孤立的弱边缘最终完成边缘检测。

approxPolyDP( curve,epsilon,closed,approxCurve=None)

求近似轮廓
curve表示输入的点集
epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数,越小就越接近原始轮廓,越大就越接近规则图像(比如说矩形等等),在这里是的精度为轮廓周长的百分之二
True表示封闭的

至此我们得到了原图像中我们所需要的部分的大致轮廓

三、进行透视变换,将原来歪扭的图像变换到平面上

  • 将原图以及轮廓的四个点的坐标传入函数中

    • 1 我们需要通过这四个点的坐标找到他们的位置,重新指定顺序
    • 2 因为轮廓是四边形,但不一定是矩形,所以我们需要求轮廓的四条边的长度
    • 3 定义变换后的图像四个点的坐标
    • 4 进行透视变换
def order_points(pts):
    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[3] = pts[np.argmax(diff)]
    rect[1] = pts[np.argmin(diff)]
    return rect
def four_point_transform(img,pts):
    #透视变换的作用是提取出图像中的文字,传入的参数为原图像,与扫描部分的四个点的坐标
    rect = order_points(pts) #rect中0123号对应的是左上、右上、右下、左下
    (tl,tr,br,bl) = rect
    #我们传入的四个点组成的是四边形,但不一定是矩形,所以我们要将四条边的长度分别算出
    widthA = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tr[1]) ** 2))
    widthB = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    maxWidth = max(int(widthA),int(widthB)) #保留最大值作为宽
    heightA = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    heightB = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[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) #求得变换矩阵M
    warped = cv2.warpPerspective(img,M,(maxWidth,maxHeight))
    # 原始图像img,变换矩阵M,变换后的宽maxWidth和高maxHeight
    # warped为变换后的图像
    # 返回变换后结果
    return warped
#------------------------------------------------------------------
warped = four_point_transform(orgi,screenCnt.reshape(4,2)*ratio)
#orig为原图像(没有经过尺寸变换的图像) screenCnt.reshape将之转换为四个点的坐标,乘ratio是将之还原回原先的比例

变换图像大小的函数

def resize(image,width=None,height=None,inter=cv2.INTER_AREA):
    #这里的参数设为None意思就是在调用的时候可以不传这个参数,如果像image一样的话,就是必须要传的参数
    dim = None
    (h,w) = image.shape[:2] #得到图片的宽和高
    if width is None and height is None: #若宽和高都是默认参数,说明转换后的尺寸与原图相同,返回原图
        return image
    if width is None: #如果只传入了height,我们需要帮他计算一下width
        r = height/float(h)
        dim = (int(w*r),height)
    else: #相反的话我们就需要帮他计算一下height
        r = width/float(w)
        dim = (width,int(h*r))
    resized = cv2.resize(image,dim,interpolation=inter)
    return resized

透视变换的结果
opencv实际案例(二) 文件扫描以及OCR识别_第4张图片

关于python中的None:

  • 在定义resize函数中有个参数=None,这里的参数设为None意思就是在调用的时候可以不传这个参数,如果像image一样的话,就是必须要传的参数。下面一段话说的挺清楚:
  • 你调用参数的时候没有None,你调用函数必须给他传参,circle(这必须写参数传进去才能成功调用这个参数),如果定义函数的时候,你写了extent=None,说明调用这个函数的时候不传参数也可以调用这个函数,里边的代码也可以被执行,但是里边的代码逻辑你要规避这个参数为空会出现的报错问题。

关于None:

  • None是一个特殊的常量。
  • None和False不同。
  • None不是0。
  • None不是空字符串。
  • None和任何其他的数据类型比较永远返回False。
  • None有自己的数据类型NoneType。
  • 你可以将None复制给任何变量,但是你不能创建其他NoneType对象。 Python中的None与NULL(即空字符)的区别:
  • (1)是不同的一种数据类型表示该值是一个空对象,空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
  • 注意:[你可以将None赋值给任何变量,也可以将任何变量赋值给一个None值得对象.]
  • (2)判断的时候,均是False
  • (3)属性不同:使用dir()函数返回参数的属性、方法列表。如果参数包含方法dir(),该方法将被调用。如果参数不包含dir(),该方法将最大限度地收集参数信息。

确定传入四个点的位置
opencv实际案例(二) 文件扫描以及OCR识别_第5张图片
透视变换
下面图片引用xiaowei_cqu的博客【图像处理】透视变换 Perspective Transformation

opencv实际案例(二) 文件扫描以及OCR识别_第6张图片
M = cv2.getPerspectiveTransform(rect,dst)
求得变换矩阵M,rect为原图像中轮廓的四个顶点的坐标,dst为变换后图像的四个顶点的坐标
warped = cv2.warpPerspective(img,M,(maxWidth,maxHeight))
进行透视变换,参数中img为原始图像,M为变换矩阵,第三个参数为变换后的宽maxWidth和高maxHeight。warped为变换后的图像

四、提取透视变换结果中的文字

  • 利用到字符识别插件tesseract

    • 1 安装。tesseract安装
    • 2 将anaconda3→Lib→site-packges→pytesseract→pytesseract.py中的
      在这里插入图片描述
      这一行改为绝对路径。
# https://digi.bib.uni-mannheim.de/tesseract/
# 配置环境变量如E:\Program Files (x86)\Tesseract-OCR
# tesseract -v进行测试
# tesseract XXX.png 得到结果 
# pip install pytesseract
# anaconda lib site-packges pytesseract pytesseract.py
# tesseract_cmd 修改为绝对路径即可
from PIL import Image
import pytesseract
import cv2
import os

preprocess = 'blur' #thresh

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)                                   

opencv实际案例(二) 文件扫描以及OCR识别_第7张图片
完整代码

import numpy as np
import cv2
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-1","--image",required=True,help="Path to the image to be scanned")
args = vars(ap.parse_args())
def order_points(pts):
    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[3] = pts[np.argmax(diff)]
    rect[1] = pts[np.argmin(diff)]
    return rect
def four_point_transform(img,pts):
    #透视变换的作用是提取出图像中的文字,传入的参数为原图像,与扫描部分的四个点的坐标
    rect = order_points(pts) #rect中0123号对应的是左上、右上、右下、左下
    (tl,tr,br,bl) = rect
    #我们传入的四个点组成的是四边形,但不一定是矩形,所以我们要将四条边的长度分别算出
    widthA = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tr[1]) ** 2))
    widthB = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    maxWidth = max(int(widthA),int(widthB)) #保留最大值作为宽
    heightA = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    heightB = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[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) #求得变换矩阵M
    warped = cv2.warpPerspective(img,M,(maxWidth,maxHeight))
    # 原始图像img,变换矩阵M,变换后的宽maxWidth和高maxHeight
    # warped为变换后的图像
    # 返回变换后结果
    return warped
def resize(image,width=None,height=None,inter=cv2.INTER_AREA):
    #这里的参数设为None意思就是在调用的时候可以不传这个参数,如果像image一样的话,就是必须要传的参数
    dim = None
    (h,w) = image.shape[:2] #得到图片的宽和高
    if width is None and height is None: #若宽和高都是默认参数,说明转换后的尺寸与原图相同,返回原图
        return image
    if width is None: #如果只传入了height,我们需要帮他计算一下width
        r = height/float(h)
        dim = (int(w*r),height)
    else: #相反的话我们就需要帮他计算一下height
        r = width/float(w)
        dim = (width,int(h*r))
    resized = cv2.resize(image,dim,interpolation=inter)
    return resized

image = cv2.imread(args["image"])
ratio = image.shape[0] / 500.0#这里记住变换的比例
orgi = image.copy()
image = resize(orgi,height=500)#这里我们只传入height参数,剩下的width函数会帮我们计算出来
#将图像调整好大小后,我们要进行预处理
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) #灰度化
gray = cv2.GaussianBlur(gray,(5,5),0) #高斯滤波,剔除干扰项
edges = cv2.Canny(gray,75,200) #边缘检测
print("STEP 1:边缘检测",image)
cv2.imshow("image",image)
cv2.imshow("edges",edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
#至此我已经得到了边缘检测的结果,也就是该图像中内容的大致轮廓,现在在该结果中提取轮廓的话会精确很多
cnts = cv2.findContours(edges.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
# 轮廓检测:输入为边缘检测的结果,但是cnts中有好多的轮廓,我只想保留最大的轮廓,按照外接矩形的面积排序,得到前五个最大的轮廓
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)[:5] #这里的面积不用传入参数,否则会报错,也不知道为啥
for c in cnts:
    peri = cv2.arcLength(c,True)
    # C表示输入的点集
    # epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数,越小就越接近原始轮廓,越大就越接近规则图像(比如说矩形等等)
    # True表示封闭的
    approx = cv2.approxPolyDP(c,0.02*peri,True)
    if len(approx)==4:
        screenCnt = approx
        break
        # 如果我得到近似的结果有四个点,是一个矩形,就代表是我想要的,就break掉
        # 这里的原因有二:因为我扫描的图像是一张方形的白纸,我希望将他完整的提取出
        #             二是稍后会进行透视变换提取出文字,需要四个点的输入
print("STEP 2:获取轮廓")
cv2.drawContours(image,[screenCnt],-1,(0,0,255),2)
cv2.imshow("outline",image)
cv2.waitKey(0)
cv2.destroyAllWindows()
warped = four_point_transform(orgi,screenCnt.reshape(4,2)*ratio)
#orig为原图像(没有经过尺寸变换的图像) screenCnt.reshape将之转换为四个点的坐标,乘ratio是将之还原回原先的比例
warped = cv2.cvtColor(warped,cv2.COLOR_BGR2GRAY)
warped = cv2.threshold(warped,100,255,cv2.THRESH_BINARY)[1]
cv2.imwrite('scan_1.jpg',warped)
#展示结果
print("STEP 3:变换")
cv2.imshow('original',resize(orgi,height=500))
cv2.imshow('scanned',resize(warped,height=500))
cv2.waitKey(0)
cv2.destroyAllWindows()

五、收获

我们要提取图像中我们需要的部分,可以先对其进行滤波、边缘检测,这样可以改善结果。
透视变换的流程,可以将图像重新投影到新的平面上,更加方便处理。
OCR字符识别(不知道为什么效果不好)

你可能感兴趣的:(学习笔记,opencv)