5.3.1 节和 5.3.2 节讲的两种仿射变换都比较简单,对于更复杂仿射变换, OpenCV 提供了函数 cv2.getAffineTransform()来生成仿射函数 cv2.warpAffine()所使用的转换矩阵 M。该函数的语法格式为:
retval=cv2.getAffineTransform(src, dst)
式中:
src 代表输入图像的三个点坐标。
dst 代表输出图像的三个点坐标。
在该函数中,其参数值 src 和 dst 是包含三个二维数组(x, y)点的数组。上述参数通过函数cv2.getAffineTransform()定义了两个平行四边形。 src 和 dst 中的三个点分别对应平行四边形的左上角、右上角、左下角三个点。函数 cv2.warpAffine()以函数 cv2.getAffineTransform()获取的转换矩阵 M 为参数,将 src 中的点仿射到 dst 中。函数 cv2.getAffineTransform()对所指定的点完成映射后,将所有其他点的映射关系按照指定点的关系计算确定。
【例 5.7】 设计程序,完成图像仿射。
根据题目要求,设计程序如下:
import cv2
import numpy as np
img=cv2.imread('lena.bmp')
rows,cols,ch=img.shape
p1=np.float32([[0,0],[cols-1,0],[0,rows-1]])
p2=np.float32([[0,rows*0.33],[cols*0.85,rows*0.25],[cols*0.15,rows*0.7]])
M=cv2.getAffineTransform(p1,p2)
dst=cv2.warpAffine(img,M,(cols,rows))
cv2.imshow("origianl",img)
cv2.imshow("result",dst)
cv2.waitKey()
cv2.destroyAllWindows()
在本例中,首先构造了两个三分量的点集合 p1 和 p2,分别用来指代原始图像和目标图像内平行四边形的三个顶点(左上角、右上角、左下角)。然后使用 M=cv2.getAffineTransform(p1,p2)获取转换矩阵 M。接下来, dst=cv2.warpAffine(img,M,(cols,rows))完成了从原始图像到目标图像的仿射。
运行程序,出现如图 5-5 所示的运行结果,其中左图是原始图像, 右图是仿射结果图像。
5.3 节所讲的仿射变换可以将矩形映射为任意平行四边形,透视变换则可以将矩形映射为任意四边形。
透视变换通过函数 cv2.warpPerspective()实现,该函数的语法是:
dst = cv2.warpPerspective( src, M, dsize[, flags[, borderMode[, borderValue]]] )
式中:
dst 代表透视处理后的输出图像,该图像和原始图像具有相同的类型。 dsize 决定输出图
像的实际大小。
src 代表要透视的图像。
M 代表一个 3×3 的变换矩阵。
dsize 代表输出图像的尺寸大小。
flags 代表插值方法,默认为 INTER_LINEAR。 当该值为 WARP_INVERSE_MAP 时,
意味着 M 是逆变换类型, 能实现从目标图像 dst 到原始图像 src 的逆变换。具体可选值
参见表 5-1。
borderMode 代 表 边 类 型 , 默 认 为 BORDER_CONSTANT 。 当 该 值 为 BORDER_
TRANSPARENT 时,意味着目标图像内的值不做改变, 这些值对应原始图像内的异常值。
borderValue 代表边界值,默认是 0。
与仿射变换一样,同样可以使用一个函数来生成函数 cv2.warpPerspective()所使用的转换矩阵。该函数是 cv2.getPerspectiveTransform(),其语法格式为:
retval = cv2.getPerspectiveTransform( src, dst )
式中:
src 代表输入图像的四个顶点的坐标。
dst 代表输出图像的四个顶点的坐标。
需 要 注 意 的 是 , src 参 数 和 dst 参 数 是 包 含 四 个 点 的 数 组 , 与 仿 射 变 换 函 数cv2.getAffineTransform()中的三个点是不同的。实际使用中,我们可以根据需要控制 src 中的四个点映射到 dst 中的四个点。
【例 5.8】 设计程序,完成图像透视。根据题目要求,设计程序如下:
import cv2
import numpy as np
img=cv2.imread('demo.bmp')
rows,cols=img.shape[:2]
print(rows,cols)
pts1 = np.float32([[150,50],[400,50],[60,450],[310,450]])
pts2 = np.float32([[50,50],[rows-50,50],[50,cols-50],[rows-50,cols-50]])
M=cv2.getPerspectiveTransform(pts1,pts2)
dst=cv2.warpPerspective(img,M,(cols,rows))
cv2.imshow("img",img)
cv2.imshow("dst",dst)
cv2.waitKey()
cv2.destroyAllWindows()
在本例中,指定原始图像中平行四边形的四个顶点 pts1,指定目标图像中矩形的四个顶点pts2,使用 M=cv2.getPerspectiveTransform(pts1,pts2)生成转换矩阵 M。接下来,使用语句dst=cv2.warpPerspective(img,M,(cols,rows))完成从平行四边形到矩形的转换。
运行程序,出现如图 5-6 所示的运行结果。 其中,左图是原始图像, 是一个平行四边形(内含多个小平行四边形);右图是透视结果图像, 是一个矩形(内含多个小矩形)。
把一幅图像内的像素点放置到另外一幅图像内的指定位置, 这个过程称为重映射。 OpenCV提供了多种重映射方式,但是我们有时会希望使用自定义的方式来完成重映射。
OpenCV 内的重映射函数 cv2.remap()提供了更方便、更自由的映射方式,其语法格式如下:
dst = cv2.remap( src, map1, map2, interpolation[, borderMode[, borderValue]] )
式中:
dst 代表目标图像,它和 src 具有相同的大小和类型。
src 代表原始图像。
map1 参数有两种可能的值:
表示(x,y)点的一个映射。
表示 CV_16SC2 , CV_32FC1, CV_32FC2 类型(x,y)点的 x 值。
map2 参数同样有两种可能的值:
当 map1 表示(x,y)时, 该值为空。
当 map1 表示(x,y)点的 x 值时, 该值是 CV_16UC1, CV_32FC1 类型(x,y)点的 y 值。
Interpolation 代表插值方式,这里不支持 INTER_AREA 方法。 具体值参见表 5-1。
borderMode 代表边界模式。 当该值为 BORDER_TRANSPARENT 时,表示目标图像内的对应源图像内奇异点(outliers)的像素不会被修改。
borderValue 代表边界值,该值默认为 0。
重映射通过修改像素点的位置得到一幅新图像。在构建新图像时,需要确定新图像中每个像素点在原始图像中的位置。因此,映射函数的作用是查找新图像像素在原始图像内的位置。该过程是将新图像像素映射到原始图像的过程,因此被称为反向映射。在函数 cv2.remap()中,参数 map1 和参数 map2 用来说明反向映射, map1 针对的是坐标 x, map2 针对的是坐标 y。
需要说明的是, map1 和 map2 的值都是浮点数。因此,目标图像可以映射回一个非整数的值,这意味着目标图像可以“反向映射”到原始图像中两个像素点之间的位置(当然,该位置是不存在像素值的)。这时,可以采用不同的方法实现插值,函数中的 interpolation 参数可以控制插值方式。正是由于参数 map1 和参数 map2 的值是浮点数,所以通过函数 cv2.remamp()所能实现的映射关系变得更加随意,可以通过自定义映射参数实现不同形式的映射。
需要注意的是,函数 cv2.remap()中参数 map1 指代的是像素点所在位置的列号,参数 map2指代的是像素点所在位置的行号。例如,我们想将目标图像(映射结果图像)中某个点 A 映射为原始图像内处于第 0 行第 3 列上的像素点 B,那么需要将 A 点所对应的参数 map1 对应位置上的值设为 3,参数 map2 对应位置上的值设为 0。所以,通常情况下,我们将 map1 写为 mapx,并且将 map2 写成 mapy,以方便理解。
同样,如果想将目标图像(映射结果图像)中所有像素点都映射为原始图像内处于第 0 行第 3 列上的像素点 B,那么需要将参数 map1 内的值均设为 3,将参数 map2 内的值均设为 0。
【例 5.9】设计程序, 使用 cv2.remap()完成数组映射,将目标数组内的所有像素点都映射为原始图像内第 0 行第 3 列上的像素点,以此来了解函数 cv2.remap()内参数 map1 和 map2 的使用情况。
根据题目要求,可以确定:
用来指定列的参数 map1(mapx)内的值均为 3。
用来指定行的参数 map2(mapy)内的值均为 0。
根据题目要求及上述分析,设计程序如下:
import cv2
import numpy as np
img=np.random.randint(0,256,size=[4,5],dtype=np.uint8)
rows,cols=img.shape
mapx = np.ones(img.shape,np.float32)*3
mapy = np.ones(img.shape,np.float32)*0
rst=cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print("img=\n",img)
print("mapx=\n",mapx)
print("mapy=\n",mapy)
print("rst=\n",rst)
运行程序,出现如下结果:
img=
[[120 183 101 252 219]
[ 51 106 168 221 118]
[147 16 3 14 159]
[219 67 254 16 62]]
mapx=
[[3. 3. 3. 3. 3.]
[3. 3. 3. 3. 3.]
[3. 3. 3. 3. 3.]
[3. 3. 3. 3. 3.]]
mapy=
[[0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0.]]
rst=
[[252 252 252 252 252]
[252 252 252 252 252]
[252 252 252 252 252]
[252 252 252 252 252]]
通过观察上述结果可知,目标图像(数组) dst 内的所有值都来源于原始图像中第 0 行第 3列上的像素值 252。
为了更好地了解重映射函数 cv2.remap()的使用方法,本节介绍如何通过该函数实现图像的复制。在映射时,将参数进行如下处理:
将 map1 的值设定为对应位置上的 x 轴坐标值。
将 map2 的值设定为对应位置上的 y 轴坐标值。
通过上述处理后,可以让函数 cv2.remap()实现图像复制。下面通过一个例题来观察实现复制时,如何设置函数 cv2.remap()内的 map1 和 map2 参数的值。
【例 5.10】设计程序, 使用函数 cv2.remap()完成数组复制,了解函数 cv2.remap()内参数 map1和 map2 的使用情况。
这里为了方便理解,将参数 map1 定义为 mapx,将参数 map2 定义为 mapy。后续程序中都采用了这种定义方式,后面不再重复说明。
根据题目要求,设计程序如下:
import cv2
import numpy as np
img=np.random.randint(0,256,size=[4,5],dtype=np.uint8)
rows,cols=img.shape
mapx = np.zeros(img.shape,np.float32)
mapy = np.zeros(img.shape,np.float32)
for i in range(rows):
for j in range(cols):
mapx.itemset((i,j),j)
mapy.itemset((i,j),i)
rst=cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
print("img=\n",img)
print("mapx=\n",mapx)
print("mapy=\n",mapy)
print("rst=\n",rst)
运行程序,出现如下结果:
img=
[[203 192 82 156 8]
[245 157 191 163 2]
[148 68 46 3 91]
[227 32 35 123 240]]
mapx=
[[0. 1. 2. 3. 4.]
[0. 1. 2. 3. 4.]
[0. 1. 2. 3. 4.]
[0. 1. 2. 3. 4.]]
mapy=
[[0. 0. 0. 0. 0.]
[1. 1. 1. 1. 1.]
[2. 2. 2. 2. 2.]
[3. 3. 3. 3. 3.]]
rst=
[[203 192 82 156 8]
[245 157 191 163 2]
[148 68 46 3 91]
[227 32 35 123 240]]
通过本例可以观察到,参数 mapx 和参数 mapy 分别设置了 x 轴方向的坐标和 y 轴方向的坐标。函数 cv2.remap()利用参数 mapx、mapy 所组成的数组构造的映射关系实现了图像的复制。
例如, rst 中的像素点[3,4]在 src 内的 x、 y 轴坐标如下:
x 轴坐标取决于 mapx 中 mapx[3,4]的值,为 4。
y 轴坐标取决于 mapy 中 mapy[3,4]的值,为 3。
这说明 rst[3,4]来源于原始图像 src 的第 4 列(x 轴方向,由 mapx[3,4]决定)、第 3 行(y轴方向,由 mapy[3,4]决定),即 rst[3,4]=src[3,4]。原始对象 src[3,4]的值为 240,所以目标对象rst[3,4]的值为 240。
【例 5.11】设计程序, 使用函数 cv2.remap()完成图像复制。根据题目要求,设计程序如下:
import cv2
import numpy as np
img=cv2.imread("lena.bmp")
rows,cols=img.shape[:2]
mapx = np.zeros(img.shape[:2],np.float32)
mapy = np.zeros(img.shape[:2],np.float32)
for i in range(rows):
for j in range(cols):
mapx.itemset((i,j),j)
mapy.itemset((i,j),i)
rst=cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR)
cv2.imshow("original",img)
cv2.imshow("result",rst)
cv2.waitKey()
cv2.destroyAllWindows()