【opencv】示例-epipolar_lines.cpp 对极线

这段代码总的功能是使用OpenCV库进行立体视觉的估计。它从命令行读取两个图像文件名,使用SIFT算法检测关键点并计算这些点的描述子,接着通过FLANN库进行快速近似最近邻搜索来找到匹配的关键点。然后使用RANSAC方法计算基础矩阵,找到内点,并绘制出对极线和对应的点。最后将处理后的图像显示出来,并且保存到文件。这个程序是处理和展示两个图像之间的对应关系和极线(epilines)的。

cpp 复制代码
// 这个文件属于OpenCV项目的一部分。
// 它服从发现在顶级目录的LICENSE文件中的许可条款
// 以及在http://opencv.org/license.html 网址中的条款。


#include "opencv2/calib3d.hpp" // 包含用于相机标定和三维重建的函数和类
#include "opencv2/highgui.hpp"  // 包含GUI功能,如显示和保存图像,处理鼠标事件等。
#include "opencv2/imgproc.hpp"  // 包含图像处理的功能如滤波,几何变换等。


#include <vector> // 包含标准模板库的vector容器
#include <iostream> // 包含标准输入输出库


using namespace cv; // 使用OpenCV命名空间中的所有成员


// 主函数,程序的入口点
int main(int argc, char** argv) {
    std::string img_name1, img_name2; // 声明两个string变量,用来存放两幅图像的名字
    if (argc < 3) { // 如果命令行参数少于3个(包括程序本身的名称)
       CV_Error(Error::StsBadArg, // 抛出一个错误
                "Path to two images \nFor example: " // 并显示错误信息
                "./epipolar_lines img1.jpg img2.jpg");
    } else {
       img_name1 = argv[1]; // 把第一个图像的文件名赋给img_name1
       img_name2 = argv[2]; // 把第二个图像的文件名赋给img_name2
    }


    Mat image1 = imread(img_name1); // 读取第一幅图像
    Mat image2 = imread(img_name2); // 读取第二幅图像
    Mat descriptors1, descriptors2; // 定义两个Mat对象,用来存放两组特征描述
    std::vector<KeyPoint> keypoints1, keypoints2; // 定义两个 KeyPoint 类型的向量,用来存储两组关键点


    Ptr<SIFT> detector = SIFT::create(); // 创建SIFT检测器
    detector->detect(image1, keypoints1); // 在第一幅图像中检测关键点
    detector->detect(image2, keypoints2); // 在第二幅图像中检测关键点
    detector->compute(image1, keypoints1, descriptors1); // 计算第一幅图像中关键点的特征描述
    detector->compute(image2, keypoints2, descriptors2); // 计算第二幅图像中关键点的特征描述


    FlannBasedMatcher matcher(makePtr<flann::KDTreeIndexParams>(5), makePtr<flann::SearchParams>(32)); // 使用基于FLANN的匹配器,初始化为带有两个参数的构造函数


    // 获取k=2个最佳匹配以便可以应用由D.Lowe解释的比率测试
    std::vector<std::vector<DMatch>> matches_vector; // 定义一个DMatch向量的向量,用来存放匹配对
    matcher.knnMatch(descriptors1, descriptors2, matches_vector, 2); // 在两组描述子之间进行k邻近匹配


    std::vector<Point2d> pts1, pts2; // 定义两个Point2d向量,用于存放匹配点的坐标
    pts1.reserve(matches_vector.size()); // 为pts1预留足够大小的内存空间
    pts2.reserve(matches_vector.size()); // 为pts2预留足够大小的内存空间
    for (const auto &m : matches_vector) { // 遍历所有的匹配对
        // 对最佳匹配和次佳匹配应用Lowe比率测试
        if (m[0].distance / m[1].distance < 0.75) { // 如果最佳匹配与次佳匹配的距离比小于0.75
            pts1.emplace_back(keypoints1[m[0].queryIdx].pt); // 把第一幅图像中的匹配点坐标添加到pts1
            pts2.emplace_back(keypoints2[m[0].trainIdx].pt); // 把第二幅图像中的匹配点坐标添加到pts2
        }
    }


    std::cout << "Number of points " << pts1.size() << '\n'; // 输出匹配点的数量


    Mat inliers; // 定义一个Mat对象,去存放内点
    const auto begin_time = std::chrono::steady_clock::now(); // 记录RANSAC寻找基础矩阵开始的时间
    const Mat F = findFundamentalMat(pts1, pts2, RANSAC, 1., 0.99, 2000, inliers); // 使用RANSAC算法计算基础矩阵
    // 输出RANSAC算法寻找基础矩阵所花时间
    std::cout << "RANSAC fundamental matrix time " << static_cast<int>(std::chrono::duration_cast<std::chrono::microseconds>
            (std::chrono::steady_clock::now() - begin_time).count()) << "\n";


    Mat points1 = Mat((int)pts1.size(), 2, CV_64F, pts1.data()); // 将pts1中的点转换为Mat格式用于进一步计算
    Mat points2 = Mat((int)pts2.size(), 2, CV_64F, pts2.data()); // 将pts2中的点转换为Mat格式用于进一步计算
    vconcat(points1.t(), Mat::ones(1, points1.rows, points1.type()), points1); // 把points1转置并添加一行1,生成齐次坐标
    vconcat(points2.t(), Mat::ones(1, points2.rows, points2.type()), points2); // 把points2转置并添加一行1,生成齐次坐标


    RNG rng; // 随机数生成器
    const int circle_sz = 3, line_sz = 1, max_lines = 300; // 定义绘制圆的大小,线的大小,以及最大线的数量
    std::vector<int> pts_shuffle (points1.cols); // 创建一个向量用来存放随机顺序的点的索引
    for (int i = 0; i < points1.cols; i++) // 初始化pts_shuffle向量
        pts_shuffle[i] = i;
    randShuffle(pts_shuffle); // 对点的索引进行随机排序
    int plot_lines = 0, num_inliers = 0; // 记录绘制的直线数和内点数
    double mean_err = 0; // 记录点到对极线的平均距离
    // 遍历点,绘制对极线和点,计算平均距离
    for (int pt : pts_shuffle) { // 遍历随机顺序的点
        if (inliers.at<uchar>(pt)) { // 如果是内点
            const Scalar col (rng.uniform(0,256), rng.uniform(0,256), rng.uniform(0,256)); // 随机生成颜色
            const Mat l2 = F     * points1.col(pt); // 用基础矩阵F和齐次坐标,计算第二个图像中的对极线
            const Mat l1 = F.t() * points2.col(pt); // 用基础矩阵F的转置和齐次坐标,计算第一个图像中的对极线
            // 提取对极线的参数
            double a1 = l1.at<double>(0), b1 = l1.at<double>(1), c1 = l1.at<double>(2);
            double a2 = l2.at<double>(0), b2 = l2.at<double>(1), c2 = l2.at<double>(2);
            // 对极线参数进行归一化
            const double mag1 = sqrt(a1*a1 + b1*b1), mag2 = (a2*a2 + b2*b2);
            a1 /= mag1; b1 /= mag1; c1 /= mag1; a2 /= mag2; b2 /= mag2; c2 /= mag2;
            // 如果没有绘制太多的线,就绘制对极线
            if (plot_lines++ < max_lines) {
                // 在第一个图像上绘制对极线
                line(image1, Point2d(0, -c1/b1),
                     Point2d((double)image1.cols, -(a1*image1.cols+c1)/b1), col, line_sz);
                // 在第二个图像上绘制对极线
                line(image2, Point2d(0, -c2/b2),
                     Point2d((double)image2.cols, -(a2*image2.cols+c2)/b2), col, line_sz);
            }
            // 在两幅图像上绘制内点
            circle (image1, pts1[pt], circle_sz, col, -1);
            circle (image2, pts2[pt], circle_sz, col, -1);
            // 计算点到对极线的平均距离
            mean_err += (fabs(points1.col(pt).dot(l2)) / mag2 + fabs(points2.col(pt).dot(l1) / mag1)) / 2;
            num_inliers++; // 内点数自增
        }
    }
    // 输出内点到对极线的平均距离和内点数
    std::cout << "Mean distance from tentative inliers to epipolar lines " << mean_err/num_inliers
              << " number of inliers " << num_inliers << "\n";
    // 将两幅图像横向拼接
    hconcat(image1, image2, image1);
    const int new_img_size = 1200 * 800; // 定义新图像的大小
    // 调整图像大小,并保持原始的宽高比
    resize(image1, image1, Size((int) sqrt ((double) image1.cols * new_img_size / image1.rows),
                                (int)sqrt ((double) image1.rows * new_img_size / image1.cols)));


    // 创建一个窗口并显示处理后的图像
    imshow("epipolar lines, image 1, 2", image1);
    imwrite("epipolar_lines.png", image1); // 保存图像到文件
    waitKey(0); // 等待用户按键
}
php 复制代码
FlannBasedMatcher matcher(makePtr<flann::KDTreeIndexParams>(5), makePtr<flann::SearchParams>(32)); // 使用基于FLANN的匹配器,初始化为带有两个参数的构造函数
css 复制代码
pts1.reserve(matches_vector.size());
javascript 复制代码
const Mat F = findFundamentalMat(pts1, pts2, RANSAC, 1., 0.99, 2000, inliers);
php 复制代码
const auto begin_time = std::chrono::steady_clock::now(); 
std::cout << "RANSAC fundamental matrix time " << static_cast<int>(std::chrono::duration_cast<std::chrono::microseconds>
    (std::chrono::steady_clock::now() - begin_time).count()) << "\n";

