加入极市专业CV交流群,与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度 等名校名企视觉开发者互动交流!
同时提供每月大咖直播分享、真实项目需求对接、干货资讯汇总,行业技术交流。关注 极市平台 公众号 ,回复 加群,立刻申请入群~
1. 前向映射
图像的几何变换就是建立一种源图像像素与变换后的图像像素之间的映射关系。也正是通过这种映射关系可以知道原图像任意像素点变换后的坐标,或者是变换后的图像在原图像的坐标位置等。
浮点数坐标,如(1,1)映射为(0.5,0.5),显然这是一个无效的坐标,这时我们需要使用插值算法进行进一步处理。
可以看到,旋转三十度后,输出图像两个红色的点被映射到同一个坐标,而没有点被映射到绿色问号处,这就造成了间隙和重叠,导致出现蜂窝状空洞。
为了克服前向映射的这些不足,因此引进了“后向映射”,它的数学表达式为:
在使用过程中,如果在一些不改变图像大小的几何变换中,向前映射还是十分有效的,向后映射主要运用在图像的旋转的缩放中,因为这些几何变换都会改变图像的大小。
先看第一个问题,变换的形式。在本篇文章里图像的几何变换全部都采用统一的矩阵表示法,形式如下:
这就是向前映射的矩阵表示法,其中 , 表示输出图像像素的坐标, , 表示输入图像像素的坐标,同理,向后映射的矩阵表示为:
可以证明,向后映射的矩阵的表示正好是向前映射的逆变换。
下面举几个例子。原图如下:
2. 放大为原来的两倍
3. 顺时针旋转45度
4. 水平偏移2个单位
我们都知道,图像坐标的原点在图像左上角,水平向右为 X 轴,垂直向下为 Y 轴。数学课本中常见的坐标系是以图像中心为原点,水平向右为 X 轴,垂直向上为 Y 轴,称为笛卡尔坐标系。看下图:
因此,对于旋转和偏移,就需要3步(3次变换):
将旋转后的图像的笛卡尔坐标转回图像坐标。
那么,图像坐标系与笛卡尔坐标系转换关系是什么呢?先看下图:
在图像中我们的坐标系通常是AB和AC方向的,原点为A,而笛卡尔直角坐标系是DE和DF方向的,原点为D。
逆变换为:
于是,根据前面说的3个步骤(3次变换),旋转(顺时针旋转)的变换形式就为,3次变换就有3个矩阵:
平台:WIN10
函数原型(c++)
OpenCV仿射变换相关的函数一般涉及到warpAffine和getRotationMatrix2D这两个:
OpenCV函数getRotationMatrix2D 来获得旋转矩阵。
1. warpAffined函数详解
void boxFilter( InputArray src, OutputArray dst,
int ddepth,
Size ksize,
Point anchor = Point(-1,-1),
bool normalize = true,
int borderType = BORDER_DEFAULT );
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,需和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的M,2×3的变换矩阵。
第四个参数,Size类型的dsize,表示输出图像的尺寸。
第五个参数,int类型的flags,插值方法的标识符。此参数有默认值INTER_LINEAR(线性插值)。
第六个参数,int类型的borderMode,边界像素模式,默认值为BORDER_CONSTANT。
第七个参数,const Scalar&类型的borderValue,在恒定的边界情况下取的值,默认值为Scalar(),即0。
2. getRotationMatrix2D函数详解
C++: Mat getRotationMatrix2D(Point2f center, double angle, double scale)
参数:
实现示例(c++)
1、旋转
Mat src = imread("../image/source3.jpg");//读取原图像
cv::Mat dst;
//旋转角度
double angle = 45;
cv::Size src_sz = src.size();
cv::Size dst_sz(src_sz.height, src_sz.width);
int len = std::max(src.cols, src.rows);
//指定旋转中心(图像中点)
cv::Point2f center(len / 2., len / 2.);
//获取旋转矩阵(2x3矩阵)
cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, 1.0);
//根据旋转矩阵进行仿射变换
cv::warpAffine(src, dst, rot_mat, dst_sz);
//显示旋转效果
cv::imshow("image", src);
cv::imshow("result", dst);
cv::waitKey(0);
return 0;
但是这么写有一个问题,就是旋转后图像会被截断,如下图所示:(左边为原图,右边为顺时针旋转45度后的图)
可以看到,图像的一部分被截断了,其原因是:
2. OpenCv将坐标转成笛卡尔坐标系后没转回图像坐标系。
其中比较难理解的是图像大小的变换,下面举一个例子大家就能明白了:
代码如下:
Mat src = imread("../image/source3.jpg");//读取原图像
Mat dst;
// 旋转角度
double angle = 45.0;
// 计算旋转后输出图形的尺寸
int rotated_width = ceil(src.rows * fabs(sin(angle * CV_PI / 180)) + src.cols * fabs(cos(angle * CV_PI / 180)));
int rotated_height = ceil(src.cols * fabs(sin(angle * CV_PI / 180)) + src.rows * fabs(cos(angle * CV_PI / 180)));
// 计算仿射变换矩阵
Point2f center(src.cols / 2, src.rows / 2);
Mat rotate_matrix = getRotationMatrix2D(center, angle, 1.0);
// 防止切边,对平移矩阵B进行修改
rotate_matrix.at<double>(0, 2) += (rotated_width - src.cols) / 2;
rotate_matrix.at<double>(1, 2) += (rotated_height - src.rows) / 2;
// 应用仿射变换
warpAffine(src, dst, rotate_matrix, Size(rotated_width, rotated_height), INTER_LINEAR, 0, Scalar(255, 255, 255));
imshow("result", dst);
cv::imwrite("right.jpg", dst);
waitKey();
return 0;
结果:
2、平移
Mat src = imread("../image/source2.jpg");//读取原图像
cv::Mat dst;
cv::Size dst_sz = src.size();
//定义平移矩阵
cv::Mat t_mat =cv::Mat::zeros(2, 3, CV_32FC1);
t_mat.at<float>(0, 0) = 1;
t_mat.at<float>(0, 2) = 300; //水平平移量
t_mat.at<float>(1, 1) = 1;
t_mat.at<float>(1, 2) = 300; //竖直平移量
//根据平移矩阵进行仿射变换
cv::warpAffine(src, dst, t_mat, dst_sz);
//显示平移效果
cv::imshow("image", src);
cv::imshow("result", dst);
cv::waitKey(0);
return 0;
3. 仿射变化
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src = imread("../image/source2.jpg");//读取原图像
//分别在原图像和目标图像上定义三个点
Point2f srcTri[3];
Point2f dstTri[3];
srcTri[0] = Point2f(0, 0);
srcTri[1] = Point2f(src.cols - 1, 0);
srcTri[2] = Point2f(0, src.rows - 1);
dstTri[0] = Point2f(src.cols * 0.0, src.rows * 0.33);
dstTri[1] = Point2f(src.cols * 0.85, src.rows * 0.25);
dstTri[2] = Point2f(src.cols * 0.15, src.rows * 0.7);
Mat dst;//目标图像
//设置目标图像的大小和类型与原图像一致,初始像素值都为0
dst = Mat::zeros(src.rows, src.cols, src.type());
//计算仿射变换矩阵
Mat trans_mat = getAffineTransform(srcTri, dstTri);
//对原图像应用上面求得的仿射变换
warpAffine(src, dst, trans_mat, src.size());
//显示结果
imshow("origin_image", src);
imshow("dst_image", dst);
//储存图像
imwrite("dst1.jpg", dst);
waitKey(0);
return 0;
}
添加极市小助手微信(ID : cv-mart),备注:研究方向-姓名-学校/公司-城市(如:AI移动应用-小极-北大-深圳),即可申请加入AI移动应用极市技术交流群,更有每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、行业技术交流,一起来让思想之光照的更远吧~
△长按添加极市小助手
△长按关注极市平台,获取最新CV干货
觉得有用麻烦给个在看啦~