单应矩阵是视觉SLAM当中一个很重要的概念,它用于描述处于共同平面上的一些特征点在两张图像之间的变换关系。举个现实例子,我们去商店买东西,结账的时候柜台前的微信/支付宝收款二维码就相当于处于同一个平面上的特征点组成的平面,而张三打开手机端摄像机拍到的图片和李四手机拍摄到的二维码的图片之间的转换关系就可以用单应矩阵来描述。而且,两个人站在不同的角度用手机扫一扫二维码后,在手机端出现的二维码竟然是方方正正的二维码(和柜台前的是一样的),这也和单应矩阵变换有关系。
下面这张图片非常生动的展示了单应矩阵是怎么描述处于同一平面当中的特征点在两个图像当中的变换关系的,我们可以仔细体会一下。
这两天研究了下单应矩阵相关内容,本来想写篇笔记记录一下的,当看到下面这篇通俗易懂的单应矩阵的文章后,决定转过来:
单应矩阵的计算在opencv当中提供了调用函数findHomography来帮我们进行计算。
opencv当中计算单应矩阵的公式和描述,和视觉SLAM十四讲当中的描述是一致的,只不过十四讲当中将计算公式进一步整理了。
下面记录一下单应矩阵实现的虚拟广告牌案例,这个也是笔者上边学习的博文当中的作业题。通过这个案例,我们可以感受一下单应矩阵的物理意义。
原始的广告牌图像如下:
用于替换的图像如下:
替换后的结果如下:
代码实现如下:
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
struct userdata{
Mat im;
vector<Point2f> points;
};
void mouseHandler(int event, int x, int y, int flags, void* data_ptr)
{
//判断时间类型是不是左侧鼠标按下
if( event == EVENT_LBUTTONDOWN )
{
userdata *data = ((userdata *) data_ptr);
//画点
circle(data->im, Point(x,y),3,Scalar(0,255,255), 5, CV_AA);
imshow("Image", data->im);
//如果点数少于4个的时候就将新的点坐标存入
if (data->points.size() < 4)
{
data->points.push_back(Point2f(x,y));
}
}
}
int main( int argc, char** argv)
{
// Read in the image.
Mat im_src = imread("cvlife.png");
Size size = im_src.size();
//获取图像四周的四个顶点坐标.
vector<Point2f> pts_src;
pts_src.push_back(Point2f(0,0));
pts_src.push_back(Point2f(size.width - 1, 0));
pts_src.push_back(Point2f(size.width - 1, size.height -1));
pts_src.push_back(Point2f(0, size.height - 1 ));
// Destination image
Mat im_dst = imread("ad.jpg");
// Set data for mouse handler
Mat im_temp = im_dst.clone();
userdata data;
data.im = im_temp;
//show the image
imshow("Image", im_temp);
cout << "在图片的广告牌中点击选择要替换的图片的四个顶点" << endl;
//set the callback function for any mouse event
setMouseCallback("Image", mouseHandler, &data);
waitKey(0);
//计算原图四个角点和目标图区域对应角点的 Homography
Mat Homography = findHomography(pts_src, data.points);
//用单应矩阵对原图进行透视变换
/**
**第一个参数是原图像,第二个图像是目标图像,第三个参数是变换矩阵,第四个参数是输出图像的大小
*/
warpPerspective(im_src, im_temp, Homography, im_temp.size());
//获取鼠标点击的图像中的四个顶点的坐标
Point pts_dst[4];
for(int i=0;i<4;i++)
{
pts_dst[i]=data.points[i];
}
//将目标图像的四个顶点之内的区域填充为黑色
fillConvexPoly(im_dst, pts_dst, 4, Scalar(0), CV_AA);
//将原图和目标图进行叠加
im_dst = im_dst + im_temp;
// Display image.
imshow("Image", im_dst);
imwrite("result.jpg",im_dst);
waitKey(0);
return 0;
}
这里还要重点关注一下opencv提供的另一个透视变换函数warpPerspective,看看它是怎样使用单应矩阵来进行图像之间的透视变换的。
另外,上边的代码可以直接些cmake工程来进行编译或者使用g++来编译,笔者直接使用g++进行编译的,编译命令如下:
g++ -std=c++11 `pkg-config opencv --cflags` test.cpp -o test `pkg-config opencv --libs`
代码放在了test.cpp当中,编译生成的可执行文件为test,之后执行test,并按照提示在广告牌中用鼠标选择要替换的广告牌的四个顶点即可。