OpenCV进阶操作:图像的透视变换


文章目录

  • 前言
  • 一、什么是透视变换?
  • 二、透视变换的过程
  • 三、OpenCV透视变换核心函数
  • 四、文档扫描校正(代码)
    • 1、预处理
    • 2、定义轮廓点的排序函数
    • 3、定义透视变换函数
    • 4、读取原图并缩放
    • 5、轮廓检测
    • 6、绘制最大轮廓
    • 7、对最大轮廓进行透视变换
    • 8、旋转、二值化处理
  • 总结


前言

在图像处理中,透视变换(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 imageif 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)  # 根据原始点和变换后的点计算透视变换矩阵Mwarped = 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的透视变换,我们能够轻松解决因拍摄角度导致的图像形变问题。无论是手动标定点还是结合自动检测算法,这一技术都为复杂场景下的图像处理提供了基础支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/79506.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【python】基础知识点100问

以下是Python基础语法知识的30条要点整理,涵盖数据类型、函数、控制结构等核心内容,结合最新资料归纳总结: 基础30问 一、函数特性 函数多返回值 支持用逗号分隔返回多个值,自动打包为元组,接收时可解包到多个变量 def func(): return 1, "a" x, y = func()匿…

采用AI神经网络降噪算法的通信语音降噪(ENC)模组性能测试和应用

采用AI降噪的语言通话环境抑制模组性能效果测试 随着AI时代来临.通话设备的环境噪音抑制也进入AI降噪算法时代. AI神经网络降噪技术是一款革命性的语音处理技术,他突破了传统单麦克风和双麦克风降噪的局限性,利用采集的各种日常环境中的噪音样本进行训练学习.让降噪…

openwrt目录结构(部分)

1,openwrt 原始目录需要注意的目录 tools: 该目录下存放着一些,编译工程的自动化工具包和一些在编译过程用到的命令包, 查看目录下的Makefile,知道其会在编译过程中将依赖包下载 例如: autoconf / lzma / mkimage/ …

RDB和AOF的区别

Redis提供两种主要的持久化机制:RDB(Redis Database)和AOF(Append Only File),它们在数据持久化方式、性能影响及恢复策略上各有特点。以下是两者的对比分析及使用建议: RDB(快照持久…

基于大模型的甲状腺结节诊疗全流程预测与方案研究报告

目录 一、引言 1.1 研究背景与目的 1.2 研究意义 1.3 国内外研究现状 二、大模型预测原理与方法 2.1 相关大模型概述 2.2 数据收集与预处理 2.3 模型训练与验证 三、术前预测与评估 3.1 结节性质预测 3.1.1 良恶性判断 3.1.2 与传统诊断方法对比 3.2 手术风险预测…

逆向破解:x64dbg

文章目录 一、CPU窗口1、反汇编窗口2、寄存器窗口3、栈地址窗口4、十六进制数据窗口5、堆栈参数解析窗口 二、常用快捷键三、字符串检索功能四、调试功能1、上一步 一、CPU窗口 1、反汇编窗口 2、寄存器窗口 寄存器窗口用于显示和解释当前线程环境下CPU寄存器的各种状态值和内…

免布线视频桩如何重塑停车管理模式

传统停车管理常因布线复杂、维护成本高而难以推广,而“免布线视频桩”通过无线设计、低功耗与高精度检测,为城市停车提供高效解决方案。作为智慧城市建设的创新工具,免布线视频桩以即装即用、长效续航等特性,正在重塑停车管理模式…

【CTFer成长之路】举足轻重的信息搜集

举足轻重的信息搜集 信息搜集 常见的搜集 题目描述: 一共3部分flag docker-compose.yml version: 3.2services:web:image: registry.cn-hangzhou.aliyuncs.com/n1book/web-information-backk:latestports:- 80:80启动方式 docker-compose up -d 题目Flag n1book{info_…

springboot3+vue3融合项目实战-大事件文章管理系统-更新用户密码

大致分为这三步 首先在usercontroller中增加updatePwd方法 PatchMapping ("/updatePwd")public Result updatePwd(RequestBody Map<String,String> params){//1.校验参数String oldPwd params.get("old_pwd");String newPwd params.get("n…

OpenCV进阶操作:指纹验证、识别

文章目录 前言一、指纹验证1、什么是指纹验证2、流程步骤 二、使用步骤&#xff08;案例&#xff09;三、指纹识别&#xff08;案例&#xff09;1、这是我们要识别的指纹库2、这是待识别的指纹图3、代码4、结果 总结 前言 指纹识别作为生物识别领域的核心技术之一&#xff0c;…

ECLIC中断流程及实际应用 —— RISC-V中断机制(二)

在长期的嵌入式开发实践中&#xff0c;对中断机制的理解始终停留在表面层次&#xff0c;特别当开发者长期局限于纯软件抽象层面时&#xff0c;对中断机制的理解极易陷入"知其然而不知其所以然"的困境&#xff0c;这种认知的局限更为明显&#xff1b;随着工作需要不断…

计算机网络-LDP标签发布与管理

前面学习了LDP建立邻居&#xff0c;建立会话&#xff0c;今天来学习在MPLS中的标签发布与管理。 在MPLS网络中&#xff0c;下游LSR决定标签和FEC的绑定关系&#xff0c;并将这种绑定关系发布给上游LSR。LDP通过发送标签请求和标签映射消息&#xff0c;在LDP对等体之间通告FEC和…

Go语言运算符详解

文章目录 1. 算术运算符2. 关系运算符3. 逻辑运算符4. 位运算符5. 赋值运算符6. 其他运算符运算符优先级注意事项 Go语言提供了与其他语言类似的运算符&#xff0c;包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符等。这些运算符即可满足基本的运算需求。 1. 算…

Selenium模拟人类行为,操作网页的方法(全)

看到有朋友评论问&#xff0c;用selenium怎么模仿人类行为&#xff0c;去操作网页的页面呢&#xff1f; 我想了想&#xff0c;这确实是一个很大的点&#xff0c;不应该是一段代码能解决的&#xff0c; 就像是,如果让程序模拟人类的行为。例如模拟人类买菜&#xff0c;做饭&am…

RabbitMQ的工作队列模式和路由模式有什么区别?

RabbitMQ 的工作队列模式&#xff08;Work Queues&#xff09;和路由模式&#xff08;Routing&#xff09;是两种不同的消息传递模式&#xff0c;主要区别在于消息的分发逻辑和使用场景。以下是它们的核心差异&#xff1a; 1. 工作队列模式&#xff08;Work Queues&#xff09…

牛客练习赛138(首篇万字题解???)

赛时成绩如下&#xff1a; 1. 小s的签到题 小s拿到了一个比赛榜单&#xff0c;他要用最快的速度找到签到题&#xff0c;但是小s脑子还是有点晕&#xff0c;请你帮帮小s&#xff0c;助力他找到签到题。 比赛榜单是一个 2 行 n 列的表格&#xff1a; 第一行是 n 个大写字母&#…

linux0.11内核源码修仙传第十六章——获取硬盘信息及根目录挂载

&#x1f680; 前言 书接第十四章&#xff1a;linux0.11内核源码修仙传第十四章——进程调度之fork函数&#xff0c;在这一节博客中已经通过fork进程创建了一个新的进程1&#xff0c;并且可以被调度&#xff0c;接下来接着主线继续走下去。希望各位给个三连&#xff0c;拜托啦&…

mobile自动化测试-appium webdriverio

WebdriverIO是一款支持mobile app和mobile web自动化测试框架&#xff0c;与appium集成&#xff0c;完成对mobile应用测试。支持ios 和android两种平台&#xff0c;且功能丰富&#xff0c;是mobile app自动化测试首选框架。且官方还提供了mobile 应用测试example代码&#xff0…

Kubernetes排错(十):常见网络故障排查

通用排查思路 Kubernetes 集群内不同服务之间的网络通信出现异常&#xff0c;表现为请求超时、连接失败或响应缓慢&#xff0c;导致服务间依赖关系中断&#xff0c;依赖服务的功能不可用或性能下降&#xff0c;甚至可能波及整个微服务架构&#xff0c;引发连锁反应&#xff0c…

PyTorch 张量与自动微分操作

笔记 1 张量索引操作 import torch ​ # 下标从左到右从0开始(0->第一个值), 从右到左从-1开始 # data[行下标, 列下标] # data[0轴下标, 1轴下标, 2轴下标] ​ def dm01():# 创建张量torch.manual_seed(0)data torch.randint(low0, high10, size(4, 5))print(data->,…