目录
一、透视变换
1、什么是透视变换
透视变换是一种图像处理技术,用于将二维平面上的图像或物体映射到三维空间中。它通过改变图像的视角和投影来创建一个具有透视效果的图像。
透视变换通常用于计算机图像形态学和计算机视觉领域,用于实现图像的透视效果、立体视觉、图像校正等应用。它可以模拟人眼在观察远景时的透视效果,使得远处的物体看起来比近处的物体小,同时使得平行线在远处会相交的视觉效果。
透视变换的实现通常需要通过计算图像中各点在三维空间中的坐标,并将其映射回二维平面上,从而实现透视效果。这个过程涉及到几何变换、矩阵运算和投影变换等数学概念和算法。
2、操作步骤
1)选择透视变换的源图像和目标图像
源图像是需要进行透视变换的原始图像,目标图像是希望得到的透视变换后的图像。
2)确定透视变换所需的关键点
根据透视变换的要求,需要选择源图像中的四个关键点以及对应的目标图像中的四个关键点。这四个关键点共同决定了透视变换的变换矩阵。
3)计算透视变换的变换矩阵
通过四个关键点的对应关系,使用透视变换的数学公式计算出透视变换的变换矩阵。这个变换矩阵将源图像中的像素映射到目标图像中的像素。
4)对源图像进行透视变换
使用计算得到的变换矩阵,对源图像中的每个像素进行变换,计算其在目标图像中的对应像素位置。
5)对变换后的图像进行插值处理
由于透视变换可能会导致源图像中的像素映射到目标图像中的非整数位置,因此需要对其进行插值处理,以得到最终的目标图像。
二、轮廓检测
1、什么是轮廓检测
轮廓检测是一种图像处理技术,用于在图像中找到物体的边界。在图像处理领域中,物体的边界通常被表示为连续的曲线,这些曲线被称为轮廓。轮廓检测算法可以识别图像中的明显变化或不连续的像素,从而确定物体的形状和结构。
轮廓检测算法的基本原理是通过分析图像中的亮度、颜色或纹理等特征,找到物体与背景之间的显著边缘或变化。常用的轮廓检测算法包括Canny边缘检测、Sobel算子、拉普拉斯算子等。
通过轮廓检测,可以实现图像分割、目标识别、形状匹配等应用。在计算机视觉和图像处理领域中,轮廓检测是一项重要的技术,广泛应用于物体检测与跟踪、图像分析与理解、机器视觉等领域。
2、操作步骤
1)图像预处理
首先对输入图像进行预处理,可以包括灰度化、平滑滤波、边缘增强等操作,以减少噪声和突出边缘信息。
2)边缘检测
使用边缘检测算法(如Canny、Sobel、拉普拉斯等)来检测图像中的边缘。这些算法通过计算像素间的梯度或差异,找到亮度或颜色变化较大的区域。
3)边缘连接
将离散的边缘点连接成连续的轮廓线。常用的方法包括利用边缘点的邻域信息进行连接,或者利用轮廓线的闭合性质进行曲线追踪。
4)轮廓筛选
根据一定的准则对检测到的轮廓进行筛选,去除无关的轮廓。可以根据轮廓的长度、面积、形状等特征进行筛选。
5)轮廓绘制
最后,将筛选后的轮廓绘制在原始图像上,以便观察和分析。
具体可参考博客:
三、项目实施
1、定义展示图片函数
import numpy as np
import cv2
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey(0)
2、定义自动缩放图片大小函数
# 调整图像高宽,保持图像宽高比不变
def resize(image,width=None,height=None ,inter=cv2.INTER_AREA): # 输入参数为图像、可选宽度、可选高度、插值方式默认为cv2.INTER_AREA,即面积插值
dim = None # 存储计算后的目标尺寸w、h
(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: # 此处表示为width不是None,即指定了宽度,与上述方法一致,计算比值
r = width/float(w)
dim = (width,int(h*r))
resized = cv2.resize(image,dim,interpolation=inter) # 指定图像大小为上述的dim,inter默认为cV2.INTER_AREA,即面积插值,适用于缩放图像。
return resized
3、定义轮廓点的排序函数
def order_points(pts): # 对输入的四个点按照左上、右上、右下、左下进行排序
rect = np.zeros((4,2),dtype='float32') # 创建一个4*2的数组,用来存储排序之后的坐标位置
# 按顺序找到对应坐标0123分别是左上、右上、右下、左下
s = pts.sum(axis=1) # 对pts矩阵的每个点的x y相加
rect[0] = pts[np.argmin(s)] # np.argmin(s)表示数组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 # 返回排序好的四个点的坐标
4、定义透视变换函数
# 将透视扭曲的矩形变换成一个规则的矩阵
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) # 根据原始点和变换后的点计算透视变换矩阵M
warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight)) # 对原始图像,针推变换矩阵和输出图像大小进行透视变换,返回变换后的图片
# 返回变换后的结果
return warped
5、读取原图并缩放
# # 读取输入
image = cv2.imread('fapiao.jpg') # 读取原图
cv_show('image',image) # 展示原图
# 图片过大,进行缩小处理
ratio = image.shape[0] / 500.0 # 计算缩小比率,[0]表示图像的高
orig = image.copy() # 对原图复制生成副本
image = resize(orig, height=500) # 更改图像尺寸,输入高度自动生成宽度
cv_show('1',image) # 展示缩放后的图片
运行结果:
6、进行轮廓检测
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # 灰度图
edged = cv2.threshold(gray,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 进行二值化,cv2.THRESH_OTSU自动寻找最优全局阈值,255表示高于最优阈值时将其更改为255
cnts = cv2.findContours(edged.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[1] # 轮廓检测
# cv2.RETR_LIST表示检索所有轮廓,但是不建立层次关系
# cv2.CHAIN_APPROX_SIMPLE 表示只保存轮廓拐点的信息
# 总体返回处理的图像、轮廓列表、层次结构,这里返回索引为1,表示返回轮廓列表
image_contours = cv2.drawContours(image.copy(),cnts,-1,(0,0,255),1) # 绘制所有轮廓
# 在原始图像的副本上绘制了轮廓
# 绘制轮廓的位置为上述获取的拐点信息,绘制线条颜色为红色BRG(0,0,255),线条粗细为1个像素
cv_show('image_contours',image_contours) # 展示绘制好的图片
运行结果:
5、绘制最大轮廓
screenCnt = sorted(cnts,key = cv2.contourArea,reverse=True)[0] # 对上述获取的轮廓列表,排序依据是轮廓面积,reverse=True表示降序,[0]表示获取面积最大的轮廓
peri = cv2.arcLength(screenCnt,True) # 计算最大轮廓的周长
screenCnt = cv2.approxPolyDP(screenCnt,0.02*peri,True) # 轮廓近似,近似为一个多边形,表示新的轮廓与原来的轮廓最大距离不超过原始轮廓宽度的0.02倍,True表示轮廓为闭合的
image_contour = cv2.drawContours(image.copy(),[screenCnt],-1,(0,255,0),2) # 绘制轮廓,将上述找到的轮廓绘制到原图的副本上
cv2.imshow('image_contour',image_contour)
cv2.waitKey(0)
运行结果:
6、对最大轮廓进行透视变换
warped = four_point_transform(orig,screenCnt.reshape(4,2)*ratio) # 输入参数原图,将最大轮廓图形状改变为4*2的格式,即四个点,然后乘以上述定义的比率来缩放轮廓
cv2.imwrite('invoice_new.jpg',warped) # 将经过透视变换处理的图片存入本地
cv2.namedWindow('xx',cv2.WINDOW_NORMAL) # 设置一个窗口,名称为xx,这个窗口大小用户可通过拖动随意调节大小
cv2.imshow('xx',warped) # 展示经过透视变换处理的图片
cv2.waitKey(0)
运行结果:
7、旋转、二值化处理
# 二值处理
warped = cv2.cvtColor(warped,cv2.COLOR_BGR2GRAY) # 导入新的图片的灰度图
ref = cv2.threshold(warped,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)[1] # 对灰度图进行二值化处理
kernel = np.ones((2,2),np.uint8) # 设置一个单位矩阵,大小为2*2,表示设置核kernel的大小
ref_new = cv2.morphologyEx(ref,cv2.MORPH_CLOSE,kernel) # 闭运算,先膨胀再腐蚀
ref_new = resize(ref_new.copy(),width=500) # 对闭运算处理完的图像重置大小
cv_show('yy',ref_new)
rotated_image = cv2.rotate(ref_new,cv2.ROTATE_90_COUNTERCLOCKWISE) # 对图像逆时针旋转90度
cv2.imshow('result',rotated_image)
cv2.waitKey(0)