OpenCV中的透视变换方法详解

文章目录

  • 引言
  • [1. 什么是透视变换](#1. 什么是透视变换)
  • [2. 透视变换的数学原理](#2. 透视变换的数学原理)
  • [3. OpenCV中的透视变换代码实现](#3. OpenCV中的透视变换代码实现)
    • [3.1 首先定义四个函数](#3.1 首先定义四个函数)
      • [ 3.1.1 cv_show() 函数](# 3.1.1 cv_show() 函数)
      • [ 3.1.2 def resize() 函数](# 3.1.2 def resize() 函数)
      • [ 3.1.3 order_points() 函数](# 3.1.3 order_points() 函数)
      • [ 3.1.4 four_point_transform() 函数](# 3.1.4 four_point_transform() 函数)
    • [3.2 读取图片并做预处理](#3.2 读取图片并做预处理)
    • [3.3 轮廓检测](#3.3 轮廓检测)
    • [3.4 获取最大轮廓](#3.4 获取最大轮廓)
    • [3.5 透视变换矫正](#3.5 透视变换矫正)
    • [3.6 保存矫正后的图片并显示窗口属性](#3.6 保存矫正后的图片并显示窗口属性)
  • [4. 结语](#4. 结语)

引言

透视变换是计算机视觉中一项重要的图像处理技术,它能够将图像从一个视角投影到另一个视角,广泛应用于文档校正、增强现实、视角转换等场景。本文将深入探讨OpenCV中透视变换的原理与实现方法。

1. 什么是透视变换

透视变换(Perspective Transformation)是一种将图像从一个平面投影到另一个平面的变换方式。与仿射变换不同,透视变换能够处理"近大远小"的透视效果,更真实地模拟人眼观察世界的视角变化。

2. 透视变换的数学原理

透视变换可以用一个3×3的变换矩阵表示:

python 复制代码
[a11 a12 a13]
[a21 a22 a23]
[a31 a32 a33]

对于原始图像中的点(x,y),变换后的坐标(x',y')计算方式为:

python 复制代码
x' = (a11*x + a12*y + a13) / (a31*x + a32*y + a33)
y' = (a21*x + a22*y + a23) / (a31*x + a32*y + a33)

3. OpenCV中的透视变换代码实现

3.1 首先定义四个函数

3.1.1 cv_show() 函数

python 复制代码
def cv_show(name,img):
    cv2.imshow(name, img)
    cv2.waitKey(0)

这段代码是用来输入图像并将其显示出来


3.1.2 def resize() 函数

python 复制代码
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

这段代码定义了一个名为 resize 的函数,用于调整图像的大小(缩放),同时保持图像的宽高比例(aspect ratio)。它使用 OpenCV(cv2)来实现图像缩放。


函数参数说明:

  • image: 输入的图像(NumPy 数组格式,OpenCV 默认读取的图像)。
  • width (可选): 目标宽度(如果提供,则按宽度缩放)。
  • height (可选): 目标高度(如果提供,则按高度缩放)。
  • inter (可选): 插值方法(默认 cv2.INTER_AREA,适用于缩小图像)。

函数逻辑解析

1.获取原始尺寸:

python 复制代码
(h, w) = image.shape[:2]  # 获取图像的高度和宽度

2.检查是否传入 width 或 height:

  • 如果两者都未提供(width is None and height is None),直接返回原图。
  • 如果只提供 height(width is None),则按高度缩放,宽度按比例计算:
python 复制代码
r = height / float(h)  # 计算缩放比例
dim = (int(w * r), height)  # 新尺寸:(宽度按比例缩放, 目标高度)
  • 如果提供 width(不管 height 是否提供),则按宽度缩放,高度按比例计算:
python 复制代码
r = width / float(w)  # 计算缩放比例
dim = (width, int(h * r))  # 新尺寸:(目标宽度, 高度按比例缩放)

3.执行缩放:

python 复制代码
resized = cv2.resize(image, dim, interpolation=inter)  # 使用 OpenCV 进行缩放

4.返回缩放后的图像:

python 复制代码
return resized

总结

  • 用途:保持宽高比的情况下缩放图像,避免直接 cv2.resize 可能导致的变形。

适用场景:

  • 需要固定宽度或高度,但保持比例不变。
  • 适用于图像预处理(如深度学习输入尺寸调整)。

注意:

  • 如果同时传入 width 和 height,此函数仍然只会按其中一个参数缩放(优先 width)。
  • 如果要强制指定宽高(可能变形),直接用 cv2.resize(image, (width, height))

3.1.3 order_points() 函数

python 复制代码
def order_points(pts):
    # 一共4个坐标点
    rect = np.zeros((4,2),dtype="float32") 
    # 用来存储排序之后的坐标位置
    # 按顺序找到对应坐标0123分别是 左上、右上、右下、左下
    s = pts.sum(axis=1) #对pts矩阵的每一行进行求和操作,(x+y)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff = np.diff(pts,axis=1) #对pts矩阵的每一行进行求差操作,(y-x)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

这段代码定义了一个名为 order_points 的函数,用于对给定的 4个二维坐标点 进行排序,使其按照 左上、右上、右下、左下 的顺序排列。


逐步解析

输入

pts:一个形状为 (4, 2) 的 NumPy 数组,表示 4 个点的 (x, y) 坐标。例如:

python 复制代码
pts = np.array([[x1, y1], [x2, y2], [x3, y3], [x4, y4]])

输出

rect:排序后的 (4, 2) 数组,顺序为 左上、右上、右下、左下。


排序逻辑

1.初始化存储数组

python 复制代码
rect = np.zeros((4, 2), dtype="float32")  # 存储排序后的坐标

2.计算 x + y 并找到左上和右下点

  • 左上点(rect[0]):x + y 最小的点(因为左上角的 x 和 y 都较小)。
  • 右下点(rect[2]):x + y 最大的点(因为右下角的 x 和 y 都较大)。
python 复制代码
s = pts.sum(axis=1)  # 计算每个点的 x + y
rect[0] = pts[np.argmin(s)]  # 左上点
rect[2] = pts[np.argmax(s)]  # 右下点

3.计算 y - x 并找到右上和左下点

  • 右上点(rect[1]):y - x 最小的点(因为右上角的 y 较小,x 较大)。
  • 左下点(rect[3]):y - x 最大的点(因为左下角的 y 较大,x 较小)。
python 复制代码
diff = np.diff(pts, axis=1)  # 计算 y - x
rect[1] = pts[np.argmin(diff)]  # 右上点
rect[3] = pts[np.argmax(diff)]  # 左下点

4.返回排序后的坐标

python 复制代码
return rect

注意事项

  • 输入必须是 4 个点,否则会报错。
  • 适用于凸四边形,如果点排列异常(如交叉),可能排序错误。
  • 如果 4 个点本身是 旋转的矩形(如 45° 倾斜),该方法仍然有效。

3.1.4 four_point_transform() 函数

python 复制代码
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

这段代码定义了一个名为 four_point_transform 的函数,用于对图像进行 透视变换(Perspective Transformation),将图像中的任意四边形区域 矫正为一个 矩形。


代码解析

输入参数

  • image:输入的图像(OpenCV 格式,即 NumPy 数组)。
  • pts:一个形状为 (4, 2) 的 NumPy 数组,表示待矫正的 4 个角点坐标(顺序任意)。

输出

  • warped:矫正后的图像(矩形视角)。

执行步骤

1.对 4 个点进行排序(使用 order_points 确保顺序为 左上、右上、右下、左下):

python 复制代码
rect = order_points(pts)  # 排序后的 4 个点:[tl, tr, br, bl]
(tl, tr, br, bl) = rect   # tl: 左上, tr: 右上, br: 右下, bl: 左下

2.计算矫正后的目标宽度 maxWidth:

计算底边宽度(br 到 bl 的距离):

python 复制代码
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))

计算顶边宽度(tr 到 tl 的距离):

python 复制代码
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))

取最大值作为最终宽度:

python 复制代码
maxWidth = max(int(widthA), int(widthB))

3.计算矫正后的目标高度 maxHeight:

计算右侧高度(tr 到 br 的距离):

python 复制代码
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))

计算左侧高度(tl 到 bl 的距离):

python 复制代码
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))

取最大值作为最终高度:

python 复制代码
maxHeight = max(int(heightA), int(heightB))

4.定义目标矩形坐标 dst:

python 复制代码
dst = np.array([
    [0, 0],                     # 左上
    [maxWidth - 1, 0],          # 右上
    [maxWidth - 1, maxHeight - 1],  # 右下
    [0, maxHeight - 1]          # 左下
], dtype="float32")

5.计算透视变换矩阵 M:

python 复制代码
M = cv2.getPerspectiveTransform(rect, dst)
  • rect:原始 4 个点(四边形)。
  • dst:目标 4 个点(矩形)。

6.执行透视变换:

python 复制代码
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
  • image:输入图像。
  • M:透视变换矩阵。
  • (maxWidth, maxHeight):输出图像的尺寸。

7.返回矫正后的图像:

python 复制代码
return warped

总结

four_point_transform 的作用是:

  • 1.对 4 个点进行排序(order_points)。
  • 2.计算目标矩形的尺寸(maxWidth 和 maxHeight)
  • 3.计算透视变换矩阵(cv2.getPerspectiveTransform)
  • 4.执行透视变换(cv2.warpPerspective),将任意四边形矫正为矩形

这样,我们就可以将 倾斜拍摄的物体 转换为 正面视角,便于后续处理。


3.2 读取图片并做预处理

python 复制代码
#读取输入
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)

读取一张发票图片,并进行缩小处理。

图片如下:


3.3 轮廓检测

python 复制代码
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)[-2]
image_contours = cv2.drawContours(image.copy(),cnts,-1,(0,0,255),1)
cv_show('image_contours',image_contours)

这段代码的作用是 对输入图像进行边缘检测并绘制轮廓,以下是逐步解析:

1. 转换为灰度图

python 复制代码
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  • 功能:将彩色图像(BGR格式)转换为灰度图(单通道)。
  • 为什么需要:简化后续处理,减少计算量。

2.二值化处理(阈值分割

python 复制代码
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

功能:使用 Otsu算法 自动计算最佳阈值,将灰度图转换为黑白二值图。

  • cv2.THRESH_BINARY:二值化(大于阈值的设为255,否则为0)。
  • cv2.THRESH_OTSU:自动确定最佳阈值(适合双峰直方图的图像)。

输出:edged 是一个二值图像,白色(255)代表目标,黑色(0)代表背景。

3. 查找轮廓

python 复制代码
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]

功能:在二值图像中查找所有轮廓。

  • edged.copy():确保不修改原始二值图像。
  • cv2.RETR_LIST:检测所有轮廓,不建立层级关系。
  • cv2.CHAIN_APPROX_SIMPLE:压缩轮廓点(例如直线只保留端点)。

返回值:cnts 是一个列表,每个元素是一个轮廓(点的集合)。

4. 绘制轮廓

python 复制代码
image_contours = cv2.drawContours(image.copy(), cnts, -1, (0, 0, 255), 1)

功能:在原图的副本上绘制所有轮廓。

  • image.copy():避免修改原图。
  • -1:绘制所有轮廓(若为 0 则只绘制第一个轮廓)。
  • (0, 0, 255):轮廓颜色(红色,BGR格式)。
  • 1:轮廓线宽(像素)。

5. 显示结果

python 复制代码
cv_show('image_contours', image_contours)

功能 :显示带轮廓的图像(假设 cv_show 是自定义的显示函数,等同于 cv2.imshow + cv2.waitKey)。


3.4 获取最大轮廓

python 复制代码
screenCnt = sorted(cnts,key=cv2.contourArea,reverse=True)[0]

peri = cv2.arcLength(screenCnt,True) #计算轮廓周长
screenCnt = cv2.approxPolyDP(screenCnt,0.05 * peri,True) #轮廓近似
print(screenCnt.shape)
image_contours = cv2.drawContours(image.copy(),[screenCnt],-1,(0,255,0),2)

cv2.imshow("image_contours",image_contours)
cv2.waitKey(0)

这段代码的作用是从所有检测到的轮廓中筛选出面积最大的轮廓,并进行多边形近似,最终绘制出这个近似后的轮廓。以下是逐步解析:


1. 筛选面积最大的轮廓

python 复制代码
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]

功能:对轮廓列表 cnts 按面积从大到小排序,并选择面积最大的一个。

  • cv2.contourArea:计算轮廓的面积。
  • reverse=True:降序排列。
  • [0]:取第一个(即最大轮廓)。

用途:假设图像中只有一个主要目标,直接取最大轮廓可排除噪声。

2. 计算轮廓周长

python 复制代码
peri = cv2.arcLength(screenCnt, True)

功能:计算轮廓的周长。

  • screenCnt:输入的轮廓点集。
  • True:表示轮廓是闭合的(首尾相连)。

3.多边形近似(轮廓简化)

python 复制代码
screenCnt = cv2.approxPolyDP(screenCnt, 0.05 * peri, True)

功能:用更少的点近似轮廓,减少冗余点(如将弯曲边缘近似为直线)。

  • screenCnt:原始轮廓点集。
  • 0.05 * peri:近似精度(周长的5%作为阈值,值越小越接近原始形状)。
  • True:轮廓闭合。

输出:近似后的轮廓点集(如果是矩形,会返回4个角点)。

示例:

  • 输入:复杂轮廓(如几十个点组成的弯曲边缘)。
  • 输出:简化后的多边形(如4个点组成的矩形)。

4.检查近似结果

python 复制代码
print(screenCnt.shape)

用途:打印近似后轮廓的形状(如 (4, 1, 2) 表示4个点,每个点是 (x, y) 坐标)。

典型输出

  • 矩形:(4, 1, 2)
  • 三角形:(3, 1, 2)
  • 若点数过多(如 (10, 1, 2)),可能需要调整 0.05 参数。

5.绘制近似后的轮廓

python 复制代码
image_contours = cv2.drawContours(image.copy(), [screenCnt], -1, (0, 255, 0), 2)

功能:在原图的副本上绘制绿色(BGR格式 (0,255,0))的近似轮廓。

  • [screenCnt]:将轮廓包装为列表(因 drawContours 接受列表输入)。
  • -1:绘制所有轮廓(此处只有一个)。
  • 2:线宽(像素)。

6.显示结果

python 复制代码
cv2.imshow("image_contours", image_contours)
cv2.waitKey(0)

效果:显示带绿色轮廓的图像,标识出检测到的主要目标。


完整流程总结

  1. 输入:所有检测到的轮廓 cnts(来自 cv2.findContours)。
  2. 筛选:选择面积最大的轮廓。
  3. 简化:用多边形近似轮廓(如提取矩形角点)。
  4. 绘制:在原图上标出简化后的轮廓。
  5. 输出:可视化结果。

3.5 透视变换矫正

python 复制代码
warped = four_point_transform(orig,screenCnt.reshape(4,2) * ratio)

功能:将原始图像 orig 中的四边形区域(screenCnt)矫正为正面视角的矩形。

  • screenCnt.reshape(4, 2):将轮廓点从 (4, 1, 2) 转换为 (4, 2) 格式(4个点的x,y坐标)。
  • ratio:如果预处理时图像被缩小过(如为了加速轮廓检测),需通过比例 ratio 将坐标映射回原始图像尺寸。
  • four_point_transform :自定义函数(基于 cv2.getPerspectiveTransform 和
    cv2.warpPerspective)。

3.6 保存矫正后的图片并显示窗口属性

python 复制代码
cv2.imwrite('invoice_new.jpg', warped)
cv2.namedWindow('xx',cv2.WINDOW_NORMAL)
cv2.resizeWindow('xx',800,600)

cv_show('xx',warped)
cv2.waitKey(0)
cv2.destroyAllWindows()

代码解析:

python 复制代码
cv2.imwrite('invoice_new.jpg', warped)

功能 :将矫正后的图像 warped 保存为 invoice_new.jpg。
用途:持久化处理结果,便于后续使用(如OCR识别、打印等)。


python 复制代码
cv2.namedWindow('xx', cv2.WINDOW_NORMAL)
cv2.resizeWindow('xx', 800, 600)

功能:创建一个可调整大小的窗口,并设置初始尺寸为 800x600 像素。

  • cv2.WINDOW_NORMAL :允许手动调整窗口大小(默认是固定大小的 cv2.WINDOW_AUTOSIZE)。

适用场景:当图像较大时,可通过缩放窗口查看完整内容。


python 复制代码
cv2.imshow('xx', warped)
cv2.waitKey(0)

功能 :在窗口 'xx' 中显示矫正后的图像 warped,并等待用户按键关闭窗口。
注意 :cv_show 可能是封装好的函数,若未定义需替换为 cv2.imshow + cv2.waitKey。

4. 结语

本篇博客到这里就结束啦,希望能帮助大家更好的理解和和使用OpenCV中的透视变换方法。其实方法并不难,只要我们能沉下心来慢慢学习,一定会有收获的。我一直坚信,努力会让我们收获一个更好的自己,加油!!!

相关推荐
心想事“程”3 分钟前
神经网络的 “成长密码”:正向传播与反向传播深度解析(四)
人工智能·深度学习·机器学习
whaosoft-14318 分钟前
w~视觉~3D~合集2
人工智能
云山工作室42 分钟前
基于单片机的温湿度采集系统(论文+源码)
人工智能·单片机·嵌入式硬件·毕业设计·毕设
www_pp_43 分钟前
# 基于PyTorch的食品图像分类系统:从训练到部署全流程指南
人工智能·pytorch·分类
MorleyOlsen1 小时前
【数字图像处理】立体视觉基础(1)
图像处理·人工智能·计算机视觉
watersink1 小时前
大语言模型的训练、微调及压缩技术
人工智能·语言模型·自然语言处理
爱的叹息1 小时前
AI推荐系统的详细解析 +推荐系统中滤泡效应(Filter Bubble)的详细解析+ 基于Java构建电商推荐系统的分步实现方案,结合机器学习与工程实践
java·人工智能·机器学习
IT古董1 小时前
【漫话机器学习系列】211.驻点(Stationary Points)
人工智能·机器学习
结冰架构1 小时前
【AI提示词】投资策略专家
大数据·人工智能·ai·提示词·专家
山海青风1 小时前
智能体(Intelligent Agents)入门自学教程 3 简单反射型智能体(Reactive Agents)
人工智能·python