在张正友标定法的时候,可以使用单应矩阵来计算标定板平面和像素平面之间的变换关系,其本身包含了相机的内参和标定板与相机的外参矩阵。而在射影几何中,单应矩阵更多地用来表征两个平面之间的变换关系。对于标定时
令世界点的,并将中间部分协作M,则有
如果存在两个不同的相机,或者相同相机在不同位置派到同一个平面,则有
这表明,存在一组关系,可以实现两个像素平面的互相变换,具体的计算和标定时类似。
因为最后一行的1的缘故,H矩阵仍然只有8个自由度,所以只需要四组对应点即可计算出。
使用四组对应点即可实现
可以方便将普通视图转换为鸟瞰图
因为单应矩阵可以进行视角转换,则可以将不同角度拍摄的图像转换到相同的视角,从而实现图像的拼接
平面二维标记图案长用来做AR展示,根据marker不同视角下的图像可以方便地得到虚拟物体的位置姿态并进行显示
令
整理可得
整理成矩阵形式
所以只需要四组点即可求解出单应矩阵
但是再实际中,我们计算的关键点对会包含噪声,甚至出现误匹配的现象,所以只使用4组点来计算单应矩阵会出现很大的误差。因此一般会使用多于四组点来计算。另外直接法求解很难得到最优解,所以实际中会使用其他优化方法进行求解,比如SVD、LM等算法。
在opencv中有现成的函数可以进行调用
Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method=0, double ransacReprojThreshold=3, OutputArray mask=noArray() )
从函数中看,只需要输入对应的匹配点,指定具体计算方法即可输出结果。
输入:两张图片
输出:将图二拼接到图一
代码:virtual_billboard.cpp
#include
#include
#include
#include
#include
#include
#include
class ImageMosic{
public:
ImageMosic(cv::Mat img_src,cv::Mat img_target){
img_src_ = img_src.clone();
img_target_ = img_target.clone();
SetSrcPts();
SetTargetPts();
CalcHAndPerspective();
}
private:
struct UserData{
cv::Mat img;
std::vector pts;
};
void SetSrcPts()
{
cv::Size src_size = img_src_.size();
src_pts_.push_back(cv::Point2f(0, 0));
src_pts_.push_back(cv::Point2f(src_size.width-1, 0));
src_pts_.push_back(cv::Point2f(src_size.width-1, src_size.height-1));
src_pts_.push_back(cv::Point2f(0, src_size.height-1));
}
static void mouseHandler(int event, int x, int y, int flags,void* data_ptr)
{
if (event ==cv::EVENT_LBUTTONDOWN)
{
UserData *data = (UserData *)data_ptr;
cv::circle( data->img, cv::Point(x, y), 3, cv::Scalar(0, 255, 255), 5);
cv::imshow("Image_target", data->img);
if (data->pts.size() < 4)
{
data->pts.push_back(cv::Point2f(x,y));
}
}
}
void SetTargetPts(){
//show the image
cv::imshow("Image_target", img_target_);
std::cout << "Click on four corners of a billboard and then press ENTER" << std::endl;
//set the callback function for any mouse event
UserData user_data;
user_data.img = img_target_;
cv::setMouseCallback("Image_target", mouseHandler, &user_data);
cv::waitKey(0);
target_pts_ = user_data.pts;
}
void CalcHAndPerspective(){
assert(target_pts_.size() == 4);
cv::Mat H = cv::findHomography(src_pts_, target_pts_, 0); //计算单应矩阵
cv::warpPerspective(img_src_, img_src_perspective_, H, img_target_.size()); //透视变换
cv::Point pts_dst[4] = {target_pts_[0], target_pts_[1], target_pts_[2], target_pts_[3]};
cv::fillConvexPoly(img_target_, pts_dst, 4, cv::Scalar(0));
img_target_ = img_target_ + img_src_perspective_;
cv::imshow("Image_target", img_target_);
cv::waitKey(0);
}
private:
cv::Mat img_src_;
cv::Mat img_src_perspective_;
cv::Mat img_target_;
std::vector src_pts_;
std::vector target_pts_;
};
int main(){
cv::Mat img_src = cv::imread("../images/cvlife.jpg");
cv::Mat img_target = cv::imread("../images/ad.jpg");
ImageMosic img_mosaic(img_src, img_target);
return 0;
}
CMakelists.txt
cmake_minimum_required(VERSION 1.0)
project(virtual_billboard)
find_package(OpenCV)
add_executable(virtual-billboard src/virtual_billboard.cpp)
target_link_libraries(virtual-billboard ${OpenCV_LIBS})
操作步骤:在图片上从左上角顺时针选4个点,按enter结束
结果:
与本质矩阵类似,单应矩阵需要进行分解,才能得到旋转平移矩阵。分解的方法包含数值法[2][3]和解析法[4]。与本质矩阵类似,单应矩阵的分解也会返回4组旋转平移矩阵,并且同时可以解算出他们分别对应的场景点所在平面的法向量。如果已知成像的地图点的深度全为正值(即在相机前方),则可以排除两组。最后剩余两组解,需要更多的先验信息进行判断。通常我们可以通过假设已知场景平面的法向量来解决,如场景平面与相机平面平行,那么法向量的理论值为。
单应性在SLAM中有重要意义。当特征点共面或相机发生纯旋转时,基础矩阵自由度下降,出现退化。现实中数据总会包含一些噪声,这是继续使用八点法求解基础矩阵,其多余出来的自由度将会主要由噪声决定。为了避免退化现象造成影响,通常我们会同时估计基础矩阵F和单应矩阵H,选择重投影误差较小的那个作为最终的运动估计矩阵。
[1]单应性矩阵的理解及求解_机器视觉001的博客-CSDN博客_单应性矩阵
[2]Faugeras O D, Lustman F. Motion and structure from motion in a piecewise planar environment[J]. International Journal of Pattern Recognition and Artificial Intelligence, 1988, 2(03): 485-508.
[3]Zhang Z, Hanson A R. 3D reconstruction based on homography mapping[J]. Proc. ARPA96, 1996: 1007-1012.
[4]Malis E, Vargas M. Deeper understanding of the homography decomposition for vision-based control[D]. INRIA, 2007.