RANSAC(随机采样一致性)算法在多目三维空间点重构中的应用demo(c++代码实现)

本文探讨了RANSAC算法在多目三维人体关节点重构中的应用,针对二维检测不准确的问题,利用RANSAC算法排除较差的检测结果,以提高三维重构的准确性。文中详细介绍了RANSAC算法的原理,并提供了C++实现的代码示例。
部署运行你感兴趣的模型镜像

RANSAC(随机采样一致性) 算法与最小二乘算法不同之处在于它假设数据样本中包含局外点即outliers,其余为局内点即inliers。该算法通过迭代一定的次数,每次随机选取若干样本构造一个模型,然后验证其余样本是否为局内点。以此找到一个相对合理的最优模型。在计算机视觉方面广泛应用。具体可参考网上其余博主对RANSAC算法层面上的讲述和理解。

1 问题:笔者在做多目三维人体关节点重构时遇到一个这样的问题:在某些视角视角下,关节点的二维检测是不准确的,错误的检测在三角化是就会导致重构结果偏差较大。例如下图的例子(Shelf数据集中的某一帧):

在这里插入图片描述
绿色箭头所指的这个关节点在0 1 2 4视角出现,但是0视角下该关节点的二维检测显然是不准确的,三角化后重构结果也会不准确。

2 使用RANSAC算法排除较差的二维检测

现在我们把不同视角的二维检测当做若干的样本,每次随机选择一定数量(这里为2)的视角,重构出一个三维点,将改点投影到其余视角,通过投影误差与一个阈值的比较,判定新的视角是否为inliers,是的话将其加入到重构列表里,否则不加。通过若干次随机采样的迭代后,从中选择inliers数量最多的那次作为bestModel,最后使用inliers重构最后的三维点。这么说起来,其实ransac算法还是很好理解的。

3 用c++来实现ransac三角化算法

代码实现使用了一位大神写的 RANSAC通用框架,在此基础上,我们只需要定义一个待解决问题的模型就行了,以下代码包括一个TriangulationRansacModel和测试程序,代码仓库地址,如果有帮助,可以加个星。

TriangulationModel.hpp

#pragma once

#include <opencv2/opencv.hpp>
#include "AbstractModel.hpp"

// triangulation using SVD
void TriangulateWithConf(const std::vector<cv::Point3f>& pointsOnEachCamera,
                         const std::vector<cv::Mat>& cameraMatrices,
                         cv::Mat& reconstructedPoint) {
  const auto numberCameras = (int)cameraMatrices.size();
  cv::Mat A = cv::Mat::zeros(numberCameras * 2, 4, CV_32F);
  for (auto i = 0; i < numberCameras; i++) {
    A.row(i * 2) = (pointsOnEachCamera[i].x * cameraMatrices[i].row(2) -
                    cameraMatrices[i].row(0)) *
                   pointsOnEachCamera[i].z;

    A.row(i * 2 + 1) = (pointsOnEachCamera[i].y * cameraMatrices[i].row(2) -
                        cameraMatrices[i].row(1)) *
                       pointsOnEachCamera[i].z;
  }
  // Solve x for Ax = 0 --> SVD on A
  cv::SVD::solveZ(A, reconstructedPoint);
  reconstructedPoint /= reconstructedPoint.at<float>(3);
}

// per view sample
class SingleView : public GRANSAC::AbstractParameter {
 public:
  SingleView(GRANSAC::VPFloat x, GRANSAC::VPFloat y, GRANSAC::VPFloat conf,
             cv::Mat& ProjMatrix)
      : m_ProjMatrix(ProjMatrix) {
    m_Point2D = cv::Point3f(x, y, conf);
  };

  cv::Point3f m_Point2D;
  cv::Mat& m_ProjMatrix;
};

// our model
class TriangulationRansacModel : public GRANSAC::AbstractModel<2> {
 protected:
  // Parametric form
  cv::Mat m_ReconstructedPoint;

  // calculate reprojection error
  virtual GRANSAC::VPFloat ComputeDistanceMeasure(
      std::shared_ptr<GRANSAC::AbstractParameter> Param) override {
    auto singleView = std::dynamic_pointer_cast<SingleView>(Param);
    if (singleView == nullptr)
      throw std::runtime_error(
          "TriangulationRansacModel::ComputeDistanceMeasure() - Passed "
          "parameter are not of type Point2D.");

    cv::Mat projPoint2D = singleView->m_ProjMatrix * m_ReconstructedPoint;
    projPoint2D = projPoint2D / projPoint2D.at<float>(2);
    cv::Point3f& point2D = singleView->m_Point2D;
    GRANSAC::VPFloat Dist = sqrt(pow(point2D.x - projPoint2D.at<float>(0), 2) +
                                 pow(point2D.y - projPoint2D.at<float>(1), 2));

    return Dist;
  };

