图像的几何变换是指将一幅图像映射到另一幅图像内。有缩放、翻转、仿射变换、透视、重映射等操作。

1 cv2.resize() 缩放

使用cv2.resize()函数实现对图像的缩放,但要注意cv2.resize()函数内的dsize参数与原图像的行列属性是相反的,也就是:目标图像的行数是原始图像的列数,目标图像的列数是原始图像的行数。

原型:cv2.resize(src, dst, size, fx, fy, interpolation)

参数:

  1. src:输入图片
  2. dst:输出图片
  3. size:输出图片尺寸,二元组(width,height),以像素为单位。
  4. fx,fy:沿x轴,y轴的缩放系数
  5. interpolation:采用的插值方法。支持的插值算法有:
  • INTER_NEAREST-最近邻插值
  • INTER_LINEAR   -双线性插值(默认值)
  • INTER_CUBIC     -双S三次样条插值(4x4像素领域内的双三次插值)
  • INTER_LANCZOS4     - Lanczos插值(8x8像素领域内的Lanczos插值)

返回值:dst

示例

import cv2
 
# 【读入原图片】
img = cv2.imread('C:\\Users\\xxx\\Downloads\\lena.jpg')

# 【将图片高和宽分别赋值给x,y】
x, y = img.shape[0:2]
 
# 【缩放到原来的二分之一,输出尺寸格式为(宽,高)】
img_test1 = cv2.resize(img, (int(y / 2), int(x / 2)))
# 【最近邻插值法缩放,缩放到原来的四分之一】
img_test2 = cv2.resize(img, (0, 0), fx=0.25, fy=0.25, interpolation=cv2.INTER_NEAREST)

# 【显示图片】
cv2.imshow('original', img)
cv2.imshow('resize,scale=0.5', img_test1)
cv2.imshow('resize,scale=0.25', img_test2)
cv2.waitKey()
cv2.destroyAllWindows()
# 【打印出图片尺寸】
print(img.shape)
print(img_test1.shape)
print(img_test2.shape)

运行后,得到:

2 cv2.flip()翻转

使用cv2.flip()函数对图像翻转,能够实现水平方向翻转、垂直方向翻转、两个方向同时翻转。

原型:cv2.flip(src,filpCode[,dst])

参数:

  1. src:输入图片
  2. filpCode:0-垂直翻转;>0-水平翻转;<0-垂直水平翻转
  3. dst:输出图片

返回值:dst

示例

import cv2
import matplotlib.pyplot as plt
 
# 【读入原图片】
img = cv2.imread('C:\\Users\\xxx\\Downloads\\lena.jpg')
#v_img=img.copy()

# 【翻转图片】
v_img  = cv2.flip(img,0) #垂直翻转
h_img  = cv2.flip(img,1) #水平翻转
vh_img = cv2.flip(img,-1) #垂直翻水平转

