图片的几何变换

图片剪切

正如我们前面所学到的,图片在程序中表示就是一个矩阵,我们要想操作图片,只需要操作矩阵元素就可以了.

接下来,我们要来完成图片的剪切案例,其实我们只需要想办法截取出矩阵的一部分即可!

在python中,矩阵的截取是很容易的一件事!例如如下代码

1
mat[起始行号:结束行号,开始列号:结束列号]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import cv2 as cv

# 读取原图
img = cv.imread("img/lena.jpg",cv.IMREAD_COLOR)
cv.imshow("source",img)
# 从图片(230,230) 截取一张 宽度130,高度70的图片
dstImg = img[180:250,180:310]
# 显示图片
cv.imshow("result",dstImg)

cv.waitKey(0)

图片镜像处理

图片的镜像处理其实就是将图像围绕某一个轴进行翻转,形成一幅新的图像. 我们经常可以通过某个小水坑看到天空中的云, 只不过这个云是倒着的! 这个就是我们称为的图片的镜像!

下面我们来看这样一个示例吧!我们将lena这张图片沿着x轴进行了翻转

如果我们想在一个窗口中显示出两张图片,那么我们就需要知道图片的宽高信息啦!

如何获取呢? 看下面的示例代码:

1
2
3
4
imgInfo = img.shape
imgInfo[0] : 表示高度
imgInfo[1] : 表示宽度
imgInfo[2] : 表示每个像素点由几个颜色值构成    

知道了上述信息之后,我们就可以按照如下步骤实现啦!

实现步骤:

  1. 创建一个两倍于原图的空白矩阵
  2. 将图像的数据按照从前向后,从后向前进行绘制

代码实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import cv2 as cv
import numpy as np

img = cv.imread("img/lena.jpg", cv.IMREAD_COLOR)
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]

# 创建一个两倍于原图大小的矩阵
dstImg = np.zeros((height*2,width,3),np.uint8)

# 向目标矩阵中填值
for row in range(height):
    for col in range(width):
        # 上部分直接原样填充
        dstImg[row,col] = img[row,col]
        # 下半部分倒序填充
        dstImg[height*2-row-1,col] = img[row,col]


# 显示图片出来
cv.imshow("dstImg",dstImg)

cv.waitKey(0)
cv.destroyAllWindows()

图片缩放

对于图片的操作,我们经常会用到,放大缩小,位移,还有旋转,在接下来的课程中,我们将来学习这些操作!

首先,我们来学习一下图片的缩放

关于图片的缩放,常用有两种:

  1. 等比例缩放
  2. 任意比例缩放

要进行按比例缩放,我们需要知道图片的相关信息,我们可以通过

1
2
3
4
imgInfo = img.shape
imgInfo[0] : 表示高度
imgInfo[1] : 表示宽度
imgInfo[2] : 表示每个像素点由几个颜色值构成    

图片缩放的常见算法:

  1. 最近领域插值
  2. 双线性插值
  3. 像素关系重采样
  4. 立方插值

默认使用的是双线性插值法,这里我们给出利用opencv提供的resize方法来进行图片的缩放

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import  cv2 as cv

# 读取一张图片
img = cv.imread("img/lena.jpg", cv.IMREAD_COLOR)
# 获取图片信息
imgInfo = img.shape
print(imgInfo)
# 获取图片的高度
height = imgInfo[0]
# 获取图片的宽度
width = imgInfo[1]
# 获取图片的颜色模式,表示每个像素点由3个值组成
mode = imgInfo[2]

# 定义缩放比例
newHeight = int(height*0.5)
newWidth = int(width*0.5)
# 使用api缩放
newImg = cv.resize(img, (newWidth, newHeight))
# 将图片展示出来
cv.imshow("result",newImg)

cv.waitKey(0)
cv.destroyAllWindows()

实现最近邻域插值法

从与计算位置最近的一个像素点获取颜色相关信息!

例如: 宽度和高度都放大2倍, 那么放大之后的图片坐标(2,4)这个点对应应该是原图(1,2)这个点的像素值

那放大之后的图片坐标(1,2)这个点应该对应原图(0.5,1)这个点的像素值.

但是没有0.5这样的角标呀! 那我们可以取这个点附近的颜色值,例如(0,1)这个点的像素值,这个其实就是最近邻域插值法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import  cv2 as cv
import numpy as np
"""
最近邻域插值法原理
"""
img = cv.imread("img/lena.jpg", cv.IMREAD_COLOR)
# 获取当前图片信息
imgInfo = img.shape
# 获取图片的高度
height = imgInfo[0]
# 获取图片的宽度
width = imgInfo[1]
# 指定缩放比例,宽度高度都缩放一半
scale = 0.5
# 目标高度
dstHeight = int(height*scale)
# 目标宽度
dstWidth = int(width*scale)
# 创建空白矩阵模板
dstImg = np.zeros((dstHeight, dstWidth, 3), np.uint8)
# 使用最近邻域插值法填充内容
for row in range(dstHeight):
    for col in range(dstWidth):
        # 计算当前坐标与原图坐标的映射
        sourceRow = int(row/scale)
        sourceCol = int(col/scale)
        # 从原图中获取图片的像素值
        pixel = img[sourceRow,sourceCol]
        # 将获取到的像素值,填充到新的图片中
        dstImg[row,col] = pixel

# 将目标图片显示出来
cv.imshow("scale",dstImg)

cv.waitKey(0)

图片操作原理

我们在前面描述过一张图片,在计算机程序中,其实是用矩阵来进行描述的,如果我们想对这张图片进行操作,其实就是要对矩阵进行运算.

