概念:
使用OpenCV库实现图像的透视变换处理,以矫正图像中的透视失真。通过本实验,学习者将掌握图像处理的基本操作,包括图像的读取、显示、大小调整、灰度转换、二值化、轮廓检测、轮廓近似以及透视变换。
步骤:
1. 导入库
导入必要的Python库,包括OpenCV和NumPy。
import numpy as np
import cv2
2. 定义函数
定义几个函数来处理图像的不同步骤。
cv_show(name, img)
- 显示图像的函数,接受窗口名称和图像作为参数。
def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)
order_points(pts)
- 对四个点进行排序,确保它们按照左上、右上、右下、左下的顺序。
def order_points(pts):rect = np.zeros((4,2),dtype='float32') # 创建一个4x2的数组,指定了数组中元素的数据类型为32位浮点数# 按顺序找到对应坐标0123分别是左上、右上、右下、左下s = pts.sum(axis=1) # 对pts矩阵的每一行进行求和操作rect[0] = pts[np.argmin(s)]#左上角的点rect[2] = pts[np.argmax(s)]#右下角的点diff = np.diff(pts,axis=1) # 对pts矩阵的每一行进行差值rect[1] = pts[np.argmin(diff)]#右上角的点rect[3] = pts[np.argmax(diff)]#左下角的点return rect
four_point_transform(image, pts)
- 根据四个点进行图像的透视变换。
def four_point_transform(image,pts):# 获取输入坐标点rect = order_points(pts)(tl,tr,br,bl) = rect#tl 是左上角点,tr 是右上角点,br 是右下角点,bl 是左下角点。# 计算两个可能的宽度,取这两个宽度的最大值作为新图像的宽度。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')# 图像透视变换 cv2.getPerspectiveTransform(src,dst[,solveMethod])→ MP获得转换之间的关系# cy2.warpPerspective(src, Mp, dsizel, dstl, flagsl, borderModel, borderValue]]1])- dst# #参数说明:# src:变换前图像四边形顶点坐标/第2个是原图# MP:透视变换矩阵,3行3列# dsize:输出图像的大小,二元元组(width,heiqht)M = cv2.getPerspectiveTransform(rect,dst)#计算透视变换矩阵 M,它描述了如何将原始图像中的点映射到目标坐标。warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight))#透视变换# 返回变换后的结果return warped
resize(image, width=None, height=None, inter=cv2.INTER_AREA)
- 调整图像大小的函数,接受图像、宽度、高度和插值方法作为参数。
#用于调整图像的大小。 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 imageif width is None:#如果只指定了高度,计算新的宽度以保持图像的宽高比。r=height/float(h)dim=(int(w*r),height)else:#如果只指定了宽度,计算新的高度以保持图像的宽高比。r=width/float(w)dim=(width,int(h*r))#根据计算出的尺寸 dim 调整图像大小。resized=cv2.resize(image,dim,interpolation=inter) # 默认为cV2.INTER_AREA,即面积插值,适用于缩放图像。return resized
3. 读取和显示原始图像
读取名为fapiao.jpg
的发票图像,并显示。
# 读取输入
image = cv2.imread('fapiao.jpg')
cv_show('image',image)
4. 图像缩小处理
将图像缩小到一定高度(例如500像素),并显示缩小后的图像,以便于处理。
# 图片过大,进行缩小处理
ratio = image.shape[0] / 500.0 # 计算缩小比率
orig = image.copy()
image = resize(orig, height=500)
cv_show('1',image)
5. 轮廓检测
将图像转换为灰度图,进行二值化处理,并检测图像轮廓。
# 轮廓检测
print('step 1 : 轮廓检测')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # 灰度图
#二值化处理
edged = cv2.threshold(gray,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 自动寻找网值二位化
#检测二值化图像中的轮廓。
cnts = cv2.findContours(edged.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[1]
#绘制轮廓
image_contours = cv2.drawContours(image.copy(),cnts,-1,(0,0,255),1)
cv_show('image_contours',image_contours)
6. 获取最大轮廓
找到面积最大的轮廓,并进行轮廓近似。
print('step 2 :获取最大轮廓')
#获取最大轮廓
screenCnt = sorted(cnts,key = cv2.contourArea,reverse=True)[0] # 获取面积最大的轮廓
#计算轮廓周长
peri = cv2.arcLength(screenCnt,True) # 计算轮廓周长
#轮廓近似
screenCnt = cv2.approxPolyDP(screenCnt,0.02*peri,True) # 轮廓近似
#绘制近似轮廓
image_contour = cv2.drawContours(image.copy(),[screenCnt],-1,(0,255,0),2)cv2.imshow('image_contour',image_contour)
cv2.waitKey(0)
7. 透视变换
使用近似后的轮廓点进行透视变换,矫正发票图像的透视失真。
# 透视变换
warped = four_point_transform(orig,screenCnt.reshape(4,2)*ratio)
8. 显示和保存结果
显示透视变换后的图像,并将其保存为新文件。
cv2.imwrite('invoice_new.jpg',warped)
cv2.namedWindow('xx',cv2.WINDOW_NORMAL)
cv2.imshow('xx',warped)
cv2.waitKey(0)
9.运用形态学变换图像
形态学闭运算使图像更加清晰
a=cv2.imread('invoice_new.jpg')
a=cv2.cvtColor(a,cv2.COLOR_BGR2GRAY)
a=cv2.threshold(a,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
a=cv2.resize(a,(800,600))
a=np.rot90(a,1)kernel = np.ones((1,1), np.uint8)c=cv2.morphologyEx(a,cv2.MORPH_CLOSE,kernel)cv2.imshow('a',c)
cv2.waitKey(0)
代码结果
完整代码
import numpy as np
import cv2
#透视变换
def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)def order_points(pts):rect = np.zeros((4,2),dtype='float32') # 创建一个4x2的数组,指定了数组中元素的数据类型为32位浮点数# 按顺序找到对应坐标0123分别是左上、右上、右下、左下s = pts.sum(axis=1) # 对pts矩阵的每一行进行求和操作rect[0] = pts[np.argmin(s)]#左上角的点rect[2] = pts[np.argmax(s)]#右下角的点diff = np.diff(pts,axis=1) # 对pts矩阵的每一行进行差值rect[1] = pts[np.argmin(diff)]#右上角的点rect[3] = pts[np.argmax(diff)]#左下角的点return rectdef four_point_transform(image,pts):# 获取输入坐标点rect = order_points(pts)(tl,tr,br,bl) = rect#tl 是左上角点,tr 是右上角点,br 是右下角点,bl 是左下角点。# 计算两个可能的宽度,取这两个宽度的最大值作为新图像的宽度。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')# 图像透视变换 cv2.getPerspectiveTransform(src,dst[,solveMethod])→ MP获得转换之间的关系# cy2.warpPerspective(src, Mp, dsizel, dstl, flagsl, borderModel, borderValue]]1])- dst# #参数说明:# src:变换前图像四边形顶点坐标/第2个是原图# MP:透视变换矩阵,3行3列# dsize:输出图像的大小,二元元组(width,heiqht)M = cv2.getPerspectiveTransform(rect,dst)#计算透视变换矩阵 M,它描述了如何将原始图像中的点映射到目标坐标。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 imageif width is None:#如果只指定了高度,计算新的宽度以保持图像的宽高比。r=height/float(h)dim=(int(w*r),height)else:#如果只指定了宽度,计算新的高度以保持图像的宽高比。r=width/float(w)dim=(width,int(h*r))#根据计算出的尺寸 dim 调整图像大小。resized=cv2.resize(image,dim,interpolation=inter) # 默认为cV2.INTER_AREA,即面积插值,适用于缩放图像。return resized# 读取输入
image = cv2.imread('fapiao.jpg')
cv_show('image',image)# 图片过大,进行缩小处理
ratio = image.shape[0] / 500.0 # 计算缩小比率
orig = image.copy()
image = resize(orig, height=500)
cv_show('1',image)# 轮廓检测
print('step 1 : 轮廓检测')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # 灰度图
#二值化处理
edged = cv2.threshold(gray,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 自动寻找网值二位化
#检测二值化图像中的轮廓。
cnts = cv2.findContours(edged.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[1]
#绘制轮廓
image_contours = cv2.drawContours(image.copy(),cnts,-1,(0,0,255),1)
cv_show('image_contours',image_contours)print('step 2 :获取最大轮廓')
#获取最大轮廓
screenCnt = sorted(cnts,key = cv2.contourArea,reverse=True)[0] # 获取面积最大的轮廓
#计算轮廓周长
peri = cv2.arcLength(screenCnt,True) # 计算轮廓周长
#轮廓近似
screenCnt = cv2.approxPolyDP(screenCnt,0.02*peri,True) # 轮廓近似
#绘制近似轮廓
image_contour = cv2.drawContours(image.copy(),[screenCnt],-1,(0,255,0),2)cv2.imshow('image_contour',image_contour)
cv2.waitKey(0)# 透视变换
warped = four_point_transform(orig,screenCnt.reshape(4,2)*ratio)
cv2.imwrite('invoice_new.jpg',warped)
cv2.namedWindow('xx',cv2.WINDOW_NORMAL)
cv2.imshow('xx',warped)
cv2.waitKey(0)a=cv2.imread('invoice_new.jpg')
a=cv2.cvtColor(a,cv2.COLOR_BGR2GRAY)
a=cv2.threshold(a,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
a=cv2.resize(a,(800,600))
a=np.rot90(a,1)kernel = np.ones((1,1), np.uint8)c=cv2.morphologyEx(a,cv2.MORPH_CLOSE,kernel)cv2.imshow('a',c)
cv2.waitKey(0)