对极线的绘制:

cpp 复制代码
for (int pt : pts_shuffle) {
    if (inliers.at<uchar>(pt)) {
        const Scalar col (rng.uniform(0,256), rng.uniform(0,256), rng.uniform(0,256));
        const Mat l2 = F     * points1.col(pt);
        const Mat l1 = F.t() * points2.col(pt);
        double a1 = l1.at<double>(0), b1 = l1.at<double>(1), c1 = l1.at<double>(2);
        double a2 = l2.at<double>(0), b2 = l2.at<double>(1), c2 = l2.at<double>(2);
        const double mag1 = sqrt(a1*a1 + b1*b1), mag2 = (a2*a2 + b2*b2);
        a1 /= mag1; b1 /= mag1; c1 /= mag1; a2 /= mag2; b2 /= mag2; c2 /= mag2;
        if (plot_lines++ < max_lines) {
            line(image1, Point2d(0, -c1/b1),
                 Point2d((double)image1.cols, -(a1*image1.cols+c1)/b1), col, line_sz);
            line(image2, Point2d(0, -c2/b2),
                 Point2d((double)image2.cols, -(a2*image2.cols+c2)/b2), col, line_sz);
        }
        circle (image1, pts1[pt], circle_sz, col, -1);
        circle (image2, pts2[pt], circle_sz, col, -1);
        mean_err += (fabs(points1.col(pt).dot(l2)) / mag2 + fabs(points2.col(pt).dot(l1) / mag1)) / 2;
        num_inliers++;
    }
}

