OpenCV实战(25)——3D场景重建

OpenCV实战(25)——3D场景重建

    • 0. 前言
    • 1. 重建 3D 场景
      • 1.1 3D 场景点重建
      • 1.2 算法原理
    • 2. 分解单应性
    • 3. 光束平差法
    • 4. 完整代码
    • 小结
    • 系列链接

0. 前言

在《相机姿态估计》一节中,我们学习了如何在校准相机时恢复观察 3D 场景的相机的位置。算法应用了以下事实,即有时场景中可见的某些 3D 点的坐标可能是已知的。而如果能够从多个角度观察场景,即使没有关于 3D 场景的信息可用,也可以重建 3D 姿势和结构。在本节中,我们将使用不同视图中图像点之间的对应关系来推断 3D 信息,同时介绍一个新的数学实体用于校准相机的两个视图之间的关系,并将讨论三角测量的原理以便从 2D 图像重建 3D 点。

1. 重建 3D 场景

我们使用校准后的相机,拍摄两张场景照片,然后使用 SIFT (Scale Invariant Feature Transform) 检测器和描述符来匹配这两个视图之间的特征点。
相机的校准参数为我们在世界坐标系中的计算奠定了基础,需要在相机姿态和相应点的位置之间建立物理约束。我们引入了一个新的数学实体,本质矩阵 (essential matrix),它基本矩阵的校准版本, cv::findEssentialMat 函数可用于计算本质矩阵,估计图像中的投影关系。
我们可以使用已建立的点对应调用 cv::findEssentialMat 函数,并通过随机抽样一致算法 (RANdom SAmple Consensus, RANSAC) 算法过滤掉异常点以保留符合找到的几何形状的匹配项。

1.1 3D 场景点重建

(1) 首先,使用 SIFT 检测器和描述符在每个图像中获取关键点:

// 关键点和描述符向量
std::vector<cv::KeyPoint> keypoints1;
std::vector<cv::KeyPoint> keypoints2;
cv::Mat descriptors1, descriptors2;
// SIFT特征检测器
cv::Ptr<cv::Feature2D> ptrFeature2D = cv::xfeatures2d::SIFT::create(500);
// SIFT 特征及其描述符
ptrFeature2D->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1);
ptrFeature2D->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);

(2) 使用 cv::BFMatcher 匹配使用描述符检测到的关键点并将它们存储为浮点数:

// 匹配两张图像的描述符
// 带有交叉检查的匹配器
cv::BFMatcher matcher(cv::NORM_L2, true);
// 匹配
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
// 将关键点转换为 Point2f
std::vector<cv::Point2f> points1, points2;
for (std::vector<cv::DMatch>::const_iterator it = matches.begin();
it != matches.end(); ++it) {float x = keypoints1[it->queryIdx].pt.x;float y = keypoints1[it->queryIdx].pt.y;points1.push_back(cv::Point2f(x, y));x = keypoints2[it->trainIdx].pt.x;y = keypoints2[it->trainIdx].pt.y;points2.push_back(cv::Point2f(x, y));
}

(3) 使用 cv::findEssentialMat 函数,获得两个图像中匹配的图像关键点的相机矩阵:

// 图像 1 和图像 2 之间的本质矩阵 
cv::Mat inliers;
cv::Mat essential = cv::findEssentialMat(points1, points2, cMatrix,                // 固有参数cv::RANSAC, 0.9, 1.0,   // RANSAC 方法 inliers);

生成的 inliers 匹配集如下:

生成匹配集
(4) 基本矩阵封装了两个视图的旋转和平移组件。因此,可以直接从这个矩阵中恢复两个视图之间的相对姿态,这可以利用 OpenCV 函数 cv::recoverPose 完成:

// 从本质矩阵中恢复相对相机姿态
cv::Mat rotation, translation;
cv::recoverPose(essential,      // 本质矩阵points1, points2,       // 匹配关键点cameraMatrix,           // 内在矩阵rotation, translation,  // 姿态估计inliers);               // inliers 匹配

(5) 有了两个相机之间的相对位姿,可以估计我们在两个视图之间建立对应关系的点的位置。下图显示了两台摄像机的估计位置(左边的一台位于原点)。我们选择一对对应点,并根据投影几何模型,追踪对应于相关 3D 点所有可能位置的一条射线:

投影集合模型
由于这两个图像点是由同一个 3D 点生成的,所以两条射线必定会相交于 3D 点所在位置。当两个相机的相对位置已知时,将两个对应图像点的投影线相交的方法称为三角测量 (triangulation)。这个过程首先需要两个投影矩阵,并且可以重复应用于所有匹配,但必须使用世界坐标表示。可以通过使用 cv::undistortPoints 函数来完成以上过程。

(6) 最后,调用 triangulate 函数计算三角点的位置:

// 由 R, T 组成投影矩阵 
cv::Mat projection2(3, 4, CV_64F);      // 3x4 投影矩阵
rotation.copyTo(projection2(cv::Rect(0, 0, 3, 3)));
translation.copyTo(projection2.colRange(3, 4));
// 合成通用投影矩阵
cv::Mat projection1(3, 4, CV_64F, 0.);  // 3x4 投影矩阵
cv::Mat diag(cv::Mat::eye(3, 3, CV_64F));
diag.copyTo(projection1(cv::Rect(0, 0, 3, 3)));
// inliers
std::vector<cv::Vec2d> inlierPts1;
std::vector<cv::Vec2d> inlierPts2;
// 为三角测量创建 inliers 输入点向量 
int j(0); 
for (int i = 0; i < inliers.rows; i++) {if (inliers.at<uchar>(i)) {inlierPts1.push_back(cv::Vec2d(points1[i].x, points1[i].y));inlierPts2.push_back(cv::Vec2d(points2[i].x, points2[i].y));}
}
// 矫正并归一化图像点
std::vector<cv::Vec2d> points1u;
cv::undistortPoints(inlierPts1, points1u, cameraMatrix, cameraDistCoeffs);
std::vector<cv::Vec2d> points2u;
cv::undistortPoints(inlierPts2, points2u, cameraMatrix, cameraDistCoeffs);
// 三角测量
std::vector<cv::Vec3d> points3D;
triangulate(projection1, projection2, points1u, points2u, points3D);

(7) 计算得到位于场景元素上的 3D 点,如下所示:

3D 场景点
需要注意的是,在上图的角度来看,我们绘制的两条射线并没有按照预期相交。

1.2 算法原理

校准矩阵可以用于将像素坐标转换为世界坐标。应用该矩阵可以将图像点与产生它们的 3D 点相关联。在下图中演示了物理世界点与其图像之间的关系:

物理世界点与其图像之间的关系
上图显示了由旋转 R R R 和平移 T T T 变换的两个相机,平移向量 T T T 连接了两个相机的投影中心,还有一个 x x x 向量将第一个相机中心连接到一个图像点,以及一个 x ′ x' x 向量将第二个相机中心连接到相应的图像点。由于有两个相机之间的相对位置,我们可以根据第二个相机参考将 x x x 的方向表示为 R x R_x Rx。观察所示图像点的几何形状,可以发现 T T T R x R_x Rx x ′ x' x 向量都共面,可以用以下数学方程表示这种情况:
x ′ ⋅ ( T × R x ) = x ′ E x = 0 x'\cdot(T\times R_x)=x'Ex=0 x(T×Rx)=xEx=0
可以将第一个关系简化为一个 3x3 矩阵 E E E,因为叉积也可以通过矩阵运算来表示。这个 E E E 矩阵被称为本质矩阵,相关的方程是校准的对极约束。我们可以根据图像对应估计这个基本矩阵,计算过程与基本矩阵类似,区别在于需要使用世界坐标计算。此外,基本矩阵是根据两个相机之间的旋转和平移分量构建的,一旦估计了这个矩阵,就可以对其进行分解以获得相机之间的相对位姿。以上过程可以使用 cv::recoverPose 函数实现。cv::recoverPose 函数会调用 cv::decomposeEssentialMat 函数,cv::decomposeEssentialMat 函数为相对姿势生成四种可能的解,通过计算一组匹配来确定正确的解,从而确定物理上的可能解。
一旦获得了相机之间的相对位姿,就可以通过三角测量恢复与匹配对对应的点的位置。三角剖分问题最简单的解决方案需要考虑两个投影矩阵 P P P P ′ P' P,在齐次坐标中寻找 3D 点可以表示为 X = [ X , Y , Z , 1 ] T X=[X,Y,Z,1]^T X=[X,Y,Z,1]T,因为我们已经知道 x = P X x=PX x=PX x ′ = P ′ X x'=P'X x=PX。这两个齐次方程决定了两个独立方程,足以解决 3D 点位置的三个未知数。这个超定方程系统可以使用最小二乘法求解,以上过程可以通过 OpenCV 函数 cv::solve 完成:

// 使用线性LS方法进行三角测量
cv::Vec3d triangulate(const cv::Mat &p1, const cv::Mat &p2, const cv::Vec2d &u1, const cv::Vec2d &u2) {// 假设方程组image=[u,v], X=[X,y,z,1],其中u(p3.X)=p1.X和v(p3.X)=p2.Xcv::Matx43d A(u1(0)*p1.at<double>(2, 0) - p1.at<double>(0, 0), u1(0)*p1.at<double>(2, 1) - p1.at<double>(0, 1), u1(0)*p1.at<double>(2, 2) - p1.at<double>(0, 2),u1(1)*p1.at<double>(2, 0) - p1.at<double>(1, 0), u1(1)*p1.at<double>(2, 1) - p1.at<double>(1, 1), u1(1)*p1.at<double>(2, 2) - p1.at<double>(1, 2),u2(0)*p2.at<double>(2, 0) - p2.at<double>(0, 0), u2(0)*p2.at<double>(2, 1) - p2.at<double>(0, 1), u2(0)*p2.at<double>(2, 2) - p2.at<double>(0, 2),u2(1)*p2.at<double>(2, 0) - p2.at<double>(1, 0), u2(1)*p2.at<double>(2, 1) - p2.at<double>(1, 1), u2(1)*p2.at<double>(2, 2) - p2.at<double>(1, 2));cv::Matx41d B(p1.at<double>(0, 3) - u1(0)*p1.at<double>(2, 3),p1.at<double>(1, 3) - u1(1)*p1.at<double>(2, 3),p2.at<double>(0, 3) - u2(0)*p2.at<double>(2, 3),p2.at<double>(1, 3) - u2(1)*p2.at<double>(2, 3));// X 包含重建点的三维坐标 cv::Vec3d X;// 求解 AX=Bcv::solve(A, B, X, cv::DECOMP_SVD);return X;
}

在上一小节得到的结果图像中我们已经看到,由于噪声和数字化的影响,预期应该相交的投影线实际上并不相交。因此,最小二乘解会在交点附近得到解。此外,如果尝试在无穷远处重建一个点,则此方法并不起作用,这是因为,对于这样的点,齐次坐标的第四个元素应该为 0 而不是 1
最后,重要的是需要知道比例因子才能完成 3D 重建,如果需要进行实际测量,至少需要知道一个物理距离,例如,两个相机之间的实际距离或某一物体的高度。
3D 重建是计算机视觉中一个重要的研究领域,接下来,我们介绍在 OpenCV 库中相关的一些其他内容。

2. 分解单应性

在本节中,我们了解到可以分解通过一个基本矩阵恢复两个相机之间的旋转和平移分量。我们知道平面的两个视图之间存在单应性,该单应性还包含旋转和平移分量。此外,它还包含有关平面的信息,即它相对于每个相机的法线。cv::decomposeHomographyMat 函数可以用来分解这个矩阵;但是,我们需要有一台校准过的相机。

3. 光束平差法

在本节中,我们首先从匹配中估计相机位置,然后通过三角测量重建关联的 3D 点。可以通过使用任意数量的视图来完成这个过,检测每一视图的特征点并将其与其他视图匹配。基于此,可以得到与视图、3D 点集和校准信息之间的旋转和平移相关方程。所有这些未知数都可以通过优化过程一起优化,优化过程旨在最小化每个视图中所有可见点的重投影误差。这种组合优化过程称为捆集调整或光束平差法 (bundle adjustment)。cv::detail::BundleAdjusterReproj 类实现了相机参数计算算法,其最小化重投影误差平方之和。

4. 完整代码

头文件 (triangulate.h) 完整代码如下所示:

#include <opencv2/core/core.hpp>
#include <vector>// 使用线性LS方法进行三角测量
cv::Vec3d triangulate(const cv::Mat &p1, const cv::Mat &p2, const cv::Vec2d &u1, const cv::Vec2d &u2);
void triangulate(const cv::Mat &p1, const cv::Mat &p2, const std::vector<cv::Vec2d> &pts1, const std::vector<cv::Vec2d> &pts2, std::vector<cv::Vec3d> &pts3D);// 使用线性LS方法进行三角测量
cv::Vec3d triangulate(const cv::Mat &p1, const cv::Mat &p2, const cv::Vec2d &u1, const cv::Vec2d &u2) {// 假设方程组image=[u,v], X=[X,y,z,1],其中u(p3.X)=p1.X和v(p3.X)=p2.Xcv::Matx43d A(u1(0)*p1.at<double>(2, 0) - p1.at<double>(0, 0), u1(0)*p1.at<double>(2, 1) - p1.at<double>(0, 1), u1(0)*p1.at<double>(2, 2) - p1.at<double>(0, 2),u1(1)*p1.at<double>(2, 0) - p1.at<double>(1, 0), u1(1)*p1.at<double>(2, 1) - p1.at<double>(1, 1), u1(1)*p1.at<double>(2, 2) - p1.at<double>(1, 2),u2(0)*p2.at<double>(2, 0) - p2.at<double>(0, 0), u2(0)*p2.at<double>(2, 1) - p2.at<double>(0, 1), u2(0)*p2.at<double>(2, 2) - p2.at<double>(0, 2),u2(1)*p2.at<double>(2, 0) - p2.at<double>(1, 0), u2(1)*p2.at<double>(2, 1) - p2.at<double>(1, 1), u2(1)*p2.at<double>(2, 2) - p2.at<double>(1, 2));cv::Matx41d B(p1.at<double>(0, 3) - u1(0)*p1.at<double>(2, 3),p1.at<double>(1, 3) - u1(1)*p1.at<double>(2, 3),p2.at<double>(0, 3) - u2(0)*p2.at<double>(2, 3),p2.at<double>(1, 3) - u2(1)*p2.at<double>(2, 3));// X 包含重建点的三维坐标 cv::Vec3d X;// 求解 AX=Bcv::solve(A, B, X, cv::DECOMP_SVD);return X;
}// 三角化图像点的矢量 
void triangulate(const cv::Mat &p1,const cv::Mat &p2,const std::vector<cv::Vec2d> &pts1,const std::vector<cv::Vec2d> &pts2,std::vector<cv::Vec3d> &pts3D) {for (int i = 0; i < pts1.size(); i++) {pts3D.push_back(triangulate(p1, p2, pts1[i], pts2[i]));}
}

主函数文件 (estimateE.cpp) 完整代码如下所示:

#include <iostream>
#include <vector>
#include <numeric>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/viz.hpp>
#include "triangulate.h"int main() {// 读取输入图像cv::Mat image1= cv::imread("1.png",0);cv::Mat image2= cv::imread("2.png",0);if (!image1.data || !image2.data)return 0; cv::namedWindow("Right Image");cv::imshow("Right Image",image1);cv::namedWindow("Left Image");cv::imshow("Left Image",image2);// 读取相机标定参数cv::Mat cameraMatrix;cv::Mat cameraDistCoeffs;cv::FileStorage fs("calib.xml", cv::FileStorage::READ);fs["Intrinsic"] >> cameraMatrix;fs["Distortion"] >> cameraDistCoeffs;cameraMatrix.at<double>(0, 2) = 268.;cameraMatrix.at<double>(1, 2) = 178;std::cout << " Camera intrinsic: " << cameraMatrix.rows << "x" << cameraMatrix.cols << std::endl;std::cout << cameraMatrix.at<double>(0, 0) << " " << cameraMatrix.at<double>(0, 1) << " " << cameraMatrix.at<double>(0, 2) << std::endl;std::cout << cameraMatrix.at<double>(1, 0) << " " << cameraMatrix.at<double>(1, 1) << " " << cameraMatrix.at<double>(1, 2) << std::endl;std::cout << cameraMatrix.at<double>(2, 0) << " " << cameraMatrix.at<double>(2, 1) << " " << cameraMatrix.at<double>(2, 2) << std::endl << std::endl;cv::Matx33f cMatrix(cameraMatrix);// 关键点和描述符向量std::vector<cv::KeyPoint> keypoints1;std::vector<cv::KeyPoint> keypoints2;cv::Mat descriptors1, descriptors2;// SIFT特征检测器cv::Ptr<cv::Feature2D> ptrFeature2D = cv::xfeatures2d::SIFT::create(500);// SIFT 特征及其描述符ptrFeature2D->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1);ptrFeature2D->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);std::cout << "Number of feature points (1): " << keypoints1.size() << std::endl;std::cout << "Number of feature points (2): " << keypoints2.size() << std::endl;// 匹配两张图像的描述符// 带有交叉检查的匹配器cv::BFMatcher matcher(cv::NORM_L2, true);// 匹配std::vector<cv::DMatch> matches;matcher.match(descriptors1, descriptors2, matches);// 绘制匹配cv::Mat imageMatches;cv::drawMatches(image1, keypoints1, // 第一张图像及其关键点image2, keypoints2,         // 第二张图像及其关键点matches,                    // 匹配imageMatches,               // 图像结果cv::Scalar(255, 255, 255),cv::Scalar(255, 255, 255),std::vector<char>(),cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);cv::namedWindow("Matches");cv::imshow("Matches", imageMatches);// 将关键点转换为 Point2fstd::vector<cv::Point2f> points1, points2;for (std::vector<cv::DMatch>::const_iterator it = matches.begin();it != matches.end(); ++it) {float x = keypoints1[it->queryIdx].pt.x;float y = keypoints1[it->queryIdx].pt.y;points1.push_back(cv::Point2f(x, y));x = keypoints2[it->trainIdx].pt.x;y = keypoints2[it->trainIdx].pt.y;points2.push_back(cv::Point2f(x, y));}std::cout << "Number of matches: " << points2.size() << std::endl;// 图像 1 和图像 2 之间的本质矩阵 cv::Mat inliers;cv::Mat essential = cv::findEssentialMat(points1, points2, cMatrix,                // 固有参数cv::RANSAC, 0.9, 1.0,   // RANSAC 方法 inliers);int numberOfPts(cv::sum(inliers)[0]);std::cout << "Number of inliers: " << numberOfPts << std::endl;// 绘制 inliers 匹配cv::drawMatches(image1, keypoints1, // 第一张图像及其关键点image2, keypoints2,         // 第二张图像及其关键点matches,                    // 匹配imageMatches,               // 图像结果cv::Scalar(255, 255, 255),cv::Scalar(255, 255, 255),inliers,cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);cv::namedWindow("Inliers matches");cv::imshow("Inliers matches", imageMatches);// 从本质矩阵中恢复相对相机姿态cv::Mat rotation, translation;cv::recoverPose(essential,      // 本质矩阵points1, points2,       // 匹配关键点cameraMatrix,           // 内在矩阵rotation, translation,  // 姿态估计inliers);               // inliers 匹配std::cout << "rotation:" << rotation << std::endl;std::cout << "translation:" << translation << std::endl;// 由 R, T 组成投影矩阵 cv::Mat projection2(3, 4, CV_64F);      // 3x4 投影矩阵rotation.copyTo(projection2(cv::Rect(0, 0, 3, 3)));translation.copyTo(projection2.colRange(3, 4));// 合成通用投影矩阵cv::Mat projection1(3, 4, CV_64F, 0.);  // 3x4 投影矩阵cv::Mat diag(cv::Mat::eye(3, 3, CV_64F));diag.copyTo(projection1(cv::Rect(0, 0, 3, 3)));std::cout << "First Projection matrix=" << projection1 << std::endl;std::cout << "Second Projection matrix=" << projection2 << std::endl;// inliersstd::vector<cv::Vec2d> inlierPts1;std::vector<cv::Vec2d> inlierPts2;// 为三角测量创建 inliers 输入点向量 int j(0); for (int i = 0; i < inliers.rows; i++) {if (inliers.at<uchar>(i)) {inlierPts1.push_back(cv::Vec2d(points1[i].x, points1[i].y));inlierPts2.push_back(cv::Vec2d(points2[i].x, points2[i].y));}}// 矫正并归一化图像点std::vector<cv::Vec2d> points1u;cv::undistortPoints(inlierPts1, points1u, cameraMatrix, cameraDistCoeffs);std::vector<cv::Vec2d> points2u;cv::undistortPoints(inlierPts2, points2u, cameraMatrix, cameraDistCoeffs);// 三角测量std::vector<cv::Vec3d> points3D;triangulate(projection1, projection2, points1u, points2u, points3D);// 创建 viz 窗口cv::viz::Viz3d visualizer("Viz window");visualizer.setBackgroundColor(cv::viz::Color::white());// 场景重建// 创建第 1 个虚拟相机cv::viz::WCameraPosition cam1(cMatrix,      // 内在矩阵image1,                             // 平面图像1.0,                                // 缩放因子cv::viz::Color::black());// 创建第 2 个虚拟相机cv::viz::WCameraPosition cam2(cMatrix,      // 内在矩阵image2,                             // 平面图像1.0,                                // 缩放因子cv::viz::Color::black());// 选择可视化点cv::Vec3d testPoint = triangulate(projection1, projection2, points1u[124], points2u[124]);cv::viz::WSphere point3D(testPoint, 0.05, 10, cv::viz::Color::red());// 相关投影线double lenght(4.);cv::viz::WLine line1(cv::Point3d(0., 0., 0.), cv::Point3d(lenght*points1u[124](0), lenght*points1u[124](1), lenght), cv::viz::Color::green());cv::viz::WLine line2(cv::Point3d(0., 0., 0.), cv::Point3d(lenght*points2u[124](0), lenght*points2u[124](1), lenght), cv::viz::Color::green());// 重建 3D 点云cv::viz::WCloud cloud(points3D, cv::viz::Color::blue());cloud.setRenderingProperty(cv::viz::POINT_SIZE, 3.);// 添加虚拟对象visualizer.showWidget("Camera1", cam1);visualizer.showWidget("Camera2", cam2);visualizer.showWidget("Cloud", cloud);visualizer.showWidget("Line1", line1);visualizer.showWidget("Line2", line2);visualizer.showWidget("Triangulated", point3D);// 移动第二张图像cv::Affine3d pose(rotation, translation);visualizer.setWidgetPose("Camera2", pose);visualizer.setWidgetPose("Line2", pose);// 可视化动画while (cv::waitKey(100) == -1 && !visualizer.wasStopped()){visualizer.spinOnce(1,  // 暂停 1ms true);          // 重绘}cv::waitKey();return 0;
}

小结

3D 重建是计算机视觉中一个重要的研究领域,在本节中,我们使用不同视图中图像点之间的对应关系来推断 3D 信息,使用 cv::findEssentialMat 函数计算本质矩阵,并介绍了三角测量的原理以从 2D 图像重建 3D 点。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(2)——OpenCV核心数据结构
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解
OpenCV实战(11)——形态学变换详解
OpenCV实战(12)——图像滤波详解
OpenCV实战(13)——高通滤波器及其应用
OpenCV实战(14)——图像线条提取
OpenCV实战(15)——轮廓检测详解
OpenCV实战(16)——角点检测详解
OpenCV实战(17)——FAST特征点检测
OpenCV实战(18)——特征匹配
OpenCV实战(19)——特征描述符
OpenCV实战(20)——图像投影关系
OpenCV实战(21)——基于随机样本一致匹配图像
OpenCV实战(22)——单应性及其应用
OpenCV实战(23)——相机标定
OpenCV实战(24)——相机姿态估计

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/256482.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

用 GPT-4 来面试,简直开挂啊!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 众所周知&#xff0c;ChatGPT 凭其超强的文本生成能力&#xff0c;成为了 2023 年最为火爆的 AI 应用之一。 几个月前&#xff0c;GPT-4 发布&#xff0c;又将 ChatGPT 的能力提升到了一个…

.Net Core——用代码写代码?

想要用代码写代码&#xff0c;肯定是绕不开反射的。反射的概念相比都不陌生&#xff0c;只是应用多少就因人而异&#xff0c;今天分享一个代码生成器的思路&#xff0c;仅供参考&#xff0c;不要过分依赖哦。 思路分析 众所周知&#xff0c;利用反射可以在程序运行时获取到任…

JAVA企业级开发 1.5 初探Spring AOP

一、提出游吟诗人唱赞歌任务 骑士执行任务前和执行任务后&#xff0c;游吟诗人唱赞歌 &#xff08;一&#xff09;采用传统方式实现 修改day04子包的勇敢骑士类 修改day04子包里的救美骑士类 执行测试类 - TestKnight &#xff08;二&#xff09;采用传统方式实现的缺…

【JavaSE】Java基础语法(三十九):网络编程入门

文章目录 1. 网络编程概述2. 网络编程三要素3. IP地址4. InetAddress5. 端口和协议 1. 网络编程概述 计算机网络 是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络 操作系统&#xff0c;网络管理软件及网络通信协…

欧拉与莫比乌斯

更多文章可以在本人的个人小站&#xff1a;https://kaiserwilheim.github.io 查看。 转载请注明出处。 初稿写于2021-10-10&#xff0c; 再修改于2022-02-07 Achtung: 本文章使用p来代指“任意质数”&#xff0c;请勿混淆。 首先让我们膜拜一下莱昂哈德欧拉(Leonhard Euler)…

贝塞尔

贝塞尔曲线可视化链接 介绍&#xff1a; 贝塞尔曲线&#xff0c;又称贝兹曲线或贝济埃曲线&#xff0c;是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线&#xff0c;贝兹曲线由线段与节点组成&#xff0c;节点是可拖动的支点&#xff0c;线段像可…

详解人工智能的五大思想流派 元芳你支持哪一派?

▼ 点击上方蓝字 关注网易智能 聚焦AI&#xff0c;读懂下一个大时代&#xff01; 【网易智能讯 3月1日消息】未来的就业形势还能依靠科技巨头和首席执行官们来决定&#xff0c;而人工智能的未来&#xff0c;依旧充满了太多的不确定性。 这一状况是源自于人工智能及其在科技行业…

【科大讯飞】全球首款,Mobius莫比斯同声翻译耳机 ,AI智能运动耳机 ,支持英日法韩俄西班牙6种语音...

© 程序员严选 丨 为您甄选全球好物 科大讯飞重磅推出 翻译界的最新黑科技神器 同声翻译 智能耳机 对方说外语&#xff0c;耳机就会同声语音翻译出来哦~ 。。。 著名语音AI品牌科大讯飞与咪咕联合打造了一款智能翻译耳机&#xff0c;全球首款全语音人工智能耳机——Mobius…

DailyMart03:如何基于DDD设计商城的领域模型?

大家好&#xff0c;我是飘渺。既然有人催更那今天咱们就继续更新DDD&微服务系列&#xff01; 在面向对象开发中&#xff0c;所有事物都可以看作是对象。然而&#xff0c;在日常开发中&#xff0c;我们通常从数据出发来设计对象的表现形式&#xff0c;这种做法侧重于数据属性…

哈萨比斯的人类补完计划

在著名动漫《新世纪福音战士》里&#xff0c;碇源堂和他背后的SEELE组织始终在执行一项叫做“人类补完计划”的神秘行动。 这个计划到底是什么意思&#xff0c;粉丝们已经争吵了很多年。但大体上应该是说利用“神性”来补完人类族群&#xff0c;从而消除人类社会中的种种问题。…

阿基里斯之踵

阿基里斯是古希腊神话中最伟大的英雄之一。相传&#xff0c;他的母亲是一位女神&#xff0c;在他降生之初&#xff0c;女神为了使他长生不死&#xff0c;将他浸入冥河洗礼。阿基里斯从此刀枪不入&#xff0c;百毒不侵&#xff0c;只有一点除外———他的脚踵当时被女神提在手中…

麦比乌斯带

数学家们吐露&#xff0c;麦比乌斯带只有单面&#xff0c;如果你要将它分成两半&#xff0c;你将会感到十分可笑&#xff0c;因为分开后还是一条带。 莫比乌斯环的奇妙之处有三&#xff1a; 一、莫比乌斯环只存在一个面。 二、如果沿着莫比乌斯环的中间剪开&#xff0c;将会形成…

数字与能源,交织成新基建的摩比斯环

提到新基建&#xff0c;大家可能会首先想起大数据、AI、云计算组成的数字产业&#xff0c;以及高铁、城轨、新能源汽车构成的交通产业。但如果你留心分析&#xff0c;会发现新基建的体系里还有一条“暗线”——那就是能源。 无论直接指向能源升级的特高压、充电桩&#xff0c;还…

塞尔希奥·阿奎罗和 The Sandbox 携手合作,激活元宇宙足球迷!

五次英超联赛冠军兼创纪录的得分球员选择了 The Sandbox 平台来创建他的第一个虚拟世界。 简要介绍 阿根廷在串流媒体和游戏领域上的足球传奇人物和全球典范将继续建立新的数字社区&#xff0c;这一次是与 The Sandbox 中的独特空间 Kuniverse。 来自世界各地的球迷将能够关注阿…

莫比乌斯详细介绍

莫比乌斯反演 莫比乌斯反演是数论数学中很重要的内容&#xff0c;可以用于解决很多组合数学的问题。 莫比乌斯函数 莫比乌斯函数&#xff0c;数论函数&#xff0c;由德国数学家和天文学家莫比乌斯提出。梅滕斯首先使用μ(n)作为莫比乌斯函数的记号。 莫比乌斯函数是指以下的…

无主之地kill ajax,阿克斯顿 - 无主之地中文维基 - 灰机wiki

阿克斯顿 艾克斯顿和军刀枪塔 角色类型可选角色(无主之地2) NPC(无主之地&#xff1a;前奏) 性别男性 种族人类 Axton is the playable Commando class character in Borderlands 2 Launch Date Trailer. 背景 Originally from Hieronymous, Axton spent ten years with the Da…

Mahalanobis(马哈拉诺比斯)距离

马氏距离(Mahalanobis Distance)是一种距离的度量&#xff0c;可以看作是欧氏距离的一种修正&#xff0c;修正了欧式距离中各个维度尺度不一致且相关的问题。 马氏距离&#xff08;Mahalanobis Distance&#xff09;是由马哈拉诺比斯&#xff08;P. C. Mahalanobis&#xff09;…

通用寄存器-汇编复习(1)

弄清寄存器表达,原理和配件及汇编实验验证。 往期文章: 汇编语言基础-汇编复习(0)_luozhonghua2000的博客-CSDN博客 一个典型的 CPU(此处讨论的不是某一具体的 CPU)由运算器、控制器、寄存器(CPU工作原理)等器件构成,这些器件靠内部总线相连。前一章所说的总线,相对于 CP…

想把手机内容投屏到电脑 并且可以用电脑控制手机怎么办,很简单

首先打开设置&#xff0c;点击应用》可选功能 点击 查看功能 搜索 无线显示器点击下一步 点击安装 等待安装完成 完成后我们打开 系统》投影到此电脑 把设置改为以下选项&#xff0c;然后单击启动两家应用以投影到此电脑 出现这个画面就对了&#xff0c;接下来我们开始调试手…