矩阵的运算相信大家在前面课程的学习中,已经学会了,下面我们来给大家列出常见的几种变换矩阵

这里我给大家演示的是图片的位移操作,将一个矩阵的列和行看成坐标系中的x和y我们就可以轻易的按照前面我们所学过的内容来操作矩阵啦!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import cv2 as cv
import numpy as np

img = cv.imread("img/lena.jpg", cv.IMREAD_COLOR)
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]

# 创建一个和原图同样大小的矩阵
dstImg = np.zeros((height,width,3),np.uint8)

for row in range(height):
    for col in range(width):
        # 补成齐次坐标
        sourceMatrix = np.array([col,row,1])

        matrixB = np.array([[1,0,50],
                            [0,1,100]])
        # 矩阵相乘
        dstMatrix = matrixB.dot(sourceMatrix.T)
        # 从原图中获取数据
        dstCol = int(dstMatrix[0])
        dstRow = int(dstMatrix[1])
        # 防止角标越界
        if dstCol < width and dstRow < height:
            dstImg[dstRow,dstCol] = img[row,col]

# 显示图片出来
cv.imshow("dstImg",dstImg)
cv.waitKey(0)

图片移位

刚才我们采用的是纯手工的方式来操作图片,其实我们完全没必要那样做,opencv中已经帮我们提供好了相关的计算操作,我们只需提供变换矩阵就好啦!

1
cv.warpAffine(原始图像,变换矩阵,(高度,宽度))

下面是位移的示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import cv2 as cv
import numpy as np

img = cv.imread("img/lena.jpg",cv.IMREAD_COLOR)
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]
# 定义位移矩阵
matrixShift = np.float32([
                [1,0,50],
                [0,1,100]
              ])
# 调用api
dstImg = cv.warpAffine(img,matrixShift,(width,height))

cv.imshow("dst",dstImg)
cv.waitKey(0)

图片旋转

图片的旋转其实也是很简单的,只不过默认是以图片的左上角为旋转中心

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import cv2 as cv

img = cv.imread("img/itheima.jpg", cv.IMREAD_COLOR)
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]
# 定义仿射矩阵: 参数1:中心点, 参数2:旋转角度,参数3:缩放系数
matrixAffine = cv.getRotationMatrix2D((width * 0.5, height * 0.5), 45, 0.5)
# 进行仿射变换
dstImg = cv.warpAffine(img, matrixAffine, (width, height))

cv.imshow("dstImg",dstImg)
cv.waitKey(0)

图片仿射变换

仿射变换是在几何上定义为两个向量空间之间的一个仿射变换或者仿射映射(来自拉丁语,affine,“和…相关”)由一个非奇异的线性变换(运用一次函数进行的变换)接上一个平移变换组成。

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import cv2 as cv
import numpy as np

img = cv.imread("img/itheima.jpg", cv.IMREAD_COLOR)
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]

# 定义图片 左上角,左下角 右上角的坐标
matrixSrc = np.float32([[0,0],[0,height-1],[width-1,0]])
# 将原来的点映射到新的点
matrixDst = np.float32([[50,100],[300,height-200],[width-300,100]])
# 将两个矩阵组合在一起,仿射变换矩阵
matrixAffine = cv.getAffineTransform(matrixSrc,matrixDst)

dstImg = cv.warpAffine(img,matrixAffine,(width,height))

cv.imshow("dstImg", dstImg)

cv.waitKey(0)

图像金字塔

图像金字塔是图像多尺度表达的一种,是一种以多分辨率来解释图像的有效但概念简单的结构。一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。

降低图像的分辨率,我们可以称为下采样

提高图像的分辨率,我们可以称为上采样

下面我们来测试一下采用这种采样操作进行缩放和我们直接进行resize操作他们之间有什么差别!

我们可以看到,当我们对图片进行下采样操作的时候,即使图片变得非常小,我们任然能够看到它的轮廓,这对后面我们进行机器学习是非常重要的一步操作

,而当我们直接使用resize进行操作的时候,我们发现图片似乎不能完全表示它原有的轮廓,出现了很多的小方块!

下面这里是我们当前案例的示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import cv2 as cv;

src_img = cv.imread("img/lena.jpg",cv.IMREAD_COLOR);
imgInfo = src_img.shape
height = imgInfo[0]
width = imgInfo[1]

pry_down1 = cv.pyrDown(src_img)
cv.imshow("down1",pry_down1)
pry_down2 = cv.pyrDown(pry_down1)
cv.imshow("down2",pry_down2)
pry_down3 = cv.pyrDown(pry_down2)
cv.imshow("down3",pry_down3)
pry_down4 = cv.pyrDown(pry_down3)
cv.imshow("down4",pry_down4)

pyr_up1 = cv.pyrUp(pry_down1)
cv.imshow("up1",pyr_up1)
pyr_up2 = cv.pyrUp(pry_down2)
cv.imshow("up2",pyr_up2)
pyr_up3 = cv.pyrUp(pry_down3)
cv.imshow("up3",pyr_up3)
pyr_up4 = cv.pyrUp(pry_down4)
cv.imshow("up4",pyr_up4)


# 对比resize
img2 = cv.resize(src_img,(int(height/2),int(width/2)))
cv.imshow("img1/2",img2)

img4 = cv.resize(src_img,(int(height/4),int(width/4)))
cv.imshow("img1/4",img4)

img8 = cv.resize(src_img,(int(height/8),int(width/8)))
cv.imshow("img1/8",img8)

img16 = cv.resize(src_img,(int(height/16),int(width/16)))
cv.imshow("img1/16",img16)

cv.waitKey(0)
cv.destroyAllWindows()