Practical Python and OpenCV,3rd Edition 04
图像转换(image transformations)
本节中,我们将介绍基本的图像转换。 这些是您可能应用于图像的常用技术,包括平移,旋转,调整大小,翻转和裁剪。
平移(Translation)
我们首先介绍的是Translation。Translatioin是沿x和y轴移动图像。 使用Translation,我们可以向上,向下,向左或向右移动图像,以及上述任意组合!请看下面的代码(translation.py):
import numpy as np
import argparse
import imutils
import cv2
ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
help="path to the image")
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
cv2.imshow("Original",image)
M = np.float32([[1,0,25],[0,1,50]])
shifted = cv2.warpAffine(image,M,(image.shape[1],image.shape[0]))
cv2.imshow("Shifted Down and Right",shifted)
M = np.float32([[1,0,-50],[0,1,-90]])
shifted = cv2.warpAffine(image,M,(image.shape[1],image.shape[0]))
cv2.imshow("Shifted Up and Left",shifted)
解释:
主要从M——我们的的translation matrix讲起,该矩阵告诉我们的图像要进行平移多少像素(从左到右,从上到下)。该矩阵被定义为float32类型的数组,因为OpenCV希望该矩阵是一个float类型。The first row of the matrix is [1,0,tx],其中tx是the number of pixels we will shift the image left or right,而负值则表示图像将向左平移,正值表示图像将向右平移。然后我们定义the second row of the matrix as [0,1,ty],其中,ty是the number of pixels we will shift the image up or down。其中,负值表示图像向上平移,正值表示图像向下平移。
使用了上面的那个标记,我们看代码中,将tx=25,ty=50则意味着,我们将图像向右平移25个像素,向下平移50个像素。
我们定义好了平移矩阵之后,图像的实际平移是使用了cv2.warpAffine函数来执行,该函数的第一个参数是我们要进行平移的图像,第二个参数是我们的平移矩阵M,最后我们需要手动地提供图像的尺寸(width and height)作为第三个参数。
前面实现了图像的平移,但是代码太过冗余,我们新建一个.py文件来实现平移功能(imutils.py)
import numpy as np
import cv2
def translate(image,x,y):
M = np.float32([[1,0,x],[0,1,y]])
shifted = cv2.warpAffine(image,M,(image.shape[1],image.shape[0]))
return shifted
解释:
我们的平移方法有三个参数:我们要平移的图像,我们沿x轴移动的像素数,以及我们将沿y轴移动的像素数。然后,此方法定义我们的平移矩阵M,然后再应用实际移位。最后,我们返回移位后的图像。
修改translation.py的内容
shifted = imutils.translate(image,0,100)
cv2.imshow("Shifted Down",shifted)
cv2.waitKey(0)
运行结果:

旋转(Rotation)
在这里,我们将使用θ来表示要旋转多少度。
rotate.py
import numpy as np
import argparse
import imutils
import cv2
ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
help="Path to the image")
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
cv2.imshow("Original",image)
(h,w) = image.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center,45,1.0)
rotated = cv2.warpAffine(image,M,(w,h))
cv2.imshow("Rotated by 45 Degrees",rotated)
M = cv2.getRotationMatrix2D(center,-90,1.0)
rotated = cv2.warpAffine(image,M,(w,h))
cv2.imshow("Rotated by -90 Degrees",rotated)
解释:
前面还是导入必要的包以及解析输入参数和显示原始图像。
当我们旋转图像时,我们需要指定我们想要旋转的点。 在大多数情况下,您需要围绕图像的中心旋转; 我们首先获取图像的宽度和高度,因为OpenCV将图像读取为一个numpy数组,所以和矩阵类似,矩阵的行对应着高,也就是height=image.shape[0],矩阵的列对应着图像的宽,也就是width=image.shape[1],然后我们除以2,确定图像的中心位置。这里我们使用和C语言一样的除法,//表示整除法,以确保我们得到的是整数。
就像我们定义一个矩阵来平移图像一样,我们也定义了一个矩阵来旋转图像。 不是使用NumPy手动构造矩阵,而是调用cv2.getRotationMatrix2D方法。
cv2.getRotationMatrix2D函数有三个参数:第一个是我们想要旋转图像的点,在这里是图像的中心位置,然后我们指定需要旋转的角度θ,我们第一次是旋转了45度,最后一个参数图像的比例。我们还没有讨论调整图像的大小,但是在这里你可以指定浮点值,其中1.0表示使用相同的图像尺寸。 但是,如果指定值为2.0,则图像的大小将加倍。 类似地,值0.5将图像的大小减半。
一旦我们从cv2.getRotationMatrix2D函数获得旋转矩阵M,我们就可以使用cv2.warpAffine方法将旋转应用于我们的图像。此函数的第一个参数是我们想要旋转的图像。然后,我们指定旋转矩阵M以及图像的输出尺寸(宽度和高度)。然后,显示旋转了的图像。
显示效果为:

接下来为了让代码更加的pretty and Pythonic,我们在imutils.py中添加一个rotate方法
def rotate(image,angle,center=None,scale=1.0):
(h,w) = image.shape[:2]
if center is None:
center = (w // 2 , h // 2)
M = cv2.getRotationMatrix2D(center,angle,scale)
rotated = cv2.warpAffine(image,M,(w,h))
return rotated
解释:
我们的rotate方法有四个参数。第一个是你的image。第二个是我们想要旋转图像的角度θ。我们提供两个可选的关键字参数,center和scale。center参数是我们希望旋转图像的点。如果提供了值None,则该方法自动选图像中心为旋转点。最后,scale参数用于处理在旋转期间是否应更改图像的大小。scale参数的默认值为1.0,这意味着不应调整大小。
再次修改rotate.py文件,
rotated = imutils.rotate(image,180)
cv2.imshow("Rotated by 180 Degrees",rotated)
cv2.waitKey(0)
运行结果:

确实很pythonic!!!
Resizing
import numpy as np
import argparse
import imutils
import cv2
ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,help="Path to the image")
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
cv2.imshow("Original",image)
解释:
这个和前面一样,解析参数,读取图片并显示图片
r = 150.0 / image.shape[1]
dim = (150,int(image.shape[0] * r))
resized = cv2.resize(image,dim,interpolation=cv2.INTER_AREA)
cv2.imshow("Resized (Width)",resized)
解释:
r表示the aspect ratio。这里image.shape[1]表示我们图片的宽度,上面的代码我们设置我们图片的新宽度为150个pixels,为了计算新高度与旧高度的比率,我们简单地将比率r定义为新宽度(150像素)除以旧宽度,接着为了保持宽高比,我们计算出高度(height)的变化为 height / width * 150。接下来调用resize函数,该函数的第一个参数是我们希望调整大小的图像,第二个参数是我们为新图像计算的尺寸。最后一个参数是我们的插值方法,这是在背后工作的算法 处理实际图像的大小调整方式。一般来说,我发现使用cv2.INTER_AREA在调整大小时获得最佳效果; 但是,其他适当的选择包括cv2.INTER_LINEAR,cv2.INTER_CUBIC和cv2.INTER_NEAREST。
r = 50.0 / image.shape[0]
dim = (int(image.shape[1] * r),50)
resized = cv2.resize(image,dim,interpolation=cv2.INTER_AREA)
cv2.imshow("Resized (Height)",resized)
cv2.waitKey(0)
解释:
这一段代码和前一段类似,只不过这一次是固定高度为50个像素,然后保持宽高比计算出宽度,显示图像。
resized = imutils.resize(image,width = 100)
cv2.imshow("Resized via Function",resized)
cv2.waitKey(0)
解释:
前面都是用了三行代码实现图像的resize,我们可以利用imutils.resize函数只需要一行即可实现一样的功能。
在imutils.py函数中实现:
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
解释:
第一个参数是我们想要调整大小的图像。然后,我们定义两个关键字参数,宽度和高度。这两个参数都不能为None,否则我们将不知道如何调整图像大小。我们还提供inter,这是我们的插值方法,默认为cv2.INTER_AREA。
显示效果:

Flipping
接下来我们要探索的图像转换是翻转图像。 我们可以围绕x或者翻转图像y轴,甚至两者。查看下图理解一下水平和垂直翻转的区别。

import argparse
import cv2
ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
help="Path to the image")
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
cv2.imshow("Original",image)
解释:
和前面一样,解析参数,显示原始图片
flipped = cv2.flip(image,1)
cv2.imshow("Flipped Horizontally",flipped)
flipped = cv2.flip(image,0)
cv2.imshow("Flipped Vertically",flipped)
flipped = cv2.flip(image,-1)
cv2.imshow("Flipped Horizontally & Vertically",flipped)
cv2.waitKey(0)
我们通过调用cv2.flip函数来完成图像的翻转。cv2.flip方法需要两个参数:我们要翻转的图像和a flip code,用于确定我们如何翻转图片。
使用翻转代码值1表示我们将围绕y轴水平翻转图像。指定翻转代码为0表示我们想要围绕x轴垂直翻转图像。最后,使用负翻转代码翻转两个轴图像。
显示效果:

Cropping
当我们裁剪图像时,我们想要删除我们不感兴趣的图像的外部部分。我们可以使用NumPy数组切片来完成图像裁剪。
import numpy as np
import argparse
import cv2
ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
help="Path to the image")
args = vars(ap.parse_args())
image = cv2.imread(args["image"])
cv2.imshow("Original",image)
cropped = image[30:120,240:335]
cv2.imshow("T-Rex Face",cropped)
cv2.waitKey(0)
解释:
实际裁剪在一行代码上进行。我们提供NumPy数组切片以提取图像的矩形区域,从(240,30)开始到(335,120)结束。The order in which we supply the
indexes to the crop may seem counterintuitive(有悖常理); 但请记住,OpenCV将图像表示为NumPy数组,其高度优先,宽度为次之。这意味着我们需要在x轴之前提供y轴值.
为了执行我们的裁剪,NumPy需要四个索引:
开始y:起始y坐标。在这种情况下,我们从y=30开始。
结束y:结束y坐标。我们将在y=120时结束我们的裁剪。
开始x:切片的起始x坐标。我们在x=240时开始裁剪
结束x:切片的结束x轴坐标。我们的切片在x=335处结束
显示效果:

用到的函数
cv2.warpAffine
cv2.getRotationMatrix2D
cv2.resize
cv2.flip
;