据圣经神话的记载,巴别塔(The Tower of Babel)是人类的第一个工程灾难。这个项目具备了所有的条件,但是仍然失败了。因为上帝混淆了人类的语言,从而使得他们无法再有效的沟通协作,进而导致了通天塔的崩溃和人类的混乱。
在我看来,像 Homography 这样的术语在时刻提醒我们,我们仍在为语言构成的沟通鸿沟而挣扎。Homography 就是一个很简单的概念却起了一个很唬人的名字。
考虑 同一个平面(比如书皮)的两张图片,红点表示同一个物理坐标点在两张图片上的各自位置。在 CV 术语中,我们称之为对应点。
Homography 就是将一张图像上的点映射到另一张图像上对应点的3x3变换矩阵.
A Homography is a transformation ( a 3×3 matrix ) that maps the points in one image to the corresponding points in the other image.
因为 Homography 是一个 3x3 的 矩阵,所以我们可以把它写成
对于图中的一对儿对应点,位于图一的 (x1, y1) 和 位于图二的 (x2, y2). H 把二者映射关系建立起来
对于所有的对应点,只要它们都位于同一个物理平面上,上述 Homography 就是成立的。换句话说,就是可以把图一中书皮上的所有点都映射到图二的书皮上,也就是看起来,图一中的书皮和图二中的书皮对齐了!如下图:
那么对于不在此平面上的点呢?这时再应用 Homography 就无法再对齐到对应点了。比如 上图中的 桌面,地面,橱柜面。对于这种图像中有多个平面的情况,我们就需要针对每一个平面使用其对应的Homography了。
在前面的例子中,我们知道,如果已知 两个图像的 Homography,那么我们可以把一张图像 warp 到 另外一张图像内去。但是,这里有一个很严重的bug:两张图像必须包含同一个平面,并且仅有这个平面上的点是准确对齐的。事实上,可以证明,如果你用相机拍摄一张任意风景的照片(不仅仅是一个平面)然后旋转相机再任意拍摄一张,这两张照片可以用一个 Homography 联系起来! 刚才拍摄的两张任意3D场景的图片可以用 Homography 联系起来,而两张图像会有一些共同的区域,这些区域又可以用来对齐和拼接,bingo,这样你就可以得到这两张图像的全景拼接图了。当然,这还是很粗糙的全景拼接,还需要很多精细的细节操作,但是基本原理是这样的,用Homography 把共同的部分对齐,然后巧妙的拼接,从而看不到拼缝。全景拼接,大功告成。
对于 H 矩阵,一般设 H22 为 1, 所以 H 有 8 个未知参数。至少需要8 个等式才能求解。而一组对应点可以提供 2 个等式,所以,至少需要 4 组对应点(任意三点不共线)来求得 H。 如果有更多组对应点,效果更佳。 OpenCV 可以鲁棒地计算出一个最好地拟合所有对应点的 Homography。通常,图像间的这些对应点通过 SIFT 或者 SURF 这样算法进行自动特征提取和匹配。当然,对于简单的demo,手动选取对应点就足够了。
OpenCV C++
//pts_src : 源图像点坐标
//pts_dst : 结果图像坐标
// 数据类型都是 vector.
// 需要至少4组对应点.
Mat h = findHomography(pts_src, pts_dst);
//im_src : 源图像
// im_dst : 结果图像
// h: 上一步计算得到的 Homography
// size : im_dst 的 大小(宽度,高度)
// 将 im_src 通过 h warp 到 im_dst 上去
warpPerspective(im_src, im_dst, h, size);
// warp points, not full image
vector<Point2f> dstPoints, srcPoints;
srcPoints.push_back(Point2f(1,1));
cv::perspectiveTransform(srcPoints,dstPoints,warpMatrix);
下面是一个例子
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main( int argc, char** argv)
{
// Read source image.
Mat im_src = imread("book2.jpg");
// Four corners of the book in source image
vector<Point2f> pts_src;
pts_src.push_back(Point2f(141, 131));
pts_src.push_back(Point2f(480, 159));
pts_src.push_back(Point2f(493, 630));
pts_src.push_back(Point2f(64, 601));
// Read destination image.
Mat im_dst = imread("book1.jpg");
// Four corners of the book in destination image.
vector<Point2f> pts_dst;
pts_dst.push_back(Point2f(318, 256));
pts_dst.push_back(Point2f(534, 372));
pts_dst.push_back(Point2f(316, 670));
pts_dst.push_back(Point2f(73, 473));
// Calculate Homography
Mat h = findHomography(pts_src, pts_dst);
// Output image
Mat im_out;
// Warp source image to destination based on homography
warpPerspective(im_src, im_out, h, im_dst.size());
// Display images
imshow("Source Image", im_src);
imshow("Destination Image", im_dst);
imshow("Warped Source Image", im_out);
waitKey(0);
}
在很多直播体育赛事中,广告是动态插入到直播视频流中去的,从而根据观众的个人喜好,地域习俗等展示个性化的广告。
以下是第一张上传到互联网的图片
[外链图片转存失败(img-A1fUf1PJ-1568973089922)(https://www.learnopencv.com/wp-content/uploads/2016/01/first-image.jpg)]
下图是 时代广场的 图片
[外链图片转存失败(img-Hgmsqxvc-1568973089922)(https://www.learnopencv.com/wp-content/uploads/2016/01/times-square-768x512.jpg)]
好了,我们想把 第一张图嵌入到 时代广场的 广告屏上去,4 步搞定
[外链图片转存失败(img-av2EKXBw-1568973089922)(https://www.learnopencv.com/wp-content/uploads/2016/01/virtual-billboard-768x512.jpg)]