图像的几何变换从原理上看主要包括两种:基于2×3矩阵的仿射变换(平移、缩放、旋转和翻转等)、基于3×3矩阵的透视变换。
- 仿射变换
基本的图像变换就是二维坐标的变换:从一种二维坐标(x,y)到另一种二维坐标(u,v)的线性变换:
如果写成矩阵的形式,那就是:
作如下定义:
矩阵T(2×3)就称为仿射变换的变换矩阵,R为线性变换矩阵,t为平移矩阵,简单来说,仿射变换就是线性变换+平移。变换后直线依然是直线,平行线依然是平行线,直线间的相对位置关系不变,因此非共线的三个对应点便可确定唯一的一个仿射变换,线性变换4个自由度+平移2个自由度→仿射变换自由度为6。
仿射变换在OpenCV中的实现如下:
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread('drawing.jpg') rows, cols = img.shape[:2] # 变换前的三个点 pts1 = np.float32([[50, 65], [150, 65], [210, 210]]) # 变换后的三个点 pts2 = np.float32([[50, 100], [150, 65], [100, 250]]) # 生成变换矩阵,维数:2*3 M = cv2.getAffineTransform(pts1, pts2) dst = cv2.warpAffine(img, M, (cols, rows)) plt.subplot(121), plt.imshow(img), plt.title('input') plt.subplot(122), plt.imshow(dst), plt.title('output') plt.show()
运行结果:
其实平移、旋转、缩放和翻转等变换就是对应了不同的仿射变换矩阵,下面分别来看:
(1)平移
平移就是x和y方向上的直接移动,可以上下/左右移动,自由度为2,变换矩阵可以表示为:
# 平移图片 import numpy as np rows, cols = img.shape[:2] # 定义平移矩阵,需要是numpy的float32类型 # x轴平移100,y轴平移50 M = np.float32([[1, 0, 100], [0, 1, 50]]) dst = cv2.warpAffine(img, M, (cols, rows)) cv2.imshow('shift', dst) cv2.waitKey(0)
(1)通过比例进行缩放
import cv2 as cv import numpy as np # 图片缩放 img = cv.imread('images/animal.jpg', flags=1) # flags=1读取为彩色,flags=0读取为灰度 cv.imshow('i', img) h, w, channel = img.shape # 以行列形式存储, 第几行到第几行为图像高度 dst_h = int(h*0.5) dst_w = int(w*0.5) # 最近邻域差值 双线性插值 像素关系重采样 立方差值 dst = cv.resize(img, (dst_w, dst_h)) # 默认双线性差值 cv.imshow('img', dst) cv.waitKey(0)
OpenCV提供了resize函数来改变图像的大小,C++中的函数原型如下:
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR );
函数参数说明:
src:输入,原图像,即待改变大小的图像;
dst:输出,改变大小之后的图像,这个图像和原图像具有相同的内容,只是大小和原图像不一样而已;
dsize:输出图像的大小。如果这个参数不为0,那么就代表将原图像缩放到这个Size(width,height)指定的大小;如果这个参数为0,那么原图像缩放之后的大小就要通过下面的公式来计算:
dsize = Size(round(fx*src.cols), round(fy*src.rows))
其中,fx和fy就是下面要说的两个参数,是图像width方向和height方向的缩放比例。
fx:width方向的缩放比例,如果它是0,那么它就会按照(double)dsize.width/src.cols来计算;
fy:height方向的缩放比例,如果它是0,那么它就会按照(double)dsize.height/src.rows来计算;
interpolation:这个是指定插值的方式,图像缩放之后,肯定像素要进行重新计算的,就靠这个参数来指定重新计算像素的方式,有以下几种:
- INTER_NEAREST - 最邻近插值
- INTER_LINEAR - 双线性插值,如果最后一个参数你不指定,默认使用这种方法
- INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method.
- INTER_CUBIC - 4x4像素邻域内的双立方插值
- INTER_LANCZOS4 - 8x8像素邻域内的Lanczos插值
函数使用说明:
- dsize和fx/fy不能同时为0,要么你就指定好dsize的值,让fx和fy空置直接使用默认值,就像resize(img, imgDst, Size(30,30)); 要么你就让dsize为0,指定好fx和fy的值,比如fx=fy=0.5,那么就相当于把原图两个方向缩小一倍!
- 至于最后的插值方法,正常情况下使用默认的双线性插值就够用了。几种常用方法的效率是:最邻近插值>双线性插值>双立方插值>Lanczos插值;但是效率和效果成反比,所以根据自己的情况酌情使用。
- 正常情况下,在使用之前dst图像的大小和类型都是不知道的,类型从src图像继承而来,大小也是从原图像根据参数计算出来。但是如果你事先已经指定好dst图像的大小,那么你可以通过下面这种方式来调用函数:
resize(src, dst, dst.size(), 0, 0, interpolation);
(2)通过矩阵变换进行缩放
import cv2 as cv import numpy as np # 图片缩放 img = cv.imread('../images/moon.jpg', flags=1) # flags=1读取为彩色,flags=0读取为灰度 h, w = img.shape[:2] mat_shift = np.float32([[0.5, 0, 0], [0, 0.5, 0]]) # 缩放矩阵 dst = cv.warpAffine(img, mat_shift, (int(w/2), int(h/2))) cv.imshow('img1', img) cv.imshow('img2', dst) cv.waitKey(0)