【opencv】教程代码 —features2D(4)利用两张摄像机拍摄的图片计算单应性矩阵...

c011209281ae0e106040e01dc4bfab67.png

homography_from_camera_displacement.cpp

3c402bf19ec1bddb8a29a20d23b16c94.png

Chessboard poses 棋盘姿态

1641b7fc55e4c3e5ced790fe5ed1c22e.png

使用根据相机位移计算的单应性扭曲图像

a3f7f7f6404160a24bb86a6fca3efa10.png

使用根据绝对相机姿势计算的单应性扭曲图像

415bf793b7c344a67510d6e1b49678c0.png

Warped images comparison 扭曲图像比较

左侧-nfindHomography  右侧-使用根据相机位移计算的单应性扭曲图像

终端输出:

Euclidean Homography:(根据相机位移计算单应性)
[0.2215344870864446, -0.9949332344575121, 0.1140657984013492;0.6776201343947539, 0.1839368762588706, -0.1530250021245755;0.3300066201371719, -0.5683454599466965, 1]
Euclidean Homography 2:(同上,但使用绝对相机姿势而不是相机位移 仅供检查)
[0.2215344870864446, -0.9949332344575118, 0.1140657984013491;0.6776201343947537, 0.1839368762588706, -0.1530250021245755;0.3300066201371718, -0.5683454599466965, 1]findHomography H:
[0.3290339333220099, -1.244138808862929, 536.4769088231476;0.6969763913334047, -0.0893590907257152, -80.3406850408241;0.0004051172959296097, -0.001079740100565012, 1]
homography from camera displacement:(相机位移的单应性)
[0.4160569974777896, -1.306889022263172, 553.7055455031656;0.7917584238390836, -0.06341244817498765, -108.2770026444472;0.0005926357279773245, -0.00102065172285972, 1]
homography from absolute camera poses:(绝对相机姿势的单应性)
[0.4160569974777895, -1.306889022263171, 553.7055455031656;0.7917584238390833, -0.06341244817498759, -108.2770026444472;0.0005926357279773244, -0.00102065172285972, 1]

d9fdeae07ea7e41f52e738ff039ed57f.png

