參考博客:
https://blog.csdn.net/tina_ttl/article/details/52749542
參考:
《計算機視覺中的多視幾何》 ·第八章·對極幾何和基本矩陣
該作者的幾篇好文章:(鏈接可點擊)
參考:
1.美國北卡羅來納大學教堂山分校(UNC)的Visual 3D Modeling from Images的課程notes,詳細內容見該課程目錄頁,有一些關於攝影幾何基本知識、攝像機模型、多視幾何等的基本概念的介紹,淺顯易懂,特別是配了很多特別棒的圖,易於理解!
2.University of California 的計算機視覺課程CSE 252B: Computer Vision II
3.多視幾何教程 Richard Hartley和Andrew Zisserman的多視幾何的MATLAB代碼,只有書中部分內容對應的代碼
在上上一次的文章結尾,我們說了F矩陣還有一種剔除的表達形式
關於opencv2.4.10-3.3.1左右版本的特徵點剔除與顯示問題
這裏我們完成它
先介紹一下:
1. 什麼是對極幾何·粗略概念
提到對極幾何,一定是對二幅圖像而言,對極幾何實際上是“兩幅圖像之間的對極幾何”,它是圖像平面與以基線爲軸的平面束的交的幾何(這裏的基線是指連接攝像機中心的直線),以下圖爲例:對極幾何描述的是左右兩幅圖像(點x和x’對應的圖像)與以CC’爲軸的平面束的交的幾何!
直線CC’爲基線,以該基線爲軸存在一個平面束,該平面束與兩幅圖像平面相交,下圖給出了該平面束的直觀形象,可以看到,該平面束中不同平面與兩幅圖像相交於不同直線;
上圖中的灰色平面π,只是過基線的平面束中的一個平面(當然,該平面纔是平面束中最重要的、也是我們要研究的平面);
2. 對極幾何相關的一個重要約束·5點共面約束
仍以上面貼出的圖像爲例,此處重複貼出,空間點X在兩幅圖像中的像分別爲x和x’,這兩個投影點之間存在什麼關係呢?觀察下圖
- 點x、x’與攝像機中心C和C’是共面的,並且與空間點X也是空面的,這5個點共面於平面π!這是一個最本質的約束,即5個點決定了一個平面π
- 由該約束,可以推導出一個重要性質:由圖像點x和x’反投影的射線共面,並且,在平面π上,在搜索點對應中,該性質非常重要
3. 對極幾何的幾個相關概念
對極平面束(epipolar pencil):以基線爲軸的平面束;下圖給出了包含兩個平面的對極平面束
對極平面(epipolar plane):任何包含基線的平面都稱爲對極平面,或者說是對極平面束中的平面;例如,下圖中的平面π就是一個對極平面
對極點(epipole):攝像機的基線與每幅圖像的交點;即上圖中的點e和e’
對極線(epipolar line):對極平面與圖像的交線;例如,上圖中的直線l和l’
4. 對應點的約束
現在假設只知道圖像點x,那麼,它的對應點x’如何約束呢?
根據前面的討論,點x和x’一定位於平面π上,而平面π可以利用基線CC’和圖像點x的反投影射線確定
點x’又是右側圖像平面上的點,所以,點x’一定位於平面ππ與右側圖像平面的交線l’上
前面提到,直線l’爲點x的對極線,也就是說,點x的對應點x’一定位於它的對極線上!
重點來了,點x的對應點x’一定位於它的對極線上!這將是我們接下來工作的意義:
所謂極線約束就是說世界座標系下的同一個點在兩幅圖像上的映射,已知左圖映射點p1,那麼右圖映射點p2一定在相對於p1的極線上,這樣可以減少待匹配的點數量。
對於極線約束方程可以由以下來表示:
三維向量x和x'存放相關點,F爲一個3*3且秩爲2的基礎矩陣,那麼:
這裏我們引入opencv的函數computeCorrespondEpilines
如果已知F矩陣,左圖特徵點,作用是獲取圖像1中的二維特徵點 在圖像2中對應的外極線
或者如果已知F矩陣,右圖特徵點,獲取圖像2中的二維特徵點 在圖像1中對應的外極線
使用方法:
cv::computeCorrespondEpilines(cv::Mat(queryInliers), 1, F, lines1);
cv::computeCorrespondEpilines(cv::Mat(sceneInliers), 2, F, lines1);
這裏F不用轉置,因爲源代碼:
關於這個函數的計算公式:
內部實現看源代碼:
cv::computeCorrespondEpilines(cv::Mat(sceneInliers), 2, F, lines1);
最後求出的線是三個float,即a,b,c
這裏我們插個話題,很多論文也說了這麼一個概念:
假設兩條線都求出來了,對於兩條直線,以連續點的方式存儲:I和I‘分別在左右兩幅圖像上,若他們倆有對應關係,那麼認爲他們兩條直線之間的點依次的存在對應關係,這種好像是極線匹配問題,假設初始的匹配點是絕對正確的,通過F矩陣,求出互相對應的極線,通過遍歷在對應極線上的點,去尋找匹配點
代碼:
代碼中有
1 兩種方式,把把Point2f 轉換爲KeyPoint 的方法
2 極線約束剔除算法 第二種實現,真正意義上的
等下,解釋一個知識點:
求解Ax=b時候b不爲零向量時候用cvsolve
在Ax=0時候是不能用cvsolve來接函數的,但是可以利用一下函數SVD::solveZ來求解。
A:
vec:
雖然上面我們沒用到,但是知道多一點總是好的,上面是方程組的求解
圖示效果:
利用若干初始匹配點對估計影像之間的基本矩陣F和單應矩陣H,從而實現基於對極幾何和單應映射的雙重約束(如上圖),可同時提高匹配的速度和精度
#include <iostream>
#include <opencv2/opencv.hpp>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <opencv2/xfeatures2d.hpp>
#define M_PI 3.14159265358979323846
using namespace cv;
using namespace std;
cv::Mat UpDownDrawInlier(const cv::Mat &queryImage, const cv::Mat &objectImage,
const vector<cv::Point2f> &queryCoord, const vector<cv::Point2f> &objectCoord)
{
Size sz = Size(queryImage.size().width,
queryImage.size().height + objectImage.size().height);
Mat matchingImage = Mat::zeros(sz, CV_8UC3);
// 設置matchingImage的感興趣區域大小並賦予原圖
Mat roi1 = Mat(matchingImage, Rect(0, 0, queryImage.size().width, objectImage.size().height));
queryImage.copyTo(roi1);
Mat roi2 = Mat(matchingImage, Rect(0, queryImage.size().height, objectImage.size().width, objectImage.size().height));
objectImage.copyTo(roi2);
//畫出點
for (int i = 0; i < (int)queryCoord.size(); ++i) {
Point2f pt1 = queryCoord[i];
Point2f pt2 = objectCoord[i];
Point2f from = pt1;
Point2f to = Point(pt2.x, queryImage.size().height + pt2.y);
line(matchingImage, from, to, Scalar(0, 255, 255));
}
return matchingImage;
}
int main(int argc, const char * argv[]) {
string imgFileName = "1.JPG";
Mat queryImage;
queryImage = imread(imgFileName);
string objFileName = "2.JPG";
Mat objectImage;
objectImage = imread(objFileName);
resize(queryImage, queryImage, Size(640, 480));
resize(objectImage, objectImage, Size(640, 480));
//imresize(queryImage, 480);
//imresize(objectImage, 480);
clock_t start, finish;
start = clock();
//檢測SIFT特徵點,方便後續做對比
Ptr<xfeatures2d::SIFT>feature = xfeatures2d::SIFT::create(50);
vector<KeyPoint> qKeypoints;
feature->detect(queryImage, qKeypoints);
Mat qDescriptor;
feature->compute(queryImage, qKeypoints, qDescriptor);
vector<KeyPoint> objKeypoints;
feature->detect(objectImage, objKeypoints);
Mat objDesriptor;
feature->compute(objectImage, objKeypoints, objDesriptor);
FlannBasedMatcher matcher;
vector< DMatch > matches1;
matcher.match(qDescriptor, objDesriptor, matches1);
//點轉換
vector<cv::Point2f> queryCoord;
vector<cv::Point2f> objectCoord;
for (int i = 0; i < matches1.size(); i++){
queryCoord.push_back((qKeypoints[matches1[i].queryIdx]).pt);
objectCoord.push_back((objKeypoints[matches1[i].trainIdx]).pt);
}
// 計算homography矩陣
vector<cv::Point2f> queryInliers;
vector<cv::Point2f> sceneInliers;
Mat mask;
double minVal, maxVal;
cv::minMaxIdx(queryCoord, &minVal, &maxVal);
Mat F = findFundamentalMat(queryCoord, objectCoord, FM_RANSAC, 0.006 * maxVal, 0.99, mask); //threshold from [Snavely07 4.1]
// N. Snavely, S. Seitz, R. Szeliski, “Modeling the World from Internet Photo Collections, ”Int’l J.of Computer Vision, Vol. 80, No. 2, pp.189–210, 2008.
int inliers_cnt = 0, outliers_cnt = 0;
for (int j = 0; j < mask.rows; j++){
if (mask.at<uchar>(j) == 1){
queryInliers.push_back(queryCoord[j]);
sceneInliers.push_back(objectCoord[j]);
inliers_cnt++;
}
else {
outliers_cnt++;
}
}
outliers_cnt = matches1.size() - inliers_cnt;
Mat result = UpDownDrawInlier(queryImage, objectImage, queryInliers, sceneInliers);
cout << "最近鄰匹配特徵點數 " << matches1.size() << endl;
cout << "result內點數目 " << queryInliers.size() << endl;
cout << "外點數目 " << outliers_cnt << endl;
// ------------ 一種把Point2f 轉換爲 KeyPoint 的方法
cv::Mat imagekeyPt1; // 左圖
cv::Mat imagekeyPt2; // 右圖
vector<KeyPoint> NewKeyP1;
vector<KeyPoint> NewKeyP2;
for (int i = 0; i < queryInliers.size(); i++){
KeyPoint key;
key.pt.x = queryInliers[i].x;
key.pt.y = queryInliers[i].y;
NewKeyP1.push_back(key);
key.pt.x = sceneInliers[i].x;
key.pt.y = sceneInliers[i].y;
NewKeyP2.push_back(key);
}
cv::drawKeypoints(queryImage, NewKeyP1, imagekeyPt1, cv::Scalar(255, 0, 0), cv::DrawMatchesFlags::DEFAULT);
cv::drawKeypoints(objectImage, NewKeyP2, imagekeyPt2, cv::Scalar(255, 0, 0), cv::DrawMatchesFlags::DEFAULT);
// ------------ 第二種把Point2f 轉換爲 KeyPoint 的方法
vector<KeyPoint> key1(queryInliers.size());
vector<KeyPoint> key2(sceneInliers.size());
KeyPoint::convert(queryInliers, key1);
KeyPoint::convert(sceneInliers, key2);
vector<DMatch> m_InlierMatches;
m_InlierMatches.resize(queryInliers.size());
int InlinerCount = 0;
for (int i = 0; i<mask.rows; i++)
{
if (mask.at<uchar>(i) == 1)
{
m_InlierMatches[InlinerCount].queryIdx = InlinerCount;
m_InlierMatches[InlinerCount].trainIdx = InlinerCount;
InlinerCount++;
}
}
Mat img_matches1;
drawMatches(queryImage, key1, objectImage, key2, m_InlierMatches, img_matches1);
imshow("matches1", img_matches1);
//-------------------- F 極線約束
//-------------------- F 極線約束
//-------------------- F 極線約束
//-------------------- F 極線約束
//-------------------- F 極線約束
//-------------------- F 極線約束
std::vector<cv::Vec3f> lines1; //存儲外極線
cv::computeCorrespondEpilines(cv::Mat(queryInliers), 1, F, lines1);//獲取圖像1中的二維特徵點 在圖像2中對應的外極線
vector<cv::Point2f> srcInliers;
vector<cv::Point2f> dstInliers;
int num = 0;
int num_count = 0;
for (std::vector<cv::Vec3f>::const_iterator it1 = lines1.begin();
it1 != lines1.end(); ++it1)
{
float a = (*it1)[0];// ax+by+c=0
float b = (*it1)[1];
float c = (*it1)[2];
float x = sceneInliers[num].x;
float y = sceneInliers[num].y;
float z = a*x + b*y + c;
float zz = abs(z);
if (z<=2)
{
srcInliers.push_back(queryInliers[num]);
dstInliers.push_back(sceneInliers[num]);
num_count++;
}
num++;
}
Mat result1 = UpDownDrawInlier(queryImage, objectImage, srcInliers, dstInliers);
cout << "result1內點數目 " << srcInliers.size() << endl;
imshow("result1", result1);
finish = clock();
double sift_time = (double)(finish - start) / CLOCKS_PER_SEC;
cout << setprecision(10) << fixed << "sift_time " << sift_time << endl;
//double inlier_ratio = (double)good_matches.size() / matches1.size();
//cout << "匹配率 " << inlier_ratio << endl;
imshow("matchingImage", result);
waitKey();
return 0;
}
或者
把x帶入,求出y*,然後abs(y-y*)
兩種方式效果一樣
初始匹配點集中的內點(inliers)數量和分佈對F和H的估計精度具有重要影響。如初始匹配點集中的外點(outliers)數量佔優時,隨機採樣一致性(RANdom SAmple Consensus ,RANSAC)算法可能失效或估計偏差較大
存在一個問題:如果求出的H和F不對,剔除也會出錯,所以很多論文提出把H和F計算的更好
比如論文:
基於 SIFT的寬基線立體影像最小二乘匹配方法
楊化超 ,張書畢, 張秋昭