前言
在图像处理中,透视变更(Perspective Transformation) 是一种强大的技能,能够校正因视角倾斜导致的图像变形。无论是扫描文档的主动矫正、车牌识别,照旧增强现实(AR)中的假造物体叠加,透视变更都饰演着紧张角色。本文将通过OpenCV库,手把手教你把握透视变更的核心原理与代码实现。
一、什么是透视变更?
透视变更是一种将图像从任意视角投影到新视角的几何变更。与仅能处理平移、旋转和缩放的仿射变更差别,透视变更可以处理三维视角变化,彻底改变图像的投影关系,实现“视角拉正”的效果。
核心特点:
- 可将倾斜拍摄的图像转换为正视图
- 需要提供4组对应点(原图坐标 + 目标坐标)
- 通过变更矩阵实现非线性映射
二、透视变更的过程
对一张我们即将做透视变更图像,起首要获取到图像中的4个坐标点,用于与目标图像中的坐标对应,这四个点照旧有次序的以坐标轴原点为参照点,距离原点近来的点为0号坐标,最远的为2号坐标,这两个点是最容易区分出来的;1号和3号位置可以通过坐标相减作为区分,距离X轴近的坐标的y值小于x值,以是按照x坐标减去y坐标得到的值1号坐标的值大于3号坐标的值。
区分0和2号坐标点:对四个点每个点坐标的x和y的值相加求和,我们发现,针对任意图片表面,如果被四个点描绘,距离原点近来的点求和的值最小,在右下点的值求和的数值最大,可以区分出左上和右下两个点
区分1和3号坐标点:对四个点每个点坐标的x和y的值相减(x-y),针对任意图片表面,如果被四个点描绘,位于右上角做差的值为一个很大的正数,在左下点的值做差的数值为负数,可以区分出左下和右上两个点。
水平为x轴
垂直为y轴
三、OpenCV透视变更核心函数
cv2.getPerspectiveTransform()
- 作用:根据4组对应点计算3x3透视变更矩阵。
- 输入参数:
- src: 原图4个点的坐标(格式:np.float32([[x1,y1], [x2,y2], …]))
- dst: 目标图像对应4个点的坐标
cv2.warpPerspective()
- 作用:应用变更矩阵执行透视变更。
- 参数:
- src: 输入图像
- M: 变更矩阵
- dsize: 输出图像尺寸
四、文档扫描校正(代码)
目标:将倾斜文档转为正视图
1、预处理
- import numpy as np
- import cv2
- def cv_show(name,img):
- cv2.imshow(name,img)
- cv2.waitKey(0)
- # 调整图像高宽,保持图像宽高比不变
- 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
复制代码 2、定义表面点的排序函数
- 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 # 返回排序好的四个点的坐标
复制代码 3、定义透视变更函数
- # 将透视扭曲的矩形变换成一个规则的矩阵
- 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
复制代码 4、读取原图并缩放
- # # 读取输入
- 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) # 展示缩放后的图片
复制代码
5、表面检测
- 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) # 展示绘制好的图片
复制代码
6、绘制最大表面
- 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)
复制代码
7、对最大表面进行透视变更
- 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)
复制代码
8、旋转、二值化处理
- # 二值处理
- 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)
复制代码
总结
通过OpenCV的透视变更,我们能够轻松办理因拍摄角度导致的图像形变题目。无论是手动标定点照旧结合主动检测算法,这一技能都为复杂场景下的图像处理提供了基础支持。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |