opencv对相机进行畸变矫正,及矫正前后的坐标对应

文章目录

  • 1.背景
  • 2.需求分析
  • 3.解决方案
    • 3.1.镜头畸变矫正
    • 3.2.知道矫正后的画面坐标(x, y),求其在原画面的坐标(x', y')
    • 3.2.知道原画面坐标(x1, y1),求其在矫正后的画面坐标(x2, y2)
  • 4.效果
  • 5.代码

1.背景

目前有个项目,需要用到热成像相机。但是这个热成像相机它的畸变比较厉害,因此需要用标定板进行标定,从而消除镜头畸变。
同时需要实现用户用鼠标点击矫正后的画面后,显示用户点击位置的像素所代表的温度。
另外热成像sdk中还有个功能:选定一个rect,可以返回这个rect中的最高最低温度以及其各自的位置。假如我们需要这个功能,那么又需要知道从src到dst的关系了。

2.需求分析

消除镜头畸变后,就不能直接使用热成像sdk提供的函数来查询像素对应的温度。
因为在查询函数中,有个像素坐标的形参,要求传入原来的热成像图像A的像素坐标,函数返回此像素位置的温度。
而我们经过畸变消除后,得到画面B。B上面的特定像素所处的坐标和原图不一定一样。
因此,假如用户想查询画面B上的某个像素点的有效温度,就必须要取得此像素点在原图A上的位置坐标。
而在知道原图的最高最低温度点的位置后,需要知道其在纠正后的画面中的位置,才能准确绘制出来。
总结一下,需要实现以下功能:
a、镜头畸变矫正
b、知道矫正后的画面坐标(x,y),求其在原画面的坐标(x’,y’)
c、知道原画面坐标(x1,y1),求其在矫正后的画面坐标(x2,y2)

3.解决方案

其实很简单,opencv本身就提供了。

3.1.镜头畸变矫正

在经过 findChessboardCorners、calibrateCamera之后,我们就已经获得了相机矩阵cameraMatrix、畸变矩阵distCoeffs。
然后,我们利用getOptimalNewCameraMatrix,获得了一个相对容易控制画面取舍的新相机矩阵newCamMatrix。
接下来,就有两种方式对画面进行矫正:
a、直接undistort。
b、先利用initUndistortRectifyMap得到map1、map2,然后再利用remap进行画面矫正。
后面的代码把两种都演示了。

3.2.知道矫正后的画面坐标(x, y),求其在原画面的坐标(x’, y’)

其实,我们真正需要的是第二种。
关键就在于map1、map2。
这两个矩阵是什么玩意呢?
其实你先看看他们的尺寸、通道数,再查阅一下资料就知道了:
map1、map2的尺寸与目标图像(矫正后的图像)的尺寸一致,而通道数为1(这个其实不一定,与其他参数有关,暂时先这样认为)。我们假设,最终图像(x,y)处的像素来源于源图像(x’,y’)处,那么,map1中存储了坐标(x’,y’)中的x’,而map2中存储了y’。
虽然我描述得很混乱,但是你配合代码应该明白我在说什么。😁
所以,我们直接利用这个map1、map2就可以实现从消除畸变后的画面坐标转换到原画面的坐标了。

    initUndistortRectifyMap(cameraMatrix, distCoeffs, cv::Mat(), newCamMatrix, imageSize, CV_32FC1, map1, map2);......Point dstPt(400, 109);double pt_x = map1.at<float>(dstPt);double pt_y = map2.at<float>(dstPt);

3.2.知道原画面坐标(x1, y1),求其在矫正后的画面坐标(x2, y2)

这个可以利用opencv的undistortPoints函数进行求解。
需要注意的是第三个参数使用相机矩阵、第五个参数使用空矩阵、第六个参数使用新相机矩阵。这些参数需要和initUndistortRectifyMap的想对应起来。

    vector<Point2f> srcPts;srcPts.push_back(Point2f(300, 145));vector<Point2f> dstPts;undistortPoints(srcPts, dstPts, cameraMatrix, distCoeffs, Mat(), newCamMatrix);

4.效果

由于一些原因,我不能直接展示我的效果图。这里用opencv自带的图像来演示吧。
在这里插入图片描述

5.代码

int cameraCalibration()
{Size boardSize = {9, 6};float squareSize = 0.05;bool displayCorners = false;vector<string> imageList;for(int i = 0; i < 9; i++){QString leftImgFile = QString("../data/left%1.jpg").arg(i + 1, 2, 10, QLatin1Char('0'));imageList.push_back(leftImgFile.toStdString());}// 存放相机的图像的角点位置vector<vector<Point2f>> imagePoints;// 存放实际的物体坐标vector<vector<Point3f>> objectPoints;Size imageSize;int i, j, nimages = imageList.size();imagePoints.resize(nimages);// 存放能够顺利找到角点的图像的路径vector<string> goodImageList;for(i = 0, j = 0; i < nimages; i++ ){const string& filename = imageList[i];Mat img = imread(filename, IMREAD_GRAYSCALE);// 检查图像是否为空if(img.empty())continue;imageSize = img.size();// 找角点bool found = false;vector<Point2f>& corners = imagePoints[j];found = findChessboardCorners(img, boardSize, corners,CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);if(found == false){continue;}// 再进行一次亚像素查找cornerSubPix(img, corners, Size(11,11), Size(-1,-1),TermCriteria(TermCriteria::COUNT+TermCriteria::EPS,30, 0.01));// 显示查找的结果if(displayCorners){cout << "found:" << filename.c_str() << endl;Mat cimg;cvtColor(img, cimg, COLOR_GRAY2BGR);drawChessboardCorners(cimg, boardSize, corners, found);imshow("corners", cimg);char c = (char)waitKey(100);}goodImageList.push_back(imageList[i]);j++;}nimages = j;if( nimages < 2 ){cout << "Error: too little data to run the calibration\n";return -1;}// 截取长度,保留有用的数据imagePoints.resize(nimages);// 填充3d数据objectPoints.resize(nimages);for(int i = 0; i < nimages; i++ ){for(int j = 0; j < boardSize.height; j++ )for(int k = 0; k < boardSize.width; k++ )objectPoints[i].push_back(Point3f(k*squareSize, j*squareSize, 0));}cv::Mat cameraMatrix(3, 3, CV_32FC1, cv::Scalar::all(0));  //内参矩阵3*3cv::Mat distCoeffs(1, 5, CV_32FC1, cv::Scalar::all(0));    //畸变矩阵1*5vector<cv::Mat> rotationMat;                               //旋转矩阵vector<cv::Mat> translationMat;                            //平移矩阵//!标定/*** points3D_all_images: 真实三维坐标* points_all_images: 提取的角点* image_size: 图像尺寸* camera_K : 内参矩阵K* distCoeffs: 畸变参数* rotationMat: 每个图片的旋转向量* translationMat: 每个图片的平移向量* */calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rotationMat, translationMat, 0);Mat testImg = imread(imageList[0], IMREAD_COLOR);cv::Rect validROI;Mat newCamMatrix = getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1.0, imageSize, &validROI);//    Mat undistortedImg;
//    undistort(testImg, undistortedImg, cameraMatrix, distCoeffs, newCamMatrix);//    cv::rectangle(undistortedImg, validROI, Scalar(255, 0, 0));
//    imshow("undistorted image", undistortedImg);Mat undistortedImg2;Mat map1, map2;initUndistortRectifyMap(cameraMatrix, distCoeffs, cv::Mat(), newCamMatrix, imageSize, CV_32FC1, map1, map2);//    cout << "map1 size" << map1.size() << "," << map1.channels() << endl;//    cout << "map2 size" << map2.size() << "," << map2.channels() << endl;remap(testImg, undistortedImg2, map1, map2, INTER_LINEAR);cv::rectangle(undistortedImg2, validROI, Scalar(255, 0, 0));cout << "calibration completed\r\n";// map1 map2中存储的分别是最终图像对应像素的x,y坐标// 知道dst的坐标,求src的相应坐标Point dstPt(400, 109);double pt_x = map1.at<float>(dstPt);double pt_y = map2.at<float>(dstPt);cout << "dstPt:" << dstPt << "; " << "origin pt:" << pt_x << ", "<< pt_y << endl;cv::circle(testImg, Point(pt_x, pt_y), 5, Scalar(255, 0, 0), 2);cv::circle(undistortedImg2, dstPt, 5, Scalar(255,0, 0), 2);// 知道src的坐标,求dst的相应坐标vector<Point2f> srcPts;srcPts.push_back(Point2f(300, 145));vector<Point2f> dstPts;undistortPoints(srcPts, dstPts, cameraMatrix, distCoeffs, Mat(), newCamMatrix);cout << "the dst:" << dstPts << endl;circle(testImg, srcPts[0], 8, Scalar(0, 255, 0));circle(undistortedImg2, dstPts[0], 8, Scalar(0, 255, 0));imshow("src to dst: src", testImg);imshow("src to dst: dst", undistortedImg2);
}

参考:
【关于OpenCV中的去畸变】
【用OpenCV进行相机标定(张正友标定,有代码)】
【《opencv学习笔记》-- 重映射】

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

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

相关文章

k8s deployment(k8s经典版)|PetaExpress

Deployment是什么&#xff1f; Deployment是指在软件开发中将应用程序或系统部署到目标环境中的过程。它包括将代码编译、配置、打包并安装到目标服务器或设备上的步骤。k8s deployment是&#xff08;k8s经典版&#xff09;中用来管理发布的控制器&#xff0c;在开发的过程中使…

selenium等待的三种方式(详细)

1.强制等待 time.sleep(3) 这种方式会是操作强行等待3s才会进行下一步操作&#xff0c;但是这种放法&#xff0c;可能会延长测试的时间&#xff0c;如果元素在1s中出现&#xff0c;就会浪费2s的时间&#xff0c;并且这种放法单次有效&#xff0c;每次需要等待元素的操作都需要…

Python Geoplotlib 可视化地理数据的综合指南

Geoplotlib&#xff1a; Exploring the World with Python Python Geoplotlib 可视化地理数据的综合指南 Geoplotlib是一个强大的Python库&#xff0c;用于可视化地理数据。它提供了一种简单直观的方法来创建地图并在其上绘制地理数据。 Geoplotlib 建立在 matplotlib 和 numpy…

BUG分析以及BUG定位

一般来说bug大多数存在于3个模块&#xff1a; 1、前台界面&#xff0c;包括界面的显示&#xff0c;兼容性&#xff0c;数据提交的判断&#xff0c;页面的跳转等等&#xff0c;这些bug基本都是一眼可见的&#xff0c;不太需要定位&#xff0c;当然也不排除一些特殊情况&#xf…

【Koa】[NoSQL] Koa中相关介绍和使用Redis MongoDB增删改查

目录 NoSQL非关系型数据库关系型数据库&#xff08;RMDB&#xff09;介绍非关系型数据库&#xff08;NoSQL&#xff09;介绍Redis & MongoDB 在 Koa 中使用 Redis (了解)Redis 的安装和使用在 Koa 中连接 和 调用 Redis 在 Koa 中使用 MongoDBMongoDB 的安装MongoShell 操作…

C++模拟实现stack

1.前言 stack 遵循的原则是先进后出&#xff0c;那到底是用list 还是 vector呢&#xff1f;其实都可以&#xff0c;但是队列queue就不一样了&#xff0c;他甚至不可以支付vector&#xff0c;因为效率太低了。 但是库里面用了一种新的类型&#xff0c;deque&#xff0c;它的实现…

Qt - macOS 安装配置

