- 操作系统:ubuntu22.04
- OpenCV版本:OpenCV4.9
- IDE:Visual Studio Code
- 编程语言:C++11
算法描述
为未校准的立体相机计算一个校正变换。
cv::stereoRectifyUncalibrated 是 OpenCV 库中的一个函数,用于处理未校准的立体图像对。该函数尝试在没有内参和畸变参数的情况下,通过找到两个视图之间的单应性矩阵(Homography Matrix)来实现立体图像的校正。这个过程是基于给定的匹配点对和基础矩阵(Fundamental Matrix)。
函数原型
cpp
bool cv::stereoRectifyUncalibrated
(
InputArray points1,
InputArray points2,
InputArray F,
Size imgSize,
OutputArray H1,
OutputArray H2,
double threshold = 5
)
参数
- 参数points1 第一图像中的特征点数组。
- 参数points2 第二图像中对应的特征点。支持的格式与 findFundamentalMat 中相同。
- 参数F 输入的基础矩阵(Fundamental Matrix)。可以使用相同的点对通过 findFundamentalMat 计算得到。
- 参数imgSize 图像尺寸。
- 参数H1 第一图像的输出校正单应性矩阵(Homography Matrix)。
- 参数H2 第二图像的输出校正单应性矩阵(Homography Matrix)。
- 参数threshold 可选阈值,用于过滤外点。如果该参数大于零,则所有不符合极线几何条件的点对(即 |points2[i]T⋅F⋅points1[i]| > threshold 的点)在计算单应性矩阵之前会被拒绝。否则,所有点都被视为内点。
功能描述
cv::stereoRectifyUncalibrated 函数旨在不依赖相机内参和它们在空间中的相对位置的情况下计算校正变换,这就是为什么函数名中有 "uncalibrated" 后缀的原因。它与 stereoRectify 的一个主要区别在于,它输出的是编码在单应性矩阵 H1 和 H2 中的平面透视变换,而不是三维空间中的校正变换。
该函数实现了算法 [116],用于从未经校准的立体图像中提取有用的几何信息,并生成可用于后续处理(如视差计算)的校正图像。
注意事项
尽管该算法不需要知道相机的内参,但它严重依赖于极线几何。因此,如果相机镜头存在显著的畸变,最好在计算基础矩阵和调用此函数之前进行校正。例如,可以分别使用 calibrateCamera 为每个立体相机头估计畸变系数,然后使用 undistort 校正图像,或者仅使用 undistortPoints 校正点坐标。
代码示例
cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
using namespace cv;
using namespace std;
// 生成更复杂的测试图像函数
void generateTestImages( Size imageSize, Mat& img1, Mat& img2 )
{
img1 = Mat::zeros( imageSize, CV_8UC1 );
img2 = Mat::zeros( imageSize, CV_8UC1 );
RNG rng( 12345 ); // 随机数生成器
// 在第一张图像上画随机圆,在第二张图像上画稍微偏移的随机圆模拟立体图像
for ( int i = 0; i < 100; ++i )
{ // 绘制更多随机圆
Point center( rng.uniform( 50, imageSize.width - 50 ), rng.uniform( 50, imageSize.height - 50 ) );
int radius = rng.uniform( 5, 20 );
circle( img1, center, radius, Scalar( 255 ), FILLED );
circle( img2, Point( center.x + rng.uniform( -10, 10 ), center.y + rng.uniform( -10, 10 ) ), radius, Scalar( 255 ), FILLED );
}
}
// 检测并匹配特征点的函数
void detectAndMatchFeatures( const Mat& img1, const Mat& img2, vector< KeyPoint >& keypoints1, vector< KeyPoint >& keypoints2, vector< DMatch >& good_matches )
{
Ptr< Feature2D > detector = ORB::create( 1000 ); // 增加检测到的特征点数量
// 定义描述符矩阵
Mat descriptors1, descriptors2;
// 检测关键点并计算描述符
detector->detectAndCompute( img1, noArray(), keypoints1, descriptors1 );
detector->detectAndCompute( img2, noArray(), keypoints2, descriptors2 );
// 匹配描述符
BFMatcher matcher( NORM_HAMMING );
vector< vector< DMatch > > matches;
matcher.knnMatch( descriptors1, descriptors2, matches, 2 ); // 使用knnMatch找到两个最近邻
// 过滤出好的匹配点对(Lowe's ratio test)
for ( size_t i = 0; i < matches.size(); ++i )
{
if ( matches[ i ].size() == 2 && matches[ i ][ 0 ].distance < 0.7 * matches[ i ][ 1 ].distance )
{
good_matches.push_back( matches[ i ][ 0 ] );
}
}
}
// 使用RANSAC去除异常值并重新计算F矩阵
Mat refineFundamentalMatrix( vector< Point2f >& points1, vector< Point2f >& points2 )
{
vector< uchar > inliers( points1.size() );
Mat F = findFundamentalMat( points1, points2, FM_RANSAC, 3, 0.99, inliers );
vector< Point2f > refinedPoints1, refinedPoints2;
for ( size_t i = 0; i < inliers.size(); ++i )
{
if ( inliers[ i ] )
{
refinedPoints1.push_back( points1[ i ] );
refinedPoints2.push_back( points2[ i ] );
}
}
// 用精炼后的点重新计算F矩阵
if ( !refinedPoints1.empty() && !refinedPoints2.empty() )
{
F = findFundamentalMat( refinedPoints1, refinedPoints2, FM_8POINT );
}
return F;
}
// 检查单应性矩阵是否有效
bool isValidHomography( const Mat& H )
{
if ( H.empty() )
return false;
if ( H.rows != 3 || H.cols != 3 )
return false;
// Check if the last element is close to 1 (for homography matrix)
if ( abs( H.at< double >( 2, 2 ) - 1.0 ) > 1e-6 )
return false;
return true;
}
// 可视化匹配点
void visualizeMatches( const Mat& img1, const Mat& img2, const vector< KeyPoint >& keypoints1, const vector< KeyPoint >& keypoints2, const vector< DMatch >& matches, const string& windowName )
{
Mat img_matches;
drawMatches( img1, keypoints1, img2, keypoints2, matches, img_matches );
imshow( windowName, img_matches );
}
// 验证基础矩阵的有效性
bool validateFundamentalMatrix( const Mat& F )
{
if ( F.empty() )
return false;
if ( F.rows != 3 || F.cols != 3 )
return false;
// Fundamental matrix should have rank 2
Mat W, U, Vt;
SVD::compute( F, W, U, Vt );
double rank = countNonZero( W > 1e-8 );
return rank == 2;
}
int main()
{
// 生成一对测试图像
Size imageSize( 640, 480 );
Mat img1, img2;
generateTestImages( imageSize, img1, img2 );
// 提取和匹配特征点
vector< KeyPoint > keypoints1, keypoints2;
vector< DMatch > good_matches;
detectAndMatchFeatures( img1, img2, keypoints1, keypoints2, good_matches );
if ( good_matches.size() < 8 )
{
cerr << "Error: Not enough matches found (" << good_matches.size() << ")." << endl;
return -1;
}
// 将匹配点转换为向量形式
vector< Point2f > points1, points2;
for ( DMatch match : good_matches )
{
points1.push_back( keypoints1[ match.queryIdx ].pt );
points2.push_back( keypoints2[ match.trainIdx ].pt );
}
// 可视化匹配点
visualizeMatches( img1, img2, keypoints1, keypoints2, good_matches, "Good Matches" );
// 使用RANSAC去除异常值并重新计算F矩阵
Mat F = refineFundamentalMatrix( points1, points2 );
// 验证基础矩阵的有效性
if ( !validateFundamentalMatrix( F ) )
{
cerr << "Error: Invalid fundamental matrix." << endl;
return -1;
}
cout << "Validated Fundamental matrix:\n" << F << endl;
// 执行未校准的立体校正
Mat H1, H2;
bool success = stereoRectifyUncalibrated( points1, points2, F, imageSize, H1, H2, 1 );
if ( !success || !isValidHomography( H1 ) || !isValidHomography( H2 ) )
{
cerr << "Error: Unable to compute valid rectification transformations." << endl;
// Print out the homographies for debugging purposes
cout << "H1:\n" << H1 << endl;
cout << "H2:\n" << H2 << endl;
return -1;
}
cout << "Rectification homography matrix for the first image:\n" << H1 << endl;
cout << "Rectification homography matrix for the second image:\n" << H2 << endl;
// 初始化重映射
Mat map1x, map1y, map2x, map2y;
initUndistortRectifyMap( Mat(), Mat(), H1, Mat(), imageSize, CV_32FC1, map1x, map1y );
initUndistortRectifyMap( Mat(), Mat(), H2, Mat(), imageSize, CV_32FC1, map2x, map2y );
// 确保重映射图是有效的
if ( map1x.empty() || map1y.empty() || map2x.empty() || map2y.empty() )
{
cerr << "Error: Failed to initialize remap maps." << endl;
return -1;
}
// 应用重映射
Mat rectifiedImg1, rectifiedImg2;
try
{
remap( img1, rectifiedImg1, map1x, map1y, INTER_LINEAR, BORDER_CONSTANT, Scalar() );
remap( img2, rectifiedImg2, map2x, map2y, INTER_LINEAR, BORDER_CONSTANT, Scalar() );
}
catch ( const cv::Exception& e )
{
cerr << "Error during remap: " << e.what() << endl;
return -1;
}
// 显示结果
imshow( "Original Image 1", img1 );
imshow( "Original Image 2", img2 );
imshow( "Rectified Image 1", rectifiedImg1 );
imshow( "Rectified Image 2", rectifiedImg2 );
waitKey( 0 ); // 等待按键关闭窗口
return 0;
}
运行结果
bash
Validated Fundamental matrix:
[-1.69507626646751e-05, 7.362164468986336e-05, -0.1173662750444769;
-6.367019516817851e-05, 3.353162138833532e-06, 0.001880485218793337;
0.1241506729344979, -0.009417932088193068, 1]
Error: Unable to compute valid rectification transformations.
H1:
[0.02410468346050559, -0.1256463999224076, 64.27174787791705;
0.1238268442213851, -0.002949304419357657, -9.446067724507486;
3.05103171526221e-05, -8.242409501994642e-05, 0.1300740303808192]
H2:
[0.1930536997260217, -1.169973024903469, 539.0163420645055;
1.009344355783741, 0.02531934576861759, -89.06683683526535;
9.452570423521679e-05, -0.0005728588691755615, 1.107237903246865]