简述单应矩阵
单应性矩阵是描述两幅图像之间的变换,或者是描述三维世界的平面和像平面之间的变换关系。举个例子,在相机标定中,世界坐标系是一个三维坐标系,像素坐标系是一个二维坐标系,我们定义标定板为世界坐标系Z轴为0的平面上,原点位于标定板的一角,便可以用单应矩阵H去描述两个平面的转换关系,而两个平面上对应点的坐标都是已知的,就能求解出H矩阵。
对于两张图片之间的转换可以理解为如下公式:
向量<u2,v2,1>和向量<u1,v1,1>分别是两张图片中的对应点,用齐次坐标表示,H是一个3x3的矩阵,若要求解出H矩阵,至少需要4组不同的对应点,详细讲解可参考从零开始学习「张氏相机标定法」(二)单应矩阵。OpenCV库已提供单应矩阵的计算,可以直接调用函数cv::findHomography。
输入矩阵srcPoints和dstPoints分别是原始平面和目标平面的点,这些都是二维的点,可以是Mat类型的坐标数据,CV_32FC2类型,或cv::Point2f的STL的vector对象。第三个输入变量method是计算单应性矩阵所使用的算法,cv::RANSAC(一致性随机抽样)和cv::LMEDS(最小平方中值)等。注意,为保证精确,我们只需要4个对应点,若提供更多的对应点,计算结果将会是最小二乘意义上的最优解。
实现简易P图
看完上面的三张图,会神奇地发现第二张图中人物的脸贴在第一张图上,便有了第三张图。那么这是如何实现的呢?
具体分为以为下三步:
1、通过鼠标点击选点
2、计算单应性矩阵和实现图像变换
3、画出掩膜和图像的融合
步骤一
为了方便表述,先将第一幅图称为obj,第二幅图称为scene。先将事件设置为鼠标按下左键,通过鼠标先后选取obj和scene两幅图像上选取ROI(感兴趣的区域,由4个点组成),并将点和图像共同储存在userdata定义的结构体中。
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),1,Scalar(0,0,255), 5, CV_AA);
imshow("Image", data->im);
if (data->points.size() < 4)
{
data->points.push_back(Point2f(x,y));
}
}
}
步骤二
结合步骤一获得4组对应点,调用计算单应矩阵的函数cv::findHomography和透视变换的函数cv::warpPerspective(利用单应矩阵实现图像变换)
Mat H = findHomography(data_of_obj.points,data_of_scene.points,CV_RANSAC);
warpPerspective(obj,result,H,scene.size());
步骤三
更据scene场景中的4个点,结合函数cv::fillpoly绘制出多边形,为copyTo函数提供掩膜,最后利用cv::copyTo实现融合。
{
myfillpoly(mask,data_of_scene);
scene.copyTo(result,mask);
完整代码
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
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),1,Scalar(0,0,255), 5, CV_AA);
imshow("Image", data->im);
if (data->points.size() < 4)
{
data->points.push_back(Point2f(x,y));
}
}
}
void myfillpoly(Mat& bimage,userdata &s) //绘制多边形,为copyTo函数提供掩膜。
{
Point rookPoints[1][4];
rookPoints[0][0] = s.points[0];
rookPoints[0][1] = s.points[1];
rookPoints[0][2] = s.points[2];
rookPoints[0][3] = s.points[3];
const Point* ppt[1]={rookPoints[0]};
int npt[]={4};
fillPoly(bimage,ppt,npt,1,Scalar(0,0,0),0);
}
int main()
{
Mat obj = imread("obj.jpg",1);
Mat scene = imread("scene.jpg",1);
//物体取点
Mat obj_temp = obj.clone();
userdata data_of_obj;
data_of_obj.im = obj_temp;
imshow("Image",obj_temp);
setMouseCallback("Image", mouseHandler, &data_of_obj);
waitKey(0);
//场景取点
Mat scene_temp = scene.clone();
userdata data_of_scene;
data_of_scene.im = scene_temp;
imshow("Image",scene_temp);
setMouseCallback("Image", mouseHandler, &data_of_scene);
waitKey(0);
//计算单应性矩阵
Mat H = findHomography(data_of_obj.points,data_of_scene.points,CV_RANSAC);
Mat result,mask;
mask = Mat(scene.size(),CV_8UC3,Scalar(255,255,255));
warpPerspective(obj,result,H,scene.size()); //透视变换
myfillpoly(mask,data_of_scene);
scene.copyTo(result,mask);
imshow("result",result);
imwrite("/home/dh/Matrix_H/build/image.jpg",result);
waitKey(0);
return 0;
}
视频演示:
见bilibiliOpenCV实现简易P图
同时欢迎大家关注萌新up