plt.figure(figsize=(25.6,4.8))
plt.subplot(141),plt.imshow(img),plt.title('Oringinal',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(142),plt.imshow(v_img), plt.title('v-flip',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(143),plt.imshow(h_img),plt.title('h-flip',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(144),plt.imshow(vh_img), plt.title('vh-flip',fontSize=24),plt.xticks([]),plt.yticks([])

运行后,得到:

3 cv2.remap() 重映射

把一个图像中一个位置的像素放置到另一个图片指定位置的过程,叫做 重映射

重映射是修改了像素点的位置,从而生成一幅新的图像,包括:复制、绕x轴y轴翻转,x轴y轴互换,图像缩放等。

原理

为了完成映射过程,有必要获得一些插值为非整数像素坐标,因为源图像与目标图像的像素坐标不是一一对应的。

通过重映射来表达每个像素的位置(x,y)

这里g() 是目标图像,f() 是源图像, 是作用于 的映射方法函数。

让我们来思考一个快速的例子。想象一下我们有一个图像 I ,我们想满足下面的条件作重映射:

会发生什么? 图像会按照x 轴方向发生翻转。

通过 OpenCV 的函数 remap 提供一个简单的重映射实现。

cv2.remap()函数

可以把⼀幅图像中某位置的像素放置到另⼀个图⽚指定位置的过程。可以实现图像的变形、扭曲、反转等操作。实现图像数据的增强,提升深度模型的泛化能⼒。可以根据⾃⼰设定的函数将图像进⾏变换,较常见的功能有关于x轴翻转、关于y轴翻转、关于x、y轴翻转;(仿射变换在图像处理中的主要功能为:对图像进⾏缩放、旋转、平移、扭曲等)

原型:cv2.remap(img,mapX,mapY[,interpolation[,borderMode[,borderValue]]])

参数

  1. img:输入图像
  2. mapX:用于插值的x坐标。相当于 的第一个参数。可以为CV_16SC2 , CV_32FC1, CV_32FC2 类型(x,y)点的 x 值。也可以为一个二元组,表示(x,y)的一个映射。
  3. mapY:用于插值的y坐标。相当于 的第二个参数。若mapX表示(x,y),该值为空;若mapX表示(x,y)的x值,该值为CV_16SC2 , CV_32FC1类型(x,y)的y值。
  4. interpolation:采用的插值方法。支持的插值算法有:INTER_NEAREST-最近邻插值;INTER_LINEAR   -双线性插值(默认值);INTER_CUBIC     -双S三次样条插值(逾4x4像素领域内的双三次插值);INTER_LANCZOS4     - Lanczos插值(逾8x8像素领域内的Lanczos插值)
  5. borderMode:边界模式,默认为cv2.BORDER_CONSTANT,表示目标图像中的“离群点(outliers)”的像素值不会被此函数修改。
  6. borderValue:当borderMode为cv2.BORDER_CONSTANT,有默认值Scalar(),即默认值为0。

示例:复制图像

import numpy as np
import matplotlib.pyplot as plt
import cv2

# 【解决plt显示汉字乱码的临时设置】
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 【读原始图】
img = cv2.imread('C:\\Users\\xxx\\Downloads\\lena.jpg')

# 【获取参数,重映射】
rows, cols = img.shape[:2] # 读取行列数
mapX = np.zeros(img.shape[:2],np.float32) # mapX参数设定为对应位置上的x坐标值
mapY = np.zeros(img.shape[:2],np.float32) # mapY参数设定为对应位置上的y坐标值

for i in range(rows):  # 对每个元素复制映射
    for j in range(cols):
        mapX.itemset((i,j),j)
        mapY.itemset((i,j),i)
dst = cv2.remap(img,mapX,mapY,cv2.INTER_LINEAR) # 重映射

# 【画图】
plt.figure(figsize=(12.8,4.8))
plt.subplot(121),plt.imshow(img),plt.title('原始图像',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('复制图像',fontSize=24),plt.xticks([]),plt.yticks([])

运行后,结果如下:

4 仿射变换

仿射变换是指图像实现平移、旋转等操作。

要先设置一个2×3变换矩阵M,然后使用cv2.warpAffine()函数对原图像和变换矩阵M进行仿射操作。

变换矩阵M可通过cv2.getAffineTransfrom(points1, points2)函数获得。

原理

一、一个任意的仿射变换都能表示为 乘以一个矩阵 A (线性变换) 接着再 加上一个向量 B (平移)。

我们能够用仿射变换来表示:

  1. 旋转 (线性变换)
  2. 平移 (向量加)
  3. 缩放操作 (线性变换)

事实上, 仿射变换代表的是两幅图之间的 关系

二、通常使用2×3矩阵M 来表示仿射变换。

考虑到我们要使用矩阵 AB 对二维向量 做变换, 所以也能表示为下列形式:

三、如何求得一个仿射变换?

仿射变换基本表示的就是两幅图片之间的 联系 . 关于这种联系的信息大致可从以下两种场景获得:

  1. 我们已知 XT 而且我们知道他们是有联系的. 接下来我们的工作就是求出矩阵M
  2. 我们已知 M  和X . 要想求得T . 我们只要应用算式 T=MX 即可。对于这种联系的信息可以用矩阵M 清晰的表达 (即给出明确的2×3矩阵) 或者也可以用两幅图片点之间几何关系来表达.

因为矩阵M 联系着两幅图片, 我们以其表示两图中各三点直接的联系为例. 见下图:

点1, 2 和 3 (在图一中形成一个三角形) 与图二中三个点一一映射, 仍然形成三角形, 但形状已经大大改变. 如果我们能通过这样两组三点求出仿射变换 (你能选择自己喜欢的点), 接下来我们就能把仿射变换应用到图像中所有的点.

cv2.warpAffine()函数

原型:

cv2.warpAffine(src, M, dsize[,dst[,interpolation [,borderMode[, borderValue]]]]) --> dst

参数

  1. src:输入图像    
  2. M:2×3的变换矩阵
  3. dsize:变换后输出图像尺寸
  4. dst:输出图像。默认为None
  5. interpolation:采用的插值方法。支持的插值算法有:INTER_NEAREST-最近邻插值;INTER_LINEAR   -双线性插值(默认值);INTER_CUBIC     -双S三次样条插值(4x4像素领域内的双三次插值);INTER_LANCZOS4     - Lanczos插值(8x8像素领域内的Lanczos插值)
  6. borderMode:边界像素外扩方式。
  7. borderValue:边界像素插值,默认用0填充

返回值:dst

cv2.getAffineTransform() 获取仿射变换矩阵

变换矩阵的获取需要至少三组变换前后对应的点坐标,设取原图上的三个点组成矩阵points1,变换后的三个点组成的矩阵points2

原型:cv2. getAffineTransform (points1,pointts2) --> M

参数

  1. points1:原图像上的3个点 。3x2阶矩阵。np.float32([[x1,y1],[x2,y2],[x3,y3]]
  2. points2:变换后的3个对应点。3x2阶矩阵。np.float32([[x1,y1],[x2,y2],[x3,y3]]

返回值:为2x3阶的仿射变换矩阵。

cv2.getRotationMatrix2D() 获取仿射变换矩阵

原型:cv2. getRotationMatrix2D(center,angle,scale) --> M

参数

  1. center:也是旋转中心(默认是图片的左上角) ,二元组(x,y)
  2. angle:旋转角度。正数表示顺时针旋转,负数表示逆时针旋转。
  3. scale:缩放比例。float。1表示进行等比列的缩放

返回值:为2x3阶的仿射变换矩阵。

平移变换矩阵

平移的变换矩阵M= ,其中∆x 为在宽度方向上的平移量(正数向右,负数向左);其中∆y 为在高度方向上的平移量(正数向下,负数向上)。

旋转变换矩阵

使用函数cv2.getRotationMatrix2D()获得转移矩阵M,然后使用函数cv2.warpAffine()进行仿射旋转变换。

图像的旋转矩阵一般为:

但是单纯的这个矩阵是在原点处进行变换的,为了能够在任意位置进行旋转变换,opencv采用了另一种方式:

其中θ 为旋转角度(正数为逆时针旋转,负数为顺时针旋转);旋转中心默认为图片的左上角。

示例:绕x轴翻转

import numpy as np
import matplotlib.pyplot as plt
import cv2

# 【解决plt显示汉字乱码的临时设置】
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 【读原始图】
img = cv2.imread('C:\\Users\\xxx\\Downloads\\lena.jpg')

# 【获取参数,重映射】
rows, cols = img.shape[:2] # 读取行列数
mapX = np.zeros(img.shape[:2],np.float32) # mapX参数设定为对应位置上的x坐标值
mapY = np.zeros(img.shape[:2],np.float32) # mapY参数设定为对应位置上的y坐标值
for i in range(rows):  # 对每个元素复制映射
    for j in range(cols):
        mapX.itemset((i,j),j)  # mapX保持不变
        mapY.itemset((i,j),rows-1-i)  # mapY调整为总行数-1-当前行号
dst = cv2.remap(img,mapX,mapY,cv2.INTER_LINEAR) # 重映射

# 【画图】
plt.figure(figsize=(12.8,4.8))
plt.subplot(121),plt.imshow(img),plt.title('原始图像',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('绕x轴翻转',fontSize=24),plt.xticks([]),plt.yticks([])

运行后,结果如下:

5 透视变换

透视变换(Perspective Transform)仿射变换(Affine Transform)在图像还原、局部变化处理方面有重要意义。通常,在2D平面中,仿射变换的应用比较多;在3D平面中,透视变换占领地位较高。两种变换原理相似,结果也相似,可以针对不同场合选择适合方法。

两者的计算方法就是矩阵运算,即,坐标向量和变换矩阵的乘积。

透视变换的原理

透视变换的定义为将图像投影到一个新的视平面,通常也被称之为投影映射。如果你想对图像进行校准,那么透视变换是非常有效的变换手段。

一般来说,通用的图像变换公式如下所示:

u,v 是原始图片, 对应得到的变换后的图片坐标 x , y  为:

 

变换矩阵是 ,可以拆分为4个部分, 是线性变换,如缩放、裁剪、旋转。 用于平移, 产生透视变换。重写之前的变换公式可以得到:

所以,已知变换对应的几个点就可以求取变换公式。反之,特定的变换公式也能新的变换后的图片。

x',y',w' 是变换后的坐标,[u,v,w]  是变换前的坐标,变换矩阵A3×3 的矩阵。那这里就有个疑问了呀,我们图片每个像素点不是  (x,y)  吗? 这怎么出现了3维了?

这是因为透视变换是从3维空间上进行的变换,但处理二维图形怎么办呢?

这里的 w 默认是1, 所以对于图片里面的每个像素点,其坐标就成了这样 [x,y,1] , 这也就是为啥变换之后,得到的三维坐标点转二维的时候是 的原因。

另外还有个问题,就是 恒等于1。

这样矩阵A 我们需要求8个参数,而根据上面这个方程来看,如果我们知道一组输入坐标点(u,v) , 一组输出坐标点 (x,y) ,那么我们是能建立两个等式方程的。但是我们有8个参数,需要的是8组这样的等式方程才可以解,所以我们需要四组输入坐标点,四组输出坐标点。

这样,就能把 A 矩阵的各个参数给解出来,得到了A ,我们对于每个像素点,都能根据上面的公式进行透视变换得到在新平面下的坐标, 那么新图片就出来了。

这就是透视变换的原理, 那么我们这里该怎么用呢?

首先,根据上面的操作,我们已经得到矩形框的位置,其实这里面就是我们想要的东西了嘛,所以其实接下来就是针对这个矩形框里面的所有东西做透视变换。 而恰好,我们拿到了矩形框的四个顶点,正好四组坐标。

那么我们还需要透视后图片的四个顶点坐标,我们就能透视变换了,这个怎么得到呢? 透视后图片有一个顶点是知道的,就是左上角的[0,0],那么如果我们从原始的图片里面知道了矩形框的长和宽,我们其实就能得到四个顶点坐标。有了输入图片和输出图片的四组顶点坐标,通过调用OpenCV里面的cv2.getPerspectiveTransform(rect, dst),自动就给我们解8个方程组得到 A 透视变换矩阵, 而有了这个矩阵,我们就能对框里面的每个像素做透视变换,得到最终的图片了。这里用的是cv2.warpPerspective(image, W, (maxWidth, maxHeight))

cv2.getPerspectiveTransform()获取透视变换矩阵

函数原型: t=cv2.getPerspectiveTransform(src,dist,solveMethod)

参数:

  1. src 源图像上四个点的坐标构成的矩阵,要求其中任意三点不共线
  2. dist目标图像上四个点的坐标构成的矩阵,要求其中任意三点不共线,且每个点与src的对应点对应。
  3. solveMethod:矩阵分解方法,传递给cv2.solve(DecompTypes) 求解线性方程组或解决最小二乘问题,默认值为None,表示使用cv2.DECOMP_LU。

solveMethod对应取值及含义如下

cv2.DECOMP_LU

选择最优轴元的高斯消去法

cv2.DECOMP_SVD

奇异值分解方法;支持超定系统(方程个数超过变量个数)或矩阵src1是奇异矩阵(方程个数小于变量个数)

cv2.DECOMP_EIG

特征值分解;矩阵src必须是对称的

cv2.DECOMP_CHOLEKEY

Cholesky LLT分解,即带平方根的(LLT)Cholesky算法分解对称正定矩阵,矩阵src1必须是对称正定矩阵。Cholesky分解是把一个对称正定的矩阵表示为一个下三角矩阵L和其转置的乘积的分解。它要求矩阵的所有特征值必须大于0,故分解的下三角的对角元也是大于0的。

cv2.DECOMP_QR

QR分解,QR(正交三角)分解法是求一般矩阵全部特征值的最有效并广泛应用的方法,它是将矩阵分解为一个正规正交矩阵Q与上三角矩阵R,支持支持超定系统(方程个数超过变量个数)或矩阵src1是奇异矩阵(方程个数小于变量个数)

cv2.DECOMP_NORMAL

前面5个标志是互斥的,该标志可以和前面任意一个标志进行组合使用;它表示使用正则方程(the normal equations)

src1T∙src1∙dst=src1T∙src2

求解,代替原有方程

src1∙dst=src2

注:矩阵分解 ,英文称为matrix decomposition或matrix factorization是将矩阵拆解为数个矩阵的乘积,可分为三角分解、满秩分解、Jordan分解和SVD(奇异值)分解等,常见的有三种:1)三角分解法 (Triangular Factorization),2)QR 分解法 (QR Factorization),3)奇异值分解法 (Singular Value Decompostion)。 在图像处理方面,矩阵分解被广泛用于降维(压缩)、去噪、特征提取、数字水印等,是十分重要的数学工具,其中特征分解(谱分解)和奇异值分解是两种常用方法。

返回值:mat,为一个3*3的透视变换矩阵

cv2.warpPerspective() 透视变换

warpPerspective函数用于对输入图像进行透视变换。

Perspective_img是相对于原图img是以M 变换后的图像,同理,对于原图img上任何一点的坐标A(x,y) 通过变换矩阵M 变换后可在Perspective_img上都能找到与之对应的坐标点A'(x',y') ,变换方法为:

先构造一个3×1的矩阵a=xy1 ,则:

M∙a=m11m12m13m21m22m23m31m32m33xy1

x'=m11x+m12y+m13m31x+m32v+m33

y'=m21x+m22y+m23m31x+m32v+m33

函数原型:result=cv2.wrapPerspective(src,M,dsize,dst=None,flags=None,borderType=None,borderValue=None)

参数:

  1. src 输入图像矩阵
  2. M:3*3的透视变换矩阵,可以通过getPerspectiveTransform等函数获取。
  3. dsize:结果图像大小,为宽和高的二元组
  4. flags:可选参数,插值方法的组合(int 类型),默认值 INTER_LINEAR,本函数官方材料说明取值为INTER_LINEAR 或 INTER_NEAREST与 WARP_INVERSE_MAP的组合。

如果flags标记设置了WARP_INVERSE_MAP标记,首先使用invertAffineTransform对变换矩阵进行反转即求其逆矩阵,然后将其放入上面的公式中,而不是将M直接放入

  1. borderType:可选参数,边界像素模式(int 类型),默认值 BORDER_CONSTANT,本函数官方材料说明取值为BORDER_CONSTANT 或 BORDER_REPLICATE,实际上所有取值类型都支持,包括形态变换中不支持的BORDER_WRAP、BORDER_TRANSPARENT都能支持,并且不同取值有不同效果
  2. borderValue:可选参数,边界填充值,当borderType为cv2.BORDER_CONSTANT时使用,默认值为None;

返回值:透视变换后的结果图像

示例1:平移图像

import cv2
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

img = cv2.imread('C:\\Users\\xxx\\Downloads\\lena.jpg')
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]
# 【构建平移矩阵】
#  [1,0,100]的意思是宽右移距离100,
#  [0,1,200]的意思是⾼下移200
shiftMat = np.float32([[1,0,100],[0,1,200]])
# 【平移】
dst = cv2.warpAffine(img,shiftMat,(width,height))
# 【显示图像】
plt.figure(figsize=(12.8,4.8))
plt.subplot(121),plt.imshow(img),plt.title('Oringinal',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(dst), plt.title('translation',fontSize=24),plt.xticks([]),plt.yticks([])

 示例2:旋转图像

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 【解决plt显示汉字乱码的临时设置】
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 【读原始图】
img = cv2.imread('C:\\Users\\liyou\\Downloads\\picture1.jpeg')
rows, cols = img.shape[:2]

# 【获取仿射变换矩阵】
#    第一个参数旋转中心,
#    第二个参数旋转角度,
#    第三个参数:缩放比例
M1 = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 1)
M2 = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 2)

# 【执行仿射变换】
# 得到矩阵后得用到图像的仿射变换函数才可以进行最终图像的变化
dst1 = cv2.warpAffine(img, M1, (cols, rows))
dst2 = cv2.warpAffine(img, M2, (cols, rows))

# 【画图】
plt.figure(figsize=(19.2,4.8))
plt.subplot(131),plt.imshow(img),plt.title('原始图像',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(132),plt.imshow(dst1), plt.title('围绕中心旋转45度',fontSize=24),plt.xticks([]),plt.yticks([])
plt.subplot(133),plt.imshow(dst2), plt.title('围绕中心旋转45度,放大2倍',fontSize=24),plt.xticks([]),plt.yticks([])

  示例3:将图像上的四个点的区域进行透视转换

import cv2
import numpy as np

# 读原图
img = cv2.imread('C:/Users/xxx/Downloads/picture1.jpeg')
h, w, c = img.shape  

# 挑选源图四个点
# 采用左上,左下,右下和右上,下面的代码把我们挑选的四个点画到图像上
src_list = [(61, 70), (151, 217), (269, 143), (160, 29)]
for i, pt in enumerate(src_list):
    cv2.circle(img, pt, 2, (0, 255, 0), -1)
    cv2.putText(img,str(i+1),(pt[0]+5,pt[1]+10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

# 绘制未填充的多边形
pts1 = np.int32(src_list)
cv2.polylines(img, [pts1], isClosed=True, color=(0,255,0), thickness=3)
pts1 = np.float32(src_list)

# 进行透视变换
pts2 = np.float32([[0, 0], [0, w - 2], [h - 2, w - 2], [h - 2, 0]])
matrix = cv2.getPerspectiveTransform(pts1, pts2)
result = cv2.warpPerspective(img, matrix, (h, w))
cv2.imshow("Image", img)
cv2.imshow("Perspective transformation", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

 

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