文章目录 一、关于 QT1.2 Qt的发展史1.3支持的平台1.4 Qt版本1.5 Qt 的优点1.6 成功案例 二、软件安装1、保证已 Xcode 和 Command Line Tools2、下载 QT3、下载 [qtcreator](http://download.qt.io/official_releases/qtcreator/)查看qt版本 三、创建工程Qt 常见用法 四、基础…

消息服务概述

消息服务的作用&#xff1a; 在多数应用尤其是分布式系统中&#xff0c;消息服务是不可或缺的重要部分&#xff0c;它使用起来比较简单&#xff0c;同时解决了不少难题&#xff0c;例如异步处理、应用解耦、流量削锋、分布式事务管理等&#xff0c;使用消息服务可以实现一个高…

利用vscode--sftp,将本地项目/文件上传到远程服务器中详细教程

1、首先在 vscode 中下载 sftp&#xff1a; 2、然后在 vscode 中打开本地将要上传的项目或文件&#xff1a; 3、安装完后&#xff0c;使用快捷键 ctrlshiftP 打开指令窗口&#xff0c;输入 sftp:config &#xff0c;回车&#xff0c;在当前目录中会自动生成 .vscode 文件夹及 s…

通过Jmeter压测存储过程

一、存储过程准备&#xff1a; 1、建立一个空表&#xff1a; 1 CREATE TABLE test_data ( id NUMBER, name VARCHAR2(50), age NUMBER ); 2、建立一个存储过程&#xff1a; 1 2 3 4 5 6 7 8 9 CREATE OR REPLACE PROCEDURE insert_test_data (n IN NUMBER) AS BEGIN --E…

进阶 vue自定义指令 vue中常用自定义指令以及应用场景,案例书写

文章目录 vue3自定义指令1.什么是自定义指令&#xff1f;2.注册自定义指令2.1 全局注册2.2 局部注册<script setup>中注册&#xff1a;<script>中使用&#xff1a; 3.钩子函数参数详解4.指令传值5.总结 常用自定义指令案例v-longpress 长按v-debounce 防抖v-thrott…

python 将pdf文件转图片

有小伙伴问了怎么将 pdf文件转图片的问题&#xff0c;我百度了一波儿&#xff0c;搞了以下python代码给他封装成exe工具了。 中途打包踩了个坑&#xff0c;python进程池的问题&#xff0c;本地运行没啥问题&#xff0c;打包好的exe文件双击就会使电脑内存爆破卡死&#xff0c;…

Top命令

Top top - 12:46:01 up 2 days, 11:10, 3 users, load average: 0.56, 0.59, 0.45系统基本信息&#xff1a;显示了系统运行时间、登录用户数和平均负载&#xff08;load average&#xff09;情况。平均负载是系统在特定时间范围内的平均活跃进程数&#xff0c;可以用来衡量系…

iOS 应用上架的步骤和工具简介

APP开发助手是一款能够辅助iOS APP上架到App Store的工具&#xff0c;它解决了iOS APP上架流程繁琐且耗时的问题&#xff0c;帮助跨平台APP开发者顺利将应用上架到苹果应用商店。最重要的是&#xff0c;即使没有配置Mac苹果机&#xff0c;也可以使用该工具完成一系列操作&#…

spring-cloud-alibaba——nacos-server搭建

前言&#xff1a;组件版本关系&#xff0c;官方:组件版本关系 1,nacos-server搭建&#xff08;windows环境&#xff09;&#xff0c;下载地址nacos 选择对应的版本&#xff0c;这里以目前最新版2.2.3为例子,下载后解压 单机模式 修改\nacos-server-2.2.3\nacos\bin\startup.c…

RL vs 最优控制:用于轨迹跟踪的 LQR(使用 Python 代码)

一、说明 在本博客系列中&#xff0c;我们将了解最优控制的经典方法&#xff0c;这些方法在某种程度上为强化学习等更熟悉的主题奠定了坚实的基础。这两个领域之间存在着不可避免的共同边界&#xff0c;本系列旨在提出这些最优控制的形式化方法&#xff0c;作为强化学习方法的有…

从Vue 2到Vue 3:深入了解路由配置的变化与升级建议

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏:《 VUE 》 《 javaScript 》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 目录 &#x1f4d8; 前言 vue2路由配置 &#x1f4df; 一、控制台安装vue路由 &#x1f4df; 二、项目src文件夹下创建r…

基于SSM+JSP+LayUI的宿舍管理系统

修正初始账号密码 默认账号&#xff1a;admin&#xff0c;默认密码&#xff1a;123456修复后台管理头像消失功能相对简单些&#xff0c;可能需要添加一些功能&#xff0c;需要源码免费提供需要运行服务、添加功能等联系我

matlab BP神经网络对iris数据集进行分类

iris数据集 本文所用数据集&#x1f449;&#x1f449;&#x1f449;iris分类数据集 1.数据预处理 %% 1.数据预处理 oridatareadtable(Iris.xls,Sheet,Sheet1); Xtable2array(oridata(:,(1:4))); % X转化为array类型 Ytable2array(oridata(:,5)); % Y因为包含中文字符&…

STM32CUBUMX配置FLASH(W25Q128)--保姆级教程

———————————————————————————————————— ⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩最近在开发一个STM32H723ZGT6的板子&#xff0c;使用STM32CUBEMX做了很多驱动&#x…