The End

相关推荐
IT古董几秒前
【人工智能】Python在机器学习与人工智能中的应用
开发语言·人工智能·python·机器学习
CV学术叫叫兽16 分钟前
快速图像识别:落叶植物叶片分类
人工智能·分类·数据挖掘
嵌入式大圣23 分钟前
单片机结合OpenCV
单片机·嵌入式硬件·opencv
xrgs_shz30 分钟前
MATLAB读入不同类型图像并显示图像和相关信息
图像处理·计算机视觉·matlab
WeeJot嵌入式42 分钟前
卷积神经网络:深度学习中的图像识别利器
人工智能
脆皮泡泡1 小时前
Ultiverse 和web3新玩法?AI和GameFi的结合是怎样
人工智能·web3
机器人虎哥1 小时前
【8210A-TX2】Ubuntu18.04 + ROS_ Melodic + TM-16多线激光 雷达评测
人工智能·机器学习
码银1 小时前
冲破AI 浪潮冲击下的 迷茫与焦虑
人工智能
用户37791362947551 小时前
【循环神经网络】只会Python,也能让AI写出周杰伦风格的歌词
人工智能·算法
何大春1 小时前
【弱监督语义分割】Self-supervised Image-specific Prototype Exploration for WSSS 论文阅读
论文阅读·人工智能·python·深度学习·论文笔记·原型模式