图像在计算机中均是以二维矩阵的方式存储的,矩阵中的每个元素对应一个像素点,类型为uint8,因此值在0-255之间。灰度图像就是一个二维矩阵,0-255对应从黑到白256个级别的灰度。彩色图像根据不同的色彩空间有不同的存储方式,最常见的RGB彩色空间,则有三个通道,每个通道也是一个二维矩阵,对应了红、绿、蓝三种基本颜色的分量。
因此,OpenCV读取的图片或视频流中的一帧图像均为numpy的ndarray,若为灰度图像,即为一个二维矩阵;若为三通道BGR彩色图像,则为三个二维矩阵。原点默认为左上角,x轴为水平方向,向右为正方向;y轴为垂直方向,向下为正方向。也即矩阵的第一个元素 M 00 M_{00} M00就对应坐标为 ( 0 , 0 ) (0,0) (0,0)的像素,矩阵第 i i i行第 j j j列的元素 M i j M_{ij} Mij对应坐标为 ( j , i ) (j, i) (j,i)的像素,此处注意坐标和矩阵位置的顺序!此外,从图像角度描述图像大小时往往为宽*高,而从矩阵角度描述大小时,往往为行*列。而行对应高,列对应宽,故一张 1024 × 768 1024 \times 768 1024×768的图像对应的是 768 × 1024 768 \times 1024 768×1024的矩阵。
既然图像通过矩阵方式存储,那么针对图像的很多操作实际上就是针对矩阵的操作,下面就对OpenCV封装的对矩阵的一些操作进行小结。
该函数用于对图片进行翻转。官方文档:https://docs.opencv.org/3.4.2/d2/de8/group__core__array.html#gaca7be533e3dac7feb70fc60635adf441
dst = cv2.flip(src, flipCode)
该函数用于对图像的像素数据矩阵进行转置,即将图像沿y=x翻转,相当于逆时针旋转90°后再垂直翻转(或水平翻转后再逆时针旋转90°),注意与直接逆时针旋转90°的区别!官方文档:https://docs.opencv.org/3.4.2/d2/de8/group__core__array.html#ga46630ed6c0ea6254a35f447289bd7404
dst = cv2.transpose(src)
从线性代数的知识可知,一个n维方阵可以表示对n维空间的线性变换。那么既然图像以二维矩阵的方式存储在计算机中(多通道彩色图像可以看做多个二维矩阵的叠加),图像的二维矩阵左乘一个2*2的矩阵,就可以对该图像进行线性变换,包括拉伸、旋转,再加上平移即组成了图像的仿射变换。因此OpenCV提供了对图像进行仿射变换的函数,主要作用即为对图像数据左乘2维方阵进行线性变换,再加上平移。官方文档:https://docs.opencv.org/3.4.2/d7/d1b/group__imgproc__misc.html#ga72b913f352e4a1b1b397736707afcde3
dst = cv2.warpAffine(src, M, dsize[, flags[, borderMode[, borderValue]]])
img = cv2.imread(r'.\image.jpg', cv2.IMREAD_COLOR)
h, w, channels = img.shape
# 定义平移矩阵,需要是numpy的float32类型
# x轴平移100,y轴平移50的矩阵
M = np.float32([[1, 0, 100],
[0, 1, 50]])
# 使用仿射变换实现平移,并使用白色填充边界
imgShift = cv2.warpAffine(img, M, (w, h), borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255))
cv2.imshow('shift', imgShift)
# 定义一个绕图片中心逆时针旋转90°的仿射变换矩阵
M = np.float32([[0, 1, 0],
[1, 0, 0]])
imgRotate = cv2.warpAffine(img, M, (h, w))
cv2.imshow('rotate', imgRotate)
# 定义一个斜右下角剪切(shear)的仿射变换矩阵
M = np.float32([[1, 1, 0],
[0, 1, 0]])
imgShear = cv2.warpAffine(img, M, (2*w, h), borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255))
cv2.imshow('shear', imgShear)
如上所述,仿射变换可以完成很多针对图像的操作,如平移、旋转、拉伸等。但往往计算仿射变换对应的矩阵需要具备线性代数的相关知识,并且计算量也较大。因此OpenCV提供了两个函数来生成仿射变换矩阵,极为方便。其中getRotationMatrix2D
函数是专用于生成旋转对应的仿射变换的矩阵,getAffineTransform
则更为通用,通过变换前后三个非共线点的坐标来生成对应的仿射变换矩阵。官方文档:https://docs.opencv.org/3.4.2/da/d54/group__imgproc__transform.html
# 生成旋转对应的仿射变换矩阵
M = cv2.getRotationMatrix2D(center, angle, scale)
# 根据仿射变换前后三个点的位置变化生成对应的矩阵
M = cv2.getAffineTransform(src, dst)
warpAffine
函数;img = cv2.imread(r'\image.jpg', cv2.IMREAD_COLOR)
h, w, channels = img.shape
# 绕图片中心逆时针旋转45°,并缩小一半
M = cv2.getRotationMatrix2D((w / 2, h / 2), 45, 0.5)
imgRot = cv2.warpAffine(img, M, (w, h))
cv2.imshow('rotation', imgRot)
# 通过变换前后三个点的坐标获取对应的仿射变换矩阵
# 变换前的三个点
pts1 = np.float32([[0, 0], [1, 0], [0, 1]])
# 变换后的三个点,逆时针旋转90°,并均下移宽度位置
pts2 = np.float32([[0, w], [0, -1+w], [1, w]])
# 生成变换矩阵
M = cv2.getAffineTransform(pts1, pts2)
imgRot = cv2.warpAffine(img, M, (h, w))
cv2.imshow('image Rotation', imgRot)
图像的几何变换中,除了仿射变换,还有透视变换,也就是将图片重新投影到一个新的视平面。实际应用中往往通过这种方式矫正图片。类似仿射变换,配套透视变换函数warpPerspective
的还有生成透视变换矩阵的函数getPerspectiveTransform
,通过四个非共线点来生成对应的透视变换矩阵。官方文档:https://docs.opencv.org/3.4.2/da/d54/group__imgproc__transform.html
# 根据透视变换前后非共线的四个点的位置变化生成对应的透视变换矩阵
M = cv2.getPerspectiveTransform(src, dst)
# 根据透视变换矩阵将图像进行透视变换
ret = cv2.warpPerspective(img, M, dsize[, flags[, borderMode[, borderValue]]])
warpPerspective
函数;img = cv2.imread(r'.\image.jpg', cv2.IMREAD_COLOR)
h, w, channels = img.shape
# 卡片的四个角点,非共线的四个点才能唯一确定透视变换
pts1 = np.float32([[148, 80], [437, 114], [94, 247], [423, 288]])
# 变换后分别在左上、右上、左下、右下四个点
pts2 = np.float32([[0, 0], [320, 0], [0, 178], [320, 178]])
# 根据变换前后点的位置生成透视变换矩阵
M = cv2.getPerspectiveTransform(pts1, pts2)
# 进行透视变换
imgPersp = cv2.warpPerspective(img, M, (320, 178))
cv2.imshow('Perspective transform', imgPersp)