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

news/2024/5/20 16:09:16/文章来源:https://blog.csdn.net/joyopirate/article/details/131900272

文章目录

  • 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就可以实现从消除畸变后的画面坐标转换到原画面的坐标了。

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())break;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){break;}// 再进行一次亚像素查找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.luyixian.cn/news_show_146840.aspx

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

相关文章

jQuery的DOM操作之笔记总结

jQuery的DOM操作之笔记总结 首先我们来介绍一下什么是DOM 简述&#xff1a; 1.DOM全称Document Object Model&#xff08;文档对象模型&#xff09;。 2.每个文档都是一棵DOM结构的树&#xff0c;文档里的很多元素&#xff0c;就像树上的很多节点&#xff0c;或是分叉的树枝…

集成学习——Boosting算法:Adaboost、GBDT、XGBOOST和lightGBM的简要原理和区别

1、Boosting算法 Boosting算法是通过串联的方式&#xff0c;将一组弱学习器提升为强学习器算法。它的工作机制如下&#xff1a; &#xff08;1&#xff09;用初始训练集训练出一个基学习器&#xff1b; &#xff08;2&#xff09;依据基学习器的表现对训练样本分布进行调整&…

WPF实现DiagramChart

1、文件架构 2、FlowChartStencils.xaml <ResourceDictionary xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:s"clr-namespace:DiagramDesigner"xmlns:c&…

算法通关村第一关-链表白银挑战笔记|公共子节点

两个链表公共子节点问题 提示&#xff1a;大家都在做什么&#xff1f; 不做什么。就是等夏天结束 文章目录 两个链表公共子节点问题前言题目&#xff1a;提供四种解决方法的思路&#xff1a;拿到题目要怎么思考&#xff1a;审题哈希表或集合实现使用栈来实现拼接字符串实现 (组…

短视频矩阵源码开发搭建分享--多账号授权管理

目录 文章目录 前言 一、矩阵号系统是什么&#xff1f; 二、使用步骤 1.创建推广项目 2.多账号授权 3.企业号智能客服系统 总结 前言 短视频多账号矩阵系统&#xff0c;通过多账号一键授权管理的方式&#xff0c;为运营人员打造功能强大及全面的“矩阵式“管理平台。…

从零构建深度学习推理框架-1 简介和Tensor

源代码作者&#xff1a;https://github.com/zjhellofss 本文仅作为个人学习心得领悟 &#xff0c;将原作品提炼&#xff0c;更加适合新手 什么是推理框架&#xff1f; 深度学习推理框架用于对已训练完成的神经网络进行预测&#xff0c;也就是说&#xff0c;能够将深度训练框…

UE虚幻引擎教程_生成云平台指定路径下的exe文件

市面上大量优秀的游戏都是基于UE制作的&#xff0c;UE虚幻引擎制作的作品可以在windows、mac、linux以及ps4、x-boxone、ios、android甚至是html5等平台上运行。本文介绍了UE虚幻引擎如何生成云平台指定路径下的EXE。 一、云平台会运行打包文件夹下指定路径的EXE文件 但有时候…

【多选框、表格全选】element el-checkbox、el-table

话不多说 先看效果&#xff1a; 多选框&#xff1a; 表格全选&#xff1a; <template><div><div class"titleLabel"><div class"lineStyle"></div>统计部门</div><div style"display: flex"><e…

项目开启启动命令整合

启动RabbitMQ管理插件 1.启动 RabbitMQ 管理插件。 rabbitmq-plugins enable rabbitmq_management rabbitmq-server # 直接启动&#xff0c;如果关闭窗⼝或需要在该窗⼝使⽤其他命令时应⽤就会停⽌ rabbitmq-server -detached # 后台启动 rabbitmq-server start # 启⽤服务 rab…

(二)安装部署InfluxDB

