8:OpenCV—仿射变换和坐标映射
OpenCV的仿射变换
仿射变换(Affine Transformation or Affine Map)又称仿射映射。在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间的过程。保持二维图像的“平直性”和“平行性”
一个任意的仿射变换都能表示为乘以一个矩阵(线性变换)再加上一个向量(平移)的形式常用的三种常见的变换形式
- 旋转,rotation(线性变换)
- 平移,translation向量加
- 缩放,scale线性变换
OpenCV的仿射变换相关函数一般涉及到warpAffine和getRotationMatrix2D
- Opencv函数warpAffine来实现一些简单的重映射
- Opencv函数getRotationMatrix2D来获取旋转矩阵
API说明
(1)getAffineTransform //三点法 Mat M1=getAffineTransform(const Point2f* src, const Point2f* dst)//参数const Point2f* src:原图的三个固定顶点(左上角,左下角,右上角)//参数const Point2f* dst:目标图像的三个固定顶点//返回值:Mat型变换矩阵,可直接用于warpAffine()函数//注意,顶点数组长度超过3个,则会自动以前3个为变换顶点;数组可用Point2f[]或Point2f*表示(2)getRotationMatrix2D //直接指定比例和角度 Mat M2=getRotationMatrix2D (CvPoint2D32f center,double angle,double scale)//参数CvPoint2D32f center,表示源图像旋转中心//参数double angle,旋转角度,正值表示逆时针旋转//参数double scale,缩放系数void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())//参数InputArray src:输入变换前图像//参数OutputArray dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸//参数InputArray M:变换矩阵,用另一个函数getAffineTransform()计算//参数Size dsize:设置输出图像大小//参数int flags = INTER_LINEAR:设置插值方式,默认方式为线性插值(另一种WARP_FILL_OUTLIERS)//参数int borderMode=BORDER_CONSTANT:边界像素模式,默认值BORDER_CONSTANT//参数const Scalar& borderValue=Scalar(),在恒定边界情况下取的值,默认值为Scalar(),即0
使用OpenCV实现仿射变换步骤
1、获取仿射变换矩阵
- 三点法 getAffineTransform
- 直接指定比例核角度 getRotationMatrix2D
2、进行仿射变换处理
- warpAffine(src, dst, homat2D. size)
OpenCV的坐标系
行列与坐标系对应关系
行rows:Y (height)
列cols:X (width)
注意!注意!注意!
在Mat类型变量访问时下标是反着写的,即:按照(y, x)的关系形式访问,下面通过代码展示来说明这一点
#include <opencv2/opencv.hpp> #include <iostream>using namespace cv; using namespace std;int main() {// 创建一个3X4矩阵cv::Mat matRect = Mat::eye(3,4, CV_8UC1);// 打印矩阵std::cout << matRect <<endl;// 打印矩阵的行列值cout << "rows :" << matRect.rows << endl;cout << "cols :" << matRect.cols << endl;// 访问数据// matRect.at<uchar>(y,x) => y(row), x(col)matRect.at<uchar>(0,2) = 2;matRect.at<uchar>(2,0) = 4;// 设置像素点后结果std::cout << matRect <<endl;return 0; }
仿射变换案例
点仿射变换案例
#include <iostream> #include <opencv2/opencv.hpp> #include <QDebug>using namespace cv; using namespace std;int main() {// 读取图像Mat srcImg = imread("D:/Gerry/project/opencvproj/singleandslot/OpenCV-2/ImageAndVideoHandle/resources/Car.jpg");// 定义六个窗口的名称string srcTitle = "原始图像";// 创建1个窗口namedWindow(srcTitle, WINDOW_NORMAL);// 显示原图imshow(srcTitle, srcImg);// 定义原图的三个点Point2f srcPoints[3];// 定义映射后的三个坐标点Point2f dstPoints[3];// 采用三角法srcPoints[0] = Point2f(0, 0);srcPoints[1] = Point2f(0, srcImg.rows);srcPoints[2] = Point2f(srcImg.cols, 0);// 映射后的三个点dstPoints[0] = Point2f(0, srcImg.rows * 0.2);dstPoints[1] = Point2f(srcImg.cols * 0.25, srcImg.rows * 0.75);dstPoints[2] = Point2f(srcImg.cols * 0.75, srcImg.rows * 0.25);// 获取仿射变换矩阵Mat homat2D = getAffineTransform(srcPoints, dstPoints);// 进行图像仿射变换操作Mat dstImg;warpAffine(srcImg, dstImg, homat2D, srcImg.size());// 创建1个窗口namedWindow("dstImgWd", WINDOW_NORMAL);// 显示原图imshow("dstImgWd", dstImg);waitKey(0);return 0; }
角度仿射案例
#include <iostream> #include <opencv2/opencv.hpp> #include <QDebug>using namespace cv; using namespace std;int main() {// 读取图像Mat srcImg = imread("D:/Gerry/project/opencvproj/singleandslot/OpenCV-2/ImageAndVideoHandle/resources/Car.jpg");// 定窗口的名称string srcTitle = "原始图像";// 创建1个窗口namedWindow(srcTitle, WINDOW_NORMAL);// 显示原图imshow(srcTitle, srcImg);int nCols = srcImg.cols;int nRows = srcImg.rows;//设置仿射变换参数Point2f centerPoint = Point2f(nCols / 2, nRows / 2);double angle = -50;double scale = 0.7;//获取仿射变换矩阵Mat homat2D = getRotationMatrix2D(centerPoint, angle, scale);// 进行图像仿射变换操作Mat dstImg;warpAffine(srcImg, dstImg, homat2D, srcImg.size());// 创建1个窗口namedWindow("dstImgWd", WINDOW_NORMAL);// 显示原图imshow("dstImgWd", dstImg);waitKey(0);return 0; }
仿射镜像及翻转案例
#include <iostream> #include <opencv2/opencv.hpp> #include <QDebug>using namespace cv; using namespace std;int main() {// 读取图像Mat srcImg = imread("D:/Gerry/project/opencvproj/singleandslot/OpenCV-2/ImageAndVideoHandle/resources/Car.jpg");// 定窗口的名称string srcTitle = "原始图像";// 创建1个窗口namedWindow(srcTitle, WINDOW_NORMAL);// 显示原图imshow(srcTitle, srcImg);int nCols = srcImg.cols;int nRows = srcImg.rows;//仿射变换—翻转、镜像Point2f srcPoints2[3];Point2f dstPoints2[3];srcPoints2[0] = Point2i(0, 0);srcPoints2[1] = Point2i(0, srcImg.rows);srcPoints2[2] = Point2i(srcImg.cols, 0);dstPoints2[0] = Point2i(srcImg.cols, 0);dstPoints2[1] = Point2i(srcImg.cols, srcImg.rows);dstPoints2[2] = Point2i(0, 0);Mat dst_warpFlip;Mat M4 = getAffineTransform(srcPoints2, dstPoints2);warpAffine(srcImg, dst_warpFlip, M4, Size(src.cols, src.rows));//flip(src, dst_warpFlip, 1);// flipCode:= 0 图像向下翻转// -1 上下左右翻转 0 沿Y轴方向翻转 1 沿X轴方向翻转//> 0 图像向右翻转//< 0 图像同时向下向右翻转// 创建1个窗口namedWindow("dstImgWd", WINDOW_NORMAL);// 显示原图imshow("dstImgWd", dst_warpFlip);waitKey(0);return 0; }
透视变换
透视变换的API和仿射变换的API很像,原理也相同,不同的只是由之前的三点变为四点法求透视变换矩阵,变换矩阵也由2X3变为3X3;
透视变换API
(1)MatgetPerspectiveTransform(const Point2f* src, const Point2f* dst)//参数const Point2f* src:原图的四个固定顶点//参数const Point2f* dst:目标图像的四个固定顶点//返回值:Mat型变换矩阵,可直接用于warpAffine()函数//注意,顶点数组长度超4个,则会自动以前4个为变换顶点;数组可用Point2f[]或Point2f*表示//注意:透视变换的点选取变为4个(2)C++ void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())//参数InputArray src:输入变换前图像//参数OutputArray dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸//参数InputArray M:变换矩阵,用另一个函数getAffineTransform()计算//参数Size dsize:设置输出图像大小//参数int flags = INTER_LINEAR:设置插值方式,默认方式为线性插值(另一种WARP_FILL_OUTLIERS)
透视变换案例
//透视变换 #include<opencv2/opencv.hpp> #include<iostream> #include<math.h>using namespace cv; using namespace std;int main() {Mat src, dst;src = imread("D:/Gerry/project/opencvproj/singleandslot/OpenCV-2/ImageAndVideoHandle/resources/Car.jpg");namedWindow("original image", CV_WINDOW_AUTOSIZE);imshow("original image", src);Mat dst_warp, dst_warpRotateScale, dst_warpTransformation, dst_warpFlip;Point2f srcPoints[4];//原图中的四点 ,一个包含三维点(x,y)的数组,其中x、y是浮点型数Point2f dstPoints[4];//目标图中的四点srcPoints[0] = Point2f(0, 0);srcPoints[1] = Point2f(0, src.rows);srcPoints[2] = Point2f(src.cols, 0);srcPoints[3] = Point2f(src.cols, src.rows);//映射后的四个坐标值dstPoints[0] = Point2f(src.cols*0.1, src.rows*0.1);dstPoints[1] = Point2f(0, src.rows);dstPoints[2] = Point2f(src.cols, 0);dstPoints[3] = Point2f(src.cols*0.7, src.rows*0.8);Mat M1 = getPerspectiveTransform(srcPoints, dstPoints);//由四个点对计算透视变换矩阵warpPerspective(src, dst_warp, M1, src.size());//仿射变换namedWindow("Perspective image", CV_WINDOW_AUTOSIZE);imshow("Perspective image", dst_warp);waitKey(0);return 0; }
OpenCV坐标映射
图像的坐标映射是通过原图像与目标图像之间建立一种映射关系,这种映射关系有两种:
一种是计算原图像任意像素在映射后图像后的坐标位置
二是计算变换后图像任意像素反应设在原图像的坐标位置。由原图像映射到目标图像称为正映射,相反地,由目标图像通过映射关系得到原图像称为反映射。
由于正映射常常会映射不完全以及出现映射重复现象,一般在图像处理的过程中采取反映射的方式来保证输出目标图像的每个像素都可以通过映射关系在源图像中找到唯一的对应像素。
OpenCV中提供重映射相关操作,重映射是指把一个图像中一个位置的像素通过映射关系转换到零一图像的指定位置。对于输入源图像f(x,y),目标图像为g(x,y),映射关系为T,则满足 g(x,y)=T(f(x,y))
需要注意的是通过映射关系T实现得到的目标图像可能存在目标图像像素值是非整数的情况,一般可以考虑插值或向上取整
void remap(InputArray src,OutputArray dst,InputArray map1,InputArray map2,int interpolation,int borderMode=BORDER_CONSTANT,const Scallar& borderValue=Scalar())
图像重映射操作。map1表示(x,y)点的坐标或x坐标,可以是CV_16SC2,CV_32FC1,CV_32FC2类型;map2表示y坐标,可以是CV_16UC1,CV_32FC1类型,如果map1为(x,y),map2则可以选择不用;interpolation表示插值方法;borderMode表示边界插值类型;borderValue表示插值数值。
坐标变换案例(倒转图像)
#include<opencv2/opencv.hpp> #include<iostream> using namespace std; using namespace cv;int main() {Mat src = imread("D:/Gerry/project/opencvproj/singleandslot/OpenCV-2/ImageAndVideoHandle/resources/Car.jpg");if (!src.data){return -1;}//输出矩阵定义Mat result(src.size(), src.type());//x方向与y方向矩阵Mat xMap(src.size(), CV_32FC1);Mat yMap(src.size(), CV_32FC1);//取图像的宽和高int rows = src.rows;int cols = src.cols;//图像遍历for (int j = 0; j < rows; j++){for (int i = 0; i < cols; i++){//x与y均翻转xMap.at<float>(j, i) = cols - i;yMap.at<float>(j, i) = rows - j;}}//重映射操作remap(src, result, xMap, yMap, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));//输出结果imshow("src", src);imshow("result", result);waitKey();return 0; }