// 包含必要的库
#include <iostream> // 用于基本输入输出
#include <opencv2/core.hpp> // 包含OpenCV库的核心部分
#include <opencv2/imgproc.hpp> // 包含OpenCV库的图像处理部分
#include <opencv2/highgui.hpp> // 包含OpenCV库的高级GUI部分
#include <opencv2/calib3d.hpp> // 包含OpenCV库的相机标定和3D重建部分using namespace std; // 使用标准命名空间
using namespace cv; // 使用OpenCV命名空间namespace // 定义一个无名命名空间
{
enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID }; // 定义一个枚举类型Pattern,包含棋盘、圆形网格和非对称圆形网格三种模式void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners, Pattern patternType = CHESSBOARD)
// 定义一个函数,用于计算棋盘角点
{corners.resize(0); // 重置角点向量switch (patternType) // 根据模式类型进行不同的操作{case CHESSBOARD:case CIRCLES_GRID:for( int i = 0; i < boardSize.height; i++ ) // 遍历棋盘的每一行for( int j = 0; j < boardSize.width; j++ ) // 遍历棋盘的每一列corners.push_back(Point3f(float(j*squareSize), // 将计算得到的角点坐标添加到角点向量中float(i*squareSize), 0));break;case ASYMMETRIC_CIRCLES_GRID:for( int i = 0; i < boardSize.height; i++ ) // 遍历棋盘的每一行for( int j = 0; j < boardSize.width; j++ ) // 遍历棋盘的每一列corners.push_back(Point3f(float((2*j + i % 2)*squareSize), // 将计算得到的角点坐标添加到角点向量中float(i*squareSize), 0));break;default:CV_Error(Error::StsBadArg, "Unknown pattern type\n"); // 如果模式类型未知,则抛出错误}
}//! [compute-homography]
Mat computeHomography(const Mat &R_1to2, const Mat &tvec_1to2, const double d_inv, const Mat &normal)
// 定义一个函数,用于计算单应性矩阵
{Mat homography = R_1to2 + d_inv * tvec_1to2*normal.t(); // 计算单应性矩阵return homography; // 返回单应性矩阵
}
//! [compute-homography]Mat computeHomography(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,const double d_inv, const Mat &normal)
// 定义一个函数,用于计算单应性矩阵
{Mat homography = R2 * R1.t() + d_inv * (-R2 * R1.t() * tvec1 + tvec2) * normal.t(); // 计算单应性矩阵return homography; // 返回单应性矩阵
}//! [compute-c2Mc1]
void computeC2MC1(const Mat &R1, const Mat &tvec1, const Mat &R2, const Mat &tvec2,Mat &R_1to2, Mat &tvec_1to2)
// 定义一个函数,用于计算相机位移
{//c2Mc1 = c2Mo * oMc1 = c2Mo * c1Mo.inv()R_1to2 = R2 * R1.t(); // 计算旋转矩阵tvec_1to2 = R2 * (-R1.t()*tvec1) + tvec2; // 计算平移向量
}
//! [compute-c2Mc1]void homographyFromCameraDisplacement(const string &img1Path, const string &img2Path, const Size &patternSize,const float squareSize, const string &intrinsicsPath)
// 定义一个函数,用于根据相机位移计算单应性矩阵
{Mat img1 = imread( samples::findFile( img1Path ) ); // 读取第一张图片Mat img2 = imread( samples::findFile( img2Path ) ); // 读取第二张图片//! [compute-poses]vector<Point2f> corners1, corners2; // 定义两个角点向量bool found1 = findChessboardCorners(img1, patternSize, corners1); // 在第一张图片中寻找棋盘角点bool found2 = findChessboardCorners(img2, patternSize, corners2); // 在第二张图片中寻找棋盘角点if (!found1 || !found2) // 如果在任何一张图片中都找不到棋盘角点{cout << "Error, cannot find the chessboard corners in both images." << endl; // 输出错误信息return; // 返回}vector<Point3f> objectPoints; // 定义一个对象点向量calcChessboardCorners(patternSize, squareSize, objectPoints); // 计算棋盘角点FileStorage fs( samples::findFile( intrinsicsPath ), FileStorage::READ); // 打开内参文件Mat cameraMatrix, distCoeffs; // 定义相机矩阵和畸变系数fs["camera_matrix"] >> cameraMatrix; // 读取相机矩阵fs["distortion_coefficients"] >> distCoeffs; // 读取畸变系数Mat rvec1, tvec1; // 定义旋转向量和平移向量solvePnP(objectPoints, corners1, cameraMatrix, distCoeffs, rvec1, tvec1); // 求解PnP问题Mat rvec2, tvec2; // 定义旋转向量和平移向量solvePnP(objectPoints, corners2, cameraMatrix, distCoeffs, rvec2, tvec2); // 求解PnP问题//! [compute-poses]Mat img1_copy_pose = img1.clone(), img2_copy_pose = img2.clone(); // 复制两张图片Mat img_draw_poses; // 定义一个用于绘制姿态的图像drawFrameAxes(img1_copy_pose, cameraMatrix, distCoeffs, rvec1, tvec1, 2*squareSize); // 在第一张图片上绘制坐标轴drawFrameAxes(img2_copy_pose, cameraMatrix, distCoeffs, rvec2, tvec2, 2*squareSize); // 在第二张图片上绘制坐标轴hconcat(img1_copy_pose, img2_copy_pose, img_draw_poses); // 将两张图片水平拼接imshow("Chessboard poses", img_draw_poses); // 显示棋盘姿态//! [compute-camera-displacement]Mat R1, R2; // 定义两个旋转矩阵Rodrigues(rvec1, R1); // 将旋转向量转换为旋转矩阵Rodrigues(rvec2, R2); // 将旋转向量转换为旋转矩阵Mat R_1to2, t_1to2; // 定义旋转矩阵和平移向量computeC2MC1(R1, tvec1, R2, tvec2, R_1to2, t_1to2); // 计算相机位移Mat rvec_1to2; // 定义旋转向量Rodrigues(R_1to2, rvec_1to2); // 将旋转矩阵转换为旋转向量//! [compute-camera-displacement]//! [compute-plane-normal-at-camera-pose-1]//平面法线在相机1坐标系下的坐标表示Mat normal = (Mat_<double>(3,1) << 0, 0, 1); // 定义法线向量Mat normal1 = R1*normal; // 计算第一帧的法线向量//! [compute-plane-normal-at-camera-pose-1]//! [compute-plane-distance-to-the-camera-frame-1]//世界坐标系原点在相机1坐标系下的坐标表示origin1 Mat origin(3, 1, CV_64F, Scalar(0)); // 定义原点Mat origin1 = R1*origin + tvec1; // 计算第一帧的原点double d_inv1 = 1.0 / normal1.dot(origin1); // 计算平面到第一帧相机坐标系原点的距离的倒数//! [compute-plane-distance-to-the-camera-frame-1]//!根据相机位移(位姿变化)计算单应性 [compute-homography-from-camera-displacement]Mat homography_euclidean = computeHomography(R_1to2, t_1to2, d_inv1, normal1); // 计算欧几里得单应性矩阵Mat homography = cameraMatrix * homography_euclidean * cameraMatrix.inv(); // 计算单应性矩阵homography /= homography.at<double>(2,2); // 归一化单应性矩阵homography_euclidean /= homography_euclidean.at<double>(2,2); // 归一化欧几里得单应性矩阵//! [compute-homography-from-camera-displacement]//Same but using absolute camera poses instead of camera displacement, just for checkMat homography_euclidean2 = computeHomography(R1, tvec1, R2, tvec2, d_inv1, normal1); // 计算欧几里得单应性矩阵Mat homography2 = cameraMatrix * homography_euclidean2 * cameraMatrix.inv(); // 计算单应性矩阵homography_euclidean2 /= homography_euclidean2.at<double>(2,2); // 归一化欧几里得单应性矩阵homography2 /= homography2.at<double>(2,2); // 归一化单应性矩阵cout << "\nEuclidean Homography:\n" << homography_euclidean << endl; // 输出欧几里得单应性矩阵cout << "Euclidean Homography 2:\n" << homography_euclidean2 << endl << endl; // 输出欧几里得单应性矩阵//! 估计单应性矩阵H  corners2 = H * corners1   [estimate-homography]Mat H = findHomography(corners1, corners2); // 估计单应性矩阵cout << "\nfindHomography H:\n" << H << endl; // 输出估计的单应性矩阵//! [estimate-homography]cout << "homography from camera displacement:\n" << homography << endl; // 输出由相机位移计算得到的单应性矩阵cout << "homography from absolute camera poses:\n" << homography2 << endl << endl; // 输出由绝对相机姿态计算得到的单应性矩阵//! [warp-chessboard]Mat img1_warp; // 定义一个变形后的图像warpPerspective(img1, img1_warp, H, img1.size()); // 对第一张图片进行透视变换//! [warp-chessboard]Mat img1_warp_custom; // 定义一个自定义的变形后的图像warpPerspective(img1, img1_warp_custom, homography, img1.size()); // 对第一张图片进行透视变换imshow("Warped image using homography computed from camera displacement", img1_warp_custom); // 显示由相机位移计算得到的单应性矩阵变形后的图像Mat img_draw_compare; // 定义一个用于比较的图像hconcat(img1_warp, img1_warp_custom, img_draw_compare); // 将两张变形后的图像水平拼接imshow("Warped images comparison", img_draw_compare); // 显示比较后的图像Mat img1_warp_custom2; // 定义一个自定义的变形后的图像warpPerspective(img1, img1_warp_custom2, homography2, img1.size()); // 对第一张图片进行透视变换imshow("Warped image using homography computed from absolute camera poses", img1_warp_custom2); // 显示由绝对相机姿态计算得到的单应性矩阵变形后的图像waitKey(); // 等待用户按键
}const char* params= "{ help h         |       | print usage }""{ image1         | left02.jpg | path to the source chessboard image }""{ image2         | left01.jpg | path to the desired chessboard image }""{ intrinsics     | left_intrinsics.yml | path to camera intrinsics }""{ width bw       | 9     | chessboard width }""{ height bh      | 6     | chessboard height }""{ square_size    | 0.025 | chessboard square size }";// 定义一个参数列表,包含帮助信息、图像路径、内参路径、棋盘宽度、棋盘高度和棋盘方格大小}int main(int argc, char *argv[]) // 主函数
{CommandLineParser parser(argc, argv, params); // 定义一个命令行解析器if (parser.has("help")) // 如果用户请求帮助{parser.about("Code for homography tutorial.\n""Example 3: homography from the camera displacement.\n"); // 输出关于信息parser.printMessage(); // 打印消息return 0; // 返回0}Size patternSize(parser.get<int>("width"), parser.get<int>("height")); // 获取棋盘的尺寸float squareSize = (float) parser.get<double>("square_size"); // 获取棋盘方格的大小homographyFromCameraDisplacement(parser.get<String>("image1"), // 调用函数,计算单应性矩阵parser.get<String>("image2"),patternSize, squareSize,parser.get<String>("intrinsics"));return 0; // 返回0
}

