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中的透视变换方法。其实方法并不难,只要我们能沉下心来慢慢学习,一定会有收获的。我一直坚信,努力会让我们收获一个更好的自己,加油!!!

相关推荐
文心快码BaiduComate3 小时前
百度云与光本位签署战略合作:用AI Agent 重构芯片研发流程
前端·人工智能·架构
风象南4 小时前
Claude Code这个隐藏技能,让我告别PPT焦虑
人工智能·后端
Mintopia5 小时前
OpenClaw 对软件行业产生的影响
人工智能
陈广亮5 小时前
构建具有长期记忆的 AI Agent:从设计模式到生产实践
人工智能
会写代码的柯基犬5 小时前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm
Mintopia6 小时前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区6 小时前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两9 小时前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
前端付豪9 小时前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain