OpenCv高阶(4.0)——案例:海报的透视变换

news2025/5/16 19:00:38

文章目录

  • 前言
  • 一、工具函数模块
    • 1.1 图像显示函数
    • 1.2 保持宽高比的缩放函数
    • 1.3 坐标点排序函数
  • 二、透视变换核心模块
    • 2.1 四点透视变换实现
  • 三、主流程技术分解
    • 3.1 图像预处理
    • 3.2 轮廓检测流程
    • 3.3 最大轮廓处理
  • 四、后处理技术
    • 4.1 透视变换
    • 4.2 形态学处理
  • 五、完整代码
  • 总结


前言

构建智能文档视觉矫正体系:通过 AI 图像识别技术自动分析文档倾斜特征,结合自适应透视校正模型动态调整校正参数,不仅消除物理歪斜(如扫描歪斜、拍摄俯仰角偏差),还能修复因纸张褶皱、扫描设备误差导致的 “隐性扭曲”,最终呈现视觉上完全平整、几何上严格对齐的标准文档版面,为专业打印奠定基础。

一、工具函数模块

1.1 图像显示函数

def cv_show(name, value):
    cv2.imshow(name, value)    # 创建命名窗口显示图像
    cv2.waitKey(0)             # 等待任意键输入(单位:毫秒,0表示无限等待)
    cv2.destroyAllWindows()    # 关闭所有OpenCV窗口(实际代码中未显式调用,需注意内存释放)

cv2.waitKey(0) 的返回值为按键ASCII码,可用于交互控制

调试建议:添加 cv2.destroyWindow(name) 关闭指定窗口,避免内存泄漏

1.2 保持宽高比的缩放函数

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

插值算法选择:

cv2.INTER_AREA:基于像素区域关系重采样,适合缩小图像

cv2.INTER_CUBIC:4x4像素邻域的双三次插值,适合放大图像

cv2.INTER_LINEAR:双线性插值(默认)

1.3 坐标点排序函数

def order_points(pts):
    rect = np.zeros((4, 2), dtype="float32")  # 初始化4x2矩阵
    #按顺序找到对应的坐标0123,分别是左上右上右下、左下
    # 计算坐标点x+y的和
    s = pts.sum(axis=1)  # 形状:(4,) ,对矩阵的每一行进行求和操作
    rect[0] = pts[np.argmin(s)]  # 左上角:x+y最小
    rect[2] = pts[np.argmax(s)]  # 右下角:x+y最大
    
    # 计算坐标点x-y的差
    diff = np.diff(pts, axis=1)  # 形状:(4,1)
    rect[1] = pts[np.argmin(diff)]  # 右上角:x-y最小(即y相对较大)
    rect[3] = pts[np.argmax(diff)]  # 左下角:x-y最大(即y相对较小)
    
    return rect  # 返回有序坐标:[左上, 右上, 右下, 左下]

二、透视变换核心模块

2.1 四点透视变换实现

def four_point_transform(image, pts):
    # 坐标排序(关键步骤!)
    rect = order_points(pts)
    (tl, tr, br, bl) = rect  # 解构赋值四个顶点
    
    # 计算输出图像的宽度(取两组对边最大值)
    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],          # 宽度方向预留1像素边界
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype="float32")
    
    # 计算透视变换矩阵(核心数学操作)
    M = cv2.getPerspectiveTransform(rect, dst)
    
    # 执行透视变换
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
    return warped

1.尺寸计算

公式推导:基于欧氏距离公式计算对边长度
distance = sqrt((x2-x1)^2 + (y2-y1)^2)

取最大值的原因:确保输出图像能完整包含原始四边形区域

2.cv2.getPerspectiveTransform

输入:源四边形(rect)和目标四边形(dst)的4个点

输出:3x3透视变换矩阵

数学原理:求解投影变换方程
透视变换矩阵公式
通过4对点建立8个方程,求解8个未知参数

3.cv2.warpPerspective

参数 M:3x3变换矩阵

参数 (maxWidth, maxHeight):输出图像尺寸

插值方式:默认双线性插值(可指定cv2.INTER_LINEAR等)

三、主流程技术分解

3.1 图像预处理

# 读取原始图像
img = cv2.imread('../data/hb.png')  # 默认BGR格式

# 计算缩放比例(基于原始高度)
ratio = img.shape[0] / 500.0  # 假设将高度缩放到500px
orig = img.copy()             # 深拷贝保留原始图像
img = resize(orig, height=500)  # 执行缩放

缩放目的:加速后续轮廓处理(复杂度与图像尺寸成平方关系)

ratio的作用:后续将检测到的坐标还原到原始尺寸

3.2 轮廓检测流程

# 灰度化处理
gray = cv2.cvtColor(img, 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]
img_contours=cv2.drawContours(img.copy(),cnts,-1,(0,0,255),1)
cv_show('img_contours',img_contours)

cv2.findContours参数解析:

edged.copy():避免修改原始边缘图像

cv2.RETR_LIST:检索所有轮廓,不建立层级关系

cv2.CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角方向的冗余点

[-2]:兼容不同OpenCV版本(返回格式可能为 [contours, hierarchy] 或 [image, contours, hierarchy])

3.3 最大轮廓处理

# 按轮廓面积排序(降序)
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
#获取面积最大的轮廓
# 计算轮廓周长
peri = cv2.arcLength(screenCnt, True)  # True表示轮廓闭合

# 多边形近似(Douglas-Peucker算法)
screenCnt = cv2.approxPolyDP(screenCnt, 0.01 * peri, True)
print(screenCnt.shape)
img_contours=cv2.drawContours(img.copy(),[screenCnt],-1,(0,255,0),2)

cv_show('img_contours1',img_contours)

1、cv2.contourArea

计算方式:格林公式积分

对非闭合轮廓可能返回错误值

2、cv2.approxPolyDP

参数 0.01*peri:近似精度阈值(周长比例)

原理:迭代拟合多边形,删除偏离当前线段超过阈值的点

效果:将曲线近似为折线,减少顶点数量

四、后处理技术

4.1 透视变换

# 执行变换(注意坐标还原)
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)

# 保存结果
cv2.imwrite('../data/invoice_new.jpg', warped)

screenCnt.reshape(4,2):将轮廓点从 (N,1,2) 转换为 (4,2)
ratio:将缩放后的坐标还原到原始图像尺寸

4.2 形态学处理

# 转换为灰度图
warped_gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)

# 二值化处理
_, warped_binary = cv2.threshold(warped_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

# 逆时针旋转90度,在对自己的文档进行识别时,可以根据需求调整转动角度
warped_rotated = cv2.rotate(warped_binary, cv2.ROTATE_90_COUNTERCLOCKWISE)

# 定义形态学核
kernel = np.ones((11,11),np.uint8)
#做一个闭运算,先膨胀再腐蚀
warped_rotated_closed=cv2.morphologyEx(warped_rotated,cv2.MORPH_CLOSE,kernel)
cv2.namedWindow('warped_rotated_closed',cv2.WINDOW_NORMAL)
cv_show("warped_rotated_closed",warped_rotated)

腐蚀(Erode):用核的最小值替换锚点像素,消除小物体

膨胀(Dilate):用核的最大值替换锚点像素,填充空洞

开运算 = 先腐蚀后膨胀,用于去噪

核大小选择:3x3平衡去噪效果与细节保留
目的是为了使海报上的图案和文字显示的更清晰,字迹不会很模糊。

最终效果展示:
在这里插入图片描述
将歪歪扭扭的图片扶正,并将图片上的文字变得更清晰,上边的效果仍没有达到预计的效果,可以通过图像形态学的处理来达到更好的效果。

五、完整代码

import numpy as np
import cv2
def cv_show(name,value):
    cv2.imshow(name,value)
    cv2.waitKey(0)


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


def order_points(pts):
    #一共四个坐标点
    rect=np.zeros((4,2),dtype='float32')
    #按顺序找到对应的坐标0123,分别是左上右上右下、左下
    s=pts.sum(axis=1)   #对矩阵的每一行进行求和操作
    rect[0]=pts[np.argmin(s)]
    rect[2]=pts[np.argmax(s)]
    diff=np.diff(pts,axis=1)
    rect[1]=pts[np.argmin(diff)]
    rect[3]=pts[np.argmax(diff)]
    return rect


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,0],[maxwidth,maxheight],[0,maxheight]],dtype='float32')

    M=cv2.getPerspectiveTransform(rect,dst)
    warped=cv2.warpPerspective(image,M,(maxwidth,maxheight))
    return warped

img=cv2.imread('../data/hb.png')
cv_show('fp',img)

# 图片多大调整大小
ratio=img.shape[0]/500.0    #计算缩小比例
orig=img.copy()
img=resize(orig,height=500)
# cv_show('1',img)

#轮廓检测
print("STEP 1:轮廓检测")
gray=cv2.cvtColor(img,cv2.COLOR_BGRA2GRAY)    #获取灰度图

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]
img_contours=cv2.drawContours(img.copy(),cnts,-1,(0,0,255),1)
cv_show('img_contours',img_contours)


print("STEP 2:获取最大轮廓")
screenCnt=sorted(cnts,key=cv2.contourArea,reverse=True)[0]   #获取面积最大的轮廓

peri=cv2.arcLength(screenCnt,True)

screenCnt=cv2.approxPolyDP(screenCnt,0.01*peri,True)
print(screenCnt.shape)
img_contours=cv2.drawContours(img.copy(),[screenCnt],-1,(0,255,0),2)

cv_show('img_contours1',img_contours)


# 透视变换
warped=four_point_transform(orig,screenCnt.reshape(4,2)*ratio)
cv2.imwrite('../data/invoice_new.jpg',warped)
cv2.namedWindow('xx',cv2.WINDOW_NORMAL)     #设置这个参数可以使imshow()显示出来的窗口被拉大缩小
cv2.imshow('xx',warped)
cv2.waitKey(0)

warped_gray=cv2.cvtColor(warped,cv2.COLOR_BGR2GRAY)
warped_binary=cv2.threshold(warped_gray.copy(),0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]


cv2.namedWindow('warped_binary',cv2.WINDOW_NORMAL)
cv_show("warped_binary",warped_binary)

warped_rotated=cv2.rotate(warped_binary,cv2.ROTATE_90_COUNTERCLOCKWISE)
cv2.namedWindow('warped_rotated',cv2.WINDOW_NORMAL)
cv_show("warped_rotated",warped_rotated)

#做一个闭运算,先膨胀再腐蚀
kernel = np.ones((3,3),np.uint8)
warped_rotated_closed=cv2.morphologyEx(warped_rotated,cv2.MORPH_OPEN,kernel)
cv2.namedWindow('warped_rotated_closed',cv2.WINDOW_NORMAL)
cv_show("warped_rotated_closed",warped_rotated)


总结

通过对计算机视觉技术的综合使用,可以实现很多种对文档的处理任务,还可以将将功能与摄像头结合实现实时处理任务。

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

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

相关文章

光谱相机的图像预处理技术

光谱相机的图像预处理技术旨在消除噪声、增强有效信息,为后续分析提供高质量数据。 一、预处理流程与技术要点 ‌辐射校正‌ ‌辐射定标‌:将图像灰度值转换为绝对辐射亮度,常用反射率法、辐亮度法和辐照度法消除传感器响应差异&#xff0…

k8s监控方案实践补充(一):部署Metrics Server实现kubectl top和HPA支持

k8s监控方案实践补充(一):部署Metrics Server实现kubectl top和HPA支持 文章目录 k8s监控方案实践补充(一):部署Metrics Server实现kubectl top和HPA支持一、Metrics Server简介二、Metrics Server实战部署…

嵌入式调试新宠!J-Scope:免费+实时数据可视化,让MCU调试效率飙升!

📌 痛点直击:调试还在用“断点打印”? 嵌入式开发中,你是否也经历过这些崩溃瞬间? 想实时观察变量变化,代码里插满printf,结果拖垮系统性能? 断点调试打断程序运行,时序…

微信小程序学习之搜索框

1、第一步&#xff0c;我们在index.json中引入vant中的搜索框控件&#xff1a; {"usingComponents": {"van-search": "vant/weapp/search/index"} } 2、第二步&#xff0c;直接在index.wxml中添加布局&#xff1a; <view class"index…

Altium Designer AD如何输出PIN带网络名的PDF装配图

Altium Designer AD如何输出PIN带网络名的PDF装配图 文描述在Altium Designer版本中设置焊盘网络名时遇到的问题&#xff0c;网络名大小不一致&#xff0c;部分PAD的网络名称未显示&#xff0c;可能涉及字符大小设置和版本差异。 参考 1.AD导出PCB装配图 https://blog.csd…

VMware虚拟机 安装 CentOS 7

原文链接: VMware虚拟机 安装 CentOS 7 安装准备 软件: VMware Workstation Pro 17.6.3 镜像: CentOS-7.0-1406-x86_64-DVD.iso 我打包好放这了&#xff0c;VMware 和 CentOS7 &#xff0c;下载即可。 关于VMware Workstation Pro 17.6.3&#xff0c;傻瓜式安装即可。 CentO…

Python训练打卡Day22

复习日&#xff1a; 1.标准化数据&#xff08;聚类前通常需要标准化&#xff09; scaler StandardScaler() X_scaled scaler.fit_transform(X) StandardScaler() &#xff1a;这部分代码调用了 StandardScaler 类的构造函数。在Python中&#xff0c;当你在类名后面加上括号…

Cold Diffusion: Inverting Arbitrary Image Transforms Without Noise论文阅读

冷扩散&#xff1a;无需噪声的任意图像变换反转 摘要 标准扩散模型通常涉及两个核心步骤&#xff1a;图像降质 &#xff08;添加高斯噪声&#xff09;和图像恢复 &#xff08;去噪操作&#xff09;。本文发现&#xff0c;扩散模型的生成能力并不强烈依赖于噪声的选择&#xf…

嵌软面试每日一阅----通信协议篇(二)之TCP

一. TCP和UDP的区别 可靠性 TCP&#xff1a;✅ 可靠传输&#xff08;三次握手 重传机制&#xff09; UDP&#xff1a;❌ 不可靠&#xff08;可能丢包&#xff09; 连接方式 TCP&#xff1a;面向连接&#xff08;需建立/断开连接&#xff09; UDP&#xff1a;无连接&#xff0…

机器学习 --- 模型选择与调优

机器学习 — 模型选择与调优 文章目录 机器学习 --- 模型选择与调优一&#xff0c;交叉验证1.1 保留交叉验证HoldOut1.2 K-折交叉验证(K-fold)1.3 分层k-折交叉验证Stratified k-fold 二&#xff0c;超参数搜索三&#xff0c;鸢尾花数据集示例四&#xff0c;现实世界数据集示例…

AGI大模型(15):向量检索之调用ollama向量数据库

这里介绍将向量模型下载到本地,这里使用ollama,现在本地安装ollama,这里就不过多结束了。直接从下载开始。 1 下载模型 首先搜索模型,这里使用bge-large模型,你可以根据自己的需要修改。 点击进入,复制命令到命令行工具中执行。 安装后查看: 2 代码实现 先下载ollama…

什么是Agentic AI(代理型人工智能)?

什么是Agentic AI&#xff08;代理型人工智能&#xff09;&#xff1f; 一、概述 Agentic AI&#xff08;代理型人工智能&#xff09;是一类具备自主决策、目标导向性与持续行动能力的人工智能系统。与传统AI系统依赖外部输入和显式命令不同&#xff0c;Agentic AI在设定目标…

day 17 无监督学习之聚类算法

一、聚类流程 1. 利用聚类发现数据模式 无监督算法中的聚类&#xff0c;目的就是将数据点划分成不同的组或 “簇”&#xff0c;使得同一簇内的数据点相似度较高&#xff0c;而不同簇的数据点相似度较低&#xff0c;从而发现数据中隐藏的模式。 2. 对聚类后的类别特征进行可视…

时源芯微| KY键盘接口静电浪涌防护方案

KY键盘接口静电浪涌防护方案通过集成ESD保护元件、电阻和连接键&#xff0c;形成了一道有效的防护屏障。当键盘接口受到静电放电或其他浪涌冲击时&#xff0c;该方案能够迅速将过电压和过电流引导至地&#xff0c;从而保护后续电路免受损害。 ESD保护元件是方案中的核心部分&a…

CodeBuddy编程新范式

不会写&#xff1f;不想写&#xff1f; 腾讯推出的CodeBuddy彻底解放双手。 示例 以下是我对CodeBuddy的一个小体验。 我只用一行文字对CodeBuddy说明了一下我的需求&#xff0c;剩下的全部就交给了CodeBuddy&#xff0c;我需要做的就是验收结果即可。 1.首先CodeBuddy会对任…

小刚说C语言刷题—1088求两个数M和N的最大公约数

1.题目描述 求两个正整数 M 和 N 的最大公约数(M&#xff0c;N都在长整型范围内&#xff09; .输入 输入一行&#xff0c;包括两个正整数。 输出 输出只有一行&#xff0c;包括1个正整数。 样例 输入 45 60 输出 15 2.参考代码(C语言版) #include <stdio.h> …

【LLIE专题】基于码本先验与生成式归一化流的低光照图像增强新方法

GLARE: Low Light Image Enhancement via Generative Latent Feature based Codebook Retrieval&#xff08;2024&#xff0c;ECCV&#xff09; 专题介绍一、研究背景二、GLARE方法阶段一&#xff1a;正常光照代码本学习&#xff08;Normal-Light Codebook Learning&#xff09…

[MySQL数据库] SQL优化

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

网络编程epoll和udp

# epoll模型核心要点## 1. epoll核心概念### 1.1 高效IO多路复用- 监视列表与激活列表分离- 内核使用红黑树存储描述符- 边缘触发模式(EPOLLET)支持### 1.2 事件触发机制- **水平触发(LT)**&#xff1a;- 默认模式&#xff0c;类似select/poll- 数据未读完持续触发事件- **边缘…

【iOS】源码阅读(四)——isa与类关联的原理

文章目录 前言OC对象本质探索clang探索对象本质objc_setProperty源码探索 cls与类的关联原理为什么说bits与cls为互斥关系isa的类型isa_t原理探索isa与类的关联 总结 前言 本篇文章主要是笔者在学习和理解类与isa的关联关系时所写的笔记。 OC对象本质探索 在学习和理解类与isa…