上面的C++代码是使用OpenCV库来实现基于棋盘格的摄像头标定、计算相机姿态、以及基于相机位移计算单应性矩阵。主要步骤如下:

  1. 读取两张包含棋盘格的图片。

  2. 检测棋盘格的角点。

  3. 计算角点在世界坐标系中的位置。

  4. 读取摄像头参数(内参和畸变参数)。

  5. 使用solvePnP方法分别计算两张图片中棋盘格的旋转向量和平移向量

  6. 计算棋盘格从第一张图片到第二张图片的相机位移(旋转矩阵和平移向量)。

  7. 计算单应性矩阵,将第一张图片中的棋盘格投影到第二张图片的视角

  8. 显示两种方式计算得到的单应性矩阵

  9. 使用单应性矩阵对第一张图片进行变换,以便与第二张图片对齐

该代码对于相机标定和三维重建的学习很有帮助,特别是在理解和应用单应性矩阵计算以实现图像配准和视角变换方面。

7e7d300af7f51d3fc4b6b51cefbe1658.png

863a35bbd1600c2f8e58d29961af2f11.png

18c452fb981015c06d61bb75ff3f49b9.png

单应性矩阵的物理意义

d4a741efd9b614d1e0743b182b8d2b94.png

963e2091af3523c6e2310c9c29318df8.png

如何计算单应性矩阵

564e3bcb997fcaf1ffca1b8b1e8964ce.png

Mat homography = R_1to2 + d_inv * tvec_1to2*normal.t();

46d4d4943b222f6542cbb2e5ea535312.png

solvePnP(objectPoints, corners1, cameraMatrix, distCoeffs, rvec1, tvec1);

cbaebc40ae37f48fbed5abe0c10a3cbd.png

drawFrameAxes(img1_copy_pose, cameraMatrix, distCoeffs, rvec1, tvec1, 2*squareSize);

8c36beeb2041b3ec977cb50a17a9669d.png

CV_EXPORTS_W void Rodrigues( InputArray src, OutputArray dst, OutputArray jacobian = noArray() );

0a466644d216d96fb5cb5f3d5c1aa0e2.png

Mat H = findHomography(corners1, corners2);

6c48881ef5143bbfcedde7ceffe11710.png

homography 与 homography_euclidean的区别

8824b76c66e4dfda2d17c70df8e9f2fb.png

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Electron 打包自定义NSIS脚本为安装向导增加自定义页面增加输入框

Electron 打包工具有很多&#xff0c;如Electron-build、 Electron Forge 等&#xff0c;这里使用Electron-build&#xff0c;而Electron-build使用了nsis组件来创建安装向导&#xff0c;默认情况nsis安装向导不能自定义安装向导界面&#xff0c;但是nsis提供了nsis脚本可以扩展…

Express框架搭建项目 node.js

文章目录 引言Express框架介绍express安装环境准备写一个简单的项目展示 文章总结 引言 Express是一个基于Node.js平台的轻量级Web应用框架&#xff0c;它提供了简洁的API和丰富的功能&#xff0c;使得开发者能够快速地构建Web服务器和API。本文将带领大家从零开始&#xff0c…

企业管理新思考:利润率与质量在创业路上的重要性

一、引言 在当下这个充满变革与挑战的商业环境中&#xff0c;创业者和企业家们时常面临着规模扩张与利润增长之间的权衡。著名天使投资人吴世春先生的一席话&#xff0c;为我们指明了方向&#xff1a;“做企业利润率优先于规模&#xff0c;质量优先于数量。”这一深刻见解&…

unity 使用Base64编码工具对xml json 或者其他文本进行加密 解密

Base64编码加密解密工具 这是一个加密解密的网页工具&#xff0c;别人可以把他加密后的字符串给你&#xff0c;然后你可以用代码解密出来&#xff0c; 或者自己对内容进行加密&#xff0c;解密处理。 /// <summary>/// Base64 解码/// </summary>string DecodeBase…

【WEEK6】 【DAY3】MySQL函数【中文版】

2024.4.3 Wednesday 目录 5.MySQL函数5.1.常用函数5.1.1.数据函数5.1.2.字符串函数5.1.2.1.CHAR_LENGTH(str)计算字符串str长度5.1.2.2.CONCAT(str1,str2,...)拼接字符串str1 str2 ...5.1.2.3.INSERT(str,pos,len,newstr)把原文str第pos位开始长度为len的字符串替换成newstr5.…

WPF上使用MaterialDesign框架---下载与配置

一、介绍&#xff1a; Material Design语言的一些重要功能包括 系统字体Roboto的升级版本 &#xff0c;同时颜色更鲜艳&#xff0c;动画效果更突出。杜拉特还简要谈到了新框架的一些变化。谷歌的想法是让谷歌平台上的开发者掌握这个新框架&#xff0c;从而让所有应用就有统一的…

DETR【Transformer+目标检测】

End-to-End Object Detection with Transformers 2024 NVIDIA GTC&#xff0c;发布了地表最强的GPU B200&#xff0c;同时&#xff0c;黄仁勋对谈《Attention is All You Need》论文其中的7位作者&#xff0c;座谈的目的无非就是诉说&#xff0c;Transformer才是今天人工智能成…

技术揭秘:如何打造完美互动的充电桩硬件与服务平台?

充电桩平台全套源码地址 https://gitee.com/chouleng/cdzkjjh.git 这张图像是一个系统或服务的架构图。以下是对图中各个部分的描述&#xff1a; 前端&#xff1a; 位于图像的顶部&#xff0c;颜色为浅绿色。用户服务端&#xff1a; 紧邻前端&#xff0c;颜色为淡黄色。设备服…

基于深度学习的肿瘤图像检测系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)

摘要&#xff1a;在本博客中&#xff0c;我们深入探讨了基于YOLOv8/v7/v6/v5的肿瘤图像检测系统。核心上&#xff0c;我们采用了最新的YOLOv8技术&#xff0c;并将其与YOLOv7、YOLOv6、YOLOv5算法进行了综合整合和性能指标对比分析。我们详细阐述了当前国内外在此领域的研究现状…

个人医疗开支预测项目

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; 项目背景 随着医疗成本的持续上涨&#xff0c;个人医疗开支成为一个重要议题。理解影响医疗费用的多种因素对于医疗保险公司、政府机构以及个人…

Java零基础入门-java8新特性(下篇)

一、概述 ​上几期&#xff0c;我们是完整的学完了java异常类的学习及实战演示、以及学习了线程进程等基础概念&#xff0c;而这一期&#xff0c;我们要来玩点好的东西&#xff0c;那就是java8&#xff0c;我们都知道java8是自2004年发布java5之后最重要且一次重大的版本更新&…

Mac OS上使用matplotlib库显示中文字体

文章目录 问题描述解决步骤参考文章 问题描述 如果我们想要使用matplotlib画图的话&#xff0c;可能会出现下面的这种warning: UserWarning: Glyph 24212 (\N{CJK UNIFIED IDEOGRAPH-5E94}) missing from current font.解决步骤 解决这个问题&#xff0c;可以按照下面的做法…

SpringBoot全局异常处理

问题 当我们没有做任何的异常处理时&#xff0c;我们三层架构处理异常的方案&#xff1a; Mapper接口在操作数据库的时候出错了&#xff0c;此时异常会往上抛(谁调用Mapper就抛给谁)&#xff0c;会抛给service。 service 中也存在异常了&#xff0c;会抛给controller。 而在…

Python基于深度学习的人脸识别项目源码+演示视频,利用OpenCV进行人脸检测与识别 preview

​ 一、原理介绍 该人脸识别实例是一个基于深度学习和计算机视觉技术的应用&#xff0c;主要利用OpenCV和Python作为开发工具。系统采用了一系列算法和技术&#xff0c;其中包括以下几个关键步骤&#xff1a; 图像预处理&#xff1a;首先&#xff0c;对输入图像进行预处理&am…

鸿蒙南向开发案例:【智能养花机】

样例简介 智能养花机通过感知花卉、盆栽等植宠生长环境的温度、湿度信息&#xff0c;适时为它们补充水分。在连接网络后&#xff0c;配合数字管家应用&#xff0c;用户可远程进行浇水操作。用户还可在应用中设定日程&#xff0c;有计划的按日、按周进行浇水。在日程中用户可添…

Servlet原理Servlet API

目录 一、Servlet运行原理 1.1、问题 1.2、Servlet的具体执行过程 1.3、Tomcat初始化流程小结 1.4、Tomcat处理请求流程 二、Servlet API详解 2.1、HttpServlet类 2.1.1、处理Get请求 2.2、HttpServletRequest类 2.3、HttpServletResponse类 2.3.1、设置状态码 ​2.…

二维码的生成、下载Java,并返回给前端展示

分析 将生成的二维码图片&#xff0c;以IO流的方式&#xff0c;通过response响应体直接返回给请求方。 第一、不需要落到我们的磁盘&#xff0c;操作在内存中完成&#xff0c;效率比较高。 第二、所有生成二维码的请求&#xff0c;都可以访问这里&#xff0c;前端直接拿img标…

【tools】Lokalise 可用于本地化各种类型的应用程序和网站

【tools】Lokalise 可用于本地化各种类型的应用程序和网站 1. Lokalise 基本功能2. Lokalise 可用于本地化各种类型的应用程序和网站,那部署的应用程序和网站运行再什么地方,数据存储再什么位置?https://app.lokalise.com/quick-start 1. Lokalise 基本功能 Lokalise 是一款…

RUST语言基本数据类型认识

1.RUST的基本数据类型参考: 2.使用RUST数据类型声明变量并赋值: let a:i81;//8位有符号整数let a1:u82;//8位无符号整数let b:i161;//16位有符号整数let b1:u162;//16位无符号整数let c:i321;//32位有符号整数let c1:u322;//32位无符号整数let d:i641;//64位有符号整数let d1:u…

Java零基础入门-java8新特性(上篇)

一、本期教学目标 java8有哪些新特性什么是函数式接口什么是Lambda表达式掌握Stream ApiStream和Collect集合区别Stream创建方式Stream操作三步骤 二、概述 上几期&#xff0c;我们是完整的学完了java异常类的学习及实战演示、以及学习了线程进程等基础概念&#xff0c;而这一…