以下内容来自 尚硅谷&#xff0c;写这一系列的文章&#xff0c;主要是为了方便后续自己的查看&#xff0c;不用带着个PDF找来找去的&#xff0c;太麻烦&#xff01; 第 2 章 安装部署InfluxDB 1、linux 安装方式如下 通过包管理工具安装&#xff0c;比如apt 和yum直接下载可执…

springboot()—— 集成redis

1、新建一个springboot项目 2、添加redis依赖包 可以在新建项目的时候就选上 也可以建完项目以后手动导入pom.xml <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </d…

2023年基准Kubernetes报告:6个K8s可靠性失误

云计算日益成为组织构建应用程序和服务的首选目的地。尽管一年来经济不确定性的头条新闻主要集中在通货膨胀增长和银行动荡方面&#xff0c;但大多数组织预计今年的云使用和支出将与计划的相同&#xff08;45%&#xff09;&#xff0c;或高于计划的&#xff08;45%&#xff09;…

装饰模式-扩展系统功能

买了新车后&#xff0c;不少人会对车进行装饰&#xff0c;比如给车贴膜&#xff0c;喷上骚粉的漆等。某天&#xff0c;小李和小张都买了辆车&#xff0c;小李想给车贴膜&#xff0c;小张想给车先喷漆然后再贴膜。现在中的做法是&#xff0c;把车开到改装店&#xff0c;如果要喷…

浏览器调试Android App

浏览器调试Android App 1. 背景2. 调试工具3. 手机设置4. 打开浏览器(edge)5. 连接手机6. 点击inspect 开始调试 1. 背景 在工作中经常会遇到在原生app中嵌套h5&#xff0c; 但是在某些需要在app里面调试的内容&#xff0c; 却没有像chrome开发者工具这样的工具来帮助我们快速…

react 在build读取env 数据

默认会读取.env 文件 npm install dotenv --save npm install dotenv-cli --save-dev例如读取.env.test "build:test": "dotenv -e .env.test react-app-rewired build",.env.test REACT_APP_CURRENTMODE devREACT_APP_Public_Path "https://baid…

[NLP]使用Alpaca-Lora基于llama模型进行微调教程

Stanford Alpaca 是在 LLaMA 整个模型上微调&#xff0c;即对预训练模型中的所有参数都进行微调&#xff08;full fine-tuning&#xff09;。但该方法对于硬件成本要求仍然偏高且训练低效。 [NLP]理解大型语言模型高效微调(PEFT) 因此&#xff0c; Alpaca-Lora 则是利用 Lora…

算法竞赛入门【码蹄集新手村600题】(MT1040-1060)

算法竞赛入门【码蹄集新手村600题】(MT1040-1060&#xff09; 目录MT1041 求圆面积和周长MT1042 求矩形的面积和周长MT1043 椭圆计算MT1044 三角形面积MT1045 平行四边形MT1046 菱形MT1047 梯形MT1048 扇形面积MT1049 三角形坐标MT1050 空间三角形MT1051 四边形坐标MT1052 直角…

Java通过URL对象实现简单爬虫功能

目录 一、URL类 1. URL类基本概念 2. 构造器 3. 常用方法 二、爬虫实例 1. 爬取网络图片&#xff08;简易&#xff09; 2. 爬取网页源代码 3. 爬取网站所有图片 一、URL类 1. URL类基本概念 URL&#xff1a;Uniform Resource Locator 统一资源定位符 表示统一资源定位…

day39-Password Strength Background(密码强度背景)

50 天学习 50 个项目 - HTMLCSS and JavaScript day39-Password Strength Background&#xff08;密码强度背景&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name&quo…

Linux 学习记录57(ARM篇)

Linux 学习记录57(ARM篇) 本文目录 Linux 学习记录57(ARM篇)一、外部中断1. 概念2. 流程图框 二、相关寄存器1. GIC CPU Interface (GICC)2. GIC distributor (GICD)3. EXTI registers 三、EXTI 寄存器1. 概述2. 内部框图3. 寄存器功能描述4. EXTI选择框图5. EXTI_EXTICR1 &…