 public:
  TriangulationRansacModel(
      const std::vector<std::shared_ptr<GRANSAC::AbstractParameter>>&
          InputParams) {
    Initialize(InputParams);
  };

  // estimate 3d point from two views
  virtual void Initialize(
      const std::vector<std::shared_ptr<GRANSAC::AbstractParameter>>&
          InputParams) override {
    if (InputParams.size() != 2)
      throw std::runtime_error(
          "TriangulationRansacModel - Number of input parameters does not "
          "match minimum number required for this model.");

    // Check for AbstractParamter types
    auto singleView1 = std::dynamic_pointer_cast<SingleView>(InputParams[0]);
    auto singleView2 = std::dynamic_pointer_cast<SingleView>(InputParams[1]);
    if (singleView1 == nullptr || singleView2 == nullptr)
      throw std::runtime_error(
          "TriangulationRansacModel - InputParams type mismatch. It is not a "
          "Point2D.");

    std::copy(InputParams.begin(), InputParams.end(), m_MinModelParams.begin());

    // reconstruct 3d point
    std::vector<cv::Point3f> pointsOnEachCamera{singleView1->m_Point2D,
                                                singleView2->m_Point2D};
    std::vector<cv::Mat> cameraMatrices{singleView1->m_ProjMatrix,
                                        singleView2->m_ProjMatrix};
    TriangulateWithConf(pointsOnEachCamera, cameraMatrices,
                        m_ReconstructedPoint);
  };

  virtual std::pair<GRANSAC::VPFloat,
                    std::vector<std::shared_ptr<GRANSAC::AbstractParameter>>>
  Evaluate(const std::vector<std::shared_ptr<GRANSAC::AbstractParameter>>&
               EvaluateParams,
           GRANSAC::VPFloat Threshold) {
    std::vector<std::shared_ptr<GRANSAC::AbstractParameter>> Inliers;
    int nTotalParams = EvaluateParams.size();
    int nInliers = 0;

    for (auto& Param : EvaluateParams) {
      if (ComputeDistanceMeasure(Param) < Threshold) {
        Inliers.push_back(Param);
        nInliers++;
      }
    }

    GRANSAC::VPFloat InlierFraction =
        GRANSAC::VPFloat(nInliers) /
        GRANSAC::VPFloat(nTotalParams);  // This is the inlier fraction

    return std::make_pair(InlierFraction, Inliers);
  };

  // get final reconstructed 3d point using best inliers
  cv::Mat GetBestReconstructed3DPoint(
      const std::vector<std::shared_ptr<GRANSAC::AbstractParameter>>&
          BestInliers) {
    std::vector<cv::Point3f> pointsOnEachCamera;
    std::vector<cv::Mat> cameraMatrices;

    for (auto& Inlier : BestInliers) {
      auto sv = std::dynamic_pointer_cast<SingleView>(Inlier);
      pointsOnEachCamera.emplace_back(sv->m_Point2D);
      cameraMatrices.emplace_back(sv->m_ProjMatrix);
    }
    cv::Mat reconstructed3DPoint;
    TriangulateWithConf(pointsOnEachCamera, cameraMatrices,
                        reconstructed3DPoint);
    return reconstructed3DPoint;
  }
};

TriangulationRansacSample.cpp

#include <cmath>
#include <iostream>
#include <opencv2/opencv.hpp>

#include "GRANSAC.hpp"
#include "TriangulationModel.hpp"

int main(int argc, char* arcv[]) {
  // load projection matrix
  unsigned int nCameraViews = 5;
  std::vector<cv::Mat> Ps;

  Ps.resize(nCameraViews);
  cv::FileStorage psread("/home/shuai/Desktop/Shelf_Ps.xml",
                         cv::FileStorage::READ);
  for (unsigned int i = 0; i < nCameraViews; i++) {
    cv::Mat m;
    psread["p_" + std::to_string(i)] >> m;
    Ps.at(i) = m;
  }
  psread.release();

  // prepare candidate view data
  std::vector<std::shared_ptr<GRANSAC::AbstractParameter>> CandViews;

  CandViews.emplace_back(
      std::make_shared<SingleView>(577, 768, 0.732, Ps.at(0)));
  CandViews.emplace_back(
      std::make_shared<SingleView>(933, 666, 1.06, Ps.at(1)));
  CandViews.emplace_back(
      std::make_shared<SingleView>(828, 467, 1.072, Ps.at(2)));
  CandViews.emplace_back(
      std::make_shared<SingleView>(389, 475, 1.009, Ps.at(4)));

  // RANSAC Triangulation
  GRANSAC::RANSAC<TriangulationRansacModel, 2> Estimator;
  Estimator.Initialize(15, 10);  // Threshold, iterations
  int64_t start = cv::getTickCount();
  Estimator.Estimate(CandViews);
  int64_t end = cv::getTickCount();
  std::cout << "RANSAC Took: "
            << GRANSAC::VPFloat(end - start) /
                   GRANSAC::VPFloat(cv::getTickFrequency()) * 1000.0
            << " ms." << std::endl;

  // other informations to output
  auto& BestInliers = Estimator.GetBestInliers();
  std::cout << std::endl;
  std::cout << "Best Inliers: " << std::endl;
  for (auto& Inlier : BestInliers) {
    auto sv = std::dynamic_pointer_cast<SingleView>(Inlier);
    std::cout << sv->m_Point2D << std::endl;
  }

  auto bestModel = Estimator.GetBestModel();
  auto reconstruct3DPoint =
      std::dynamic_pointer_cast<TriangulationRansacModel>(bestModel)
          ->GetBestReconstructed3DPoint(BestInliers);

  std::cout << std::endl;
  std::cout << "Reconstructed 3D Point: " << std::endl
            << reconstruct3DPoint << std::endl;

  std::cout << std::endl;
  return 0;
}

4 结果

结果中,只选择了三个视角进行重构,其中0视角的(577, 768)被忽略。

在这里插入图片描述

您可能感兴趣的与本文相关的镜像

PyTorch 2.8

PyTorch 2.8

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

% Demo code of the LNIFT algorithm. If it is helpful for you, please cite: % Li, Jiayuan, et al. "LNIFT: Locally Normalized Image for Rotation Invariant Multimodal Feature Matching." IEEE Transactions on Geoscience and Remote Sensing 60 (2022): 1-14. % Note that the results of this Matlab code is not the same as the C++ code and the running time is much slower. % MATLAB2021a or higher is required for ORB detection. tic; clc;clear; warning('off'); close all; addpath LNIFT\ folder = 'MRSIDatasets\test'; patch_size=128; % 获取所有opt和sar文件 opt_files = dir(fullfile(folder, 'pair*.png')); sar_files = dir(fullfile(folder, 'pair*.jpg')); % 提取数字并匹配文件对 file_pairs = containers.Map('KeyType', 'double', 'ValueType', 'any'); % 处理opt文件 for i = 1:length(opt_files) num = str2double(regexp(opt_files(i).name, '\d+', 'match', 'once')); if isKey(file_pairs, num) pair = file_pairs(num); pair{1} = opt_files(i).name; file_pairs(num) = pair; else file_pairs(num) = {opt_files(i).name, ''}; end end % 处理sar文件 for i = 1:length(sar_files) num = str2double(regexp(sar_files(i).name, '\d+', 'match', 'once')); if isKey(file_pairs, num) pair = file_pairs(num); pair{2} = sar_files(i).name; file_pairs(num) = pair; else file_pairs(num) = {'', sar_files(i).name}; end end all_nums = cell2mat(keys(file_pairs)); sorted_nums = sort(all_nums); all_results = zeros(1, 4); % 找到第x个文件的编号 start_num = sorted_nums(1); % 在原有代码的最终处理循环中增加计数器 max_pairs = 1; % 设置最大处理对数 processed_count = 0; % 初始化计数器 for num = sorted_nums if num < start_num continue; end pair = file_pairs(num); if ~isempty(pair{1}) && ~isempty(pair{2}) str1 = fullfile(folder, pair{1}); str2 = fullfile(folder, pair{2}); % 你的处理代码 im1 = im2uint8(imread(str1)); im2 = im2uint8(imread(str2)); % 在这里添加你的处理逻辑 if size(im1,3)==3 im1 = rgb2gray(im1); end if size(im2,3)==3 im2 = rgb2gray(im2); end % 计算互信息(MI) % imshow(im1) % numBins = 256; % 使用 256 个 bin % % 将图像转换为 double 类型以进行计算 % im1 = im2double(im1); % im2 = im2double(im2); % [~,~,~,~,~,~,mi] = mutualinfo(im1, im2, numBins); % % % 计算归一化互相关(NCC) % ncc = normalizedCrossCorrelation(im1, im2); % disp(mi); % disp(ncc); edge_threshold = graythresh(im1); % bw = imbinarize(im1, edge_threshold); edge_threshold = edge_threshold * max(im1(:)); edge_mask = (im1 > edge_threshold); edge_density = sum(edge_mask(:)) / numel(im1); % imhist(im1); % imshow(bw); % 动态计算d值(映射到1~10) % d_jiance = round(d_min + (d_max - d_min) * exp(-gamma * edge_density)); d_jiance = max(min(floor(edge_density * 6) + 2,7),2); % 动态调整公式 d_miaoshu = max(min(floor(edge_density * 6) + 17,22),17); % 动态调整公式 % d_jiance = max(round(30 - 150 * edge_density),1); % 动态调整公式 % d_miaoshu = round(3 + 200 * edge_density); % 动态调整公式 % im2 = imrotate(im2, -25, 'bilinear','crop'); % 逆时针旋转45度并裁剪到原始大小 % Keypoint detection. Please set the 'FastThreshold' of detectORBFeatures % function from 31 to 1 to get more features. [kpts1,im11,im12] = detector(im1,5000,1,d_jiance); [kpts2,im21,im22] = detector(im2,5000,1,d_jiance); %替换为PC输出中的 % % Keypoint description. [des1,validKeypoints1] = descriptor(im1,kpts1,patch_size,8,4,im11,im12,d_miaoshu); [des2,validKeypoints2] = descriptor(im2,kpts2,patch_size,8,4,im21,im22,d_miaoshu); % Keypoint matching. indexPairs12 = matchFeatures(des1',des2','MaxRatio',1,'MatchThreshold', 100); % indexPairs21 = matchFeatures(des2',des1','MaxRatio',1,'MatchThreshold', 100); kpts1 = validKeypoints1'; kpts2 = validKeypoints2'; % % 找出双向一致的匹配 % consistentMatches = []; % for i = 1:size(indexPairs12, 1) % idx1 = indexPairs12(i, 1); % idx2 = indexPairs12(i, 2); % % % 检查反向匹配是否存在 % reverseMatch = find(indexPairs21(:, 1) == idx2 & indexPairs21(:, 2) == idx1); % % if ~isempty(reverseMatch) % consistentMatches = [consistentMatches; idx1, idx2]; % end % end matchedPoints1 = kpts1(indexPairs12(:, 1), 1:2); matchedPoints2 = kpts2(indexPairs12(:, 2), 1:2); % Outlier removal [H]=FSC(matchedPoints1,matchedPoints2,'similarity',3); Y_=H*[matchedPoints1';ones(1,size(matchedPoints1,1))]; Y_(1,:)=Y_(1,:)./Y_(3,:); Y_(2,:)=Y_(2,:)./Y_(3,:); E=sqrt(sum((Y_(1:2,:)-matchedPoints2').^2)); inliersIndex=E<3; cleanedPoints1 = matchedPoints1(inliersIndex, :); cleanedPoints2 = matchedPoints2(inliersIndex, :); [cleanedPoints2,IA] = unique(cleanedPoints2,'rows'); cleanedPoints1 = cleanedPoints1(IA,:); TrueX = 1; TrueY = 1; Error_x = mean(cleanedPoints1(:,1)-cleanedPoints2(:,1)); Error_y = mean(cleanedPoints1(:,2)-cleanedPoints2(:,2)); total_matching_NUM = length(matchedPoints1); NCM = length(cleanedPoints1); % Number of correct matches ME = sqrt((Error_x-TrueX)^2+(Error_y-TrueY)^2); % ME Matching Error SR = NCM / (total_matching_NUM); % SR: Success Rate RMSE = sqrt(mean(sum((((cleanedPoints1-cleanedPoints2)-[TrueX,TrueY]).^2),2))); %RMSE Root Mean Square Error % % show results % figure; showMatchedFeatures(im1, im2, cleanedPoints1, cleanedPoints2, 'montage'); % image_fusion(im2,im1,double(H)) processed_count = processed_count + 1; all_results(processed_count,1)=NCM; all_results(processed_count,2)=ME; all_results(processed_count,3)=SR; all_results(processed_count,4)=RMSE; if processed_count >= max_pairs break; % 达到数量后终止循环 end end end toc;这个代码哪里错了,怎么改
最新发布
06-29
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值