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

news2024/12/23 17:26:45

文章目录

  • 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*3
    cv::Mat distCoeffs(1, 5, CV_32FC1, cv::Scalar::all(0));    //畸变矩阵1*5
    vector<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.coloradmin.cn/o/791103.html

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

相关文章

【Linux】网络基础之TCP协议

目录 &#x1f308;前言&#x1f338;1、基本概念&#x1f33a;2、TCP协议报文结构&#x1f368;2.1、源端口号和目的端口号&#x1f369;2.2、4位首部长度&#x1f36a;2.3、32位序号和确认序号&#xff08;重点&#xff09;&#x1f36b;2.4、16位窗口大小&#x1f36c;2.5、…

解决rosdep网络问题

众所周知&#xff0c;想要使用rosdep&#xff0c;需要两个步骤&#xff1a; sudo rosdep init rosdep update其中&#xff0c;第一步就是下载了一个文件&#xff0c;第二步是从服务器下载一些数据。 但是因为国内的网络的原因&#xff0c;这两步都有一点困难。但是可以使用tun…

P3611 [USACO17JAN] Cow Dance Show S

思路&#xff1a;二分K&#xff0c;查看当前K能否满足总时间不超过最大时间 ACcode: #include<bits/stdc.h> using namespace std; #define int long long const int N1e410; int n,tmax,a[N]; bool check(int x) {priority_queue < int, vector < int >, gre…

ASCII码、UniCode码、字符转换、中文、英文、二进制、十进制、十六进制

文章目录 效果图htmlJavaScript 效果图 html <div class"w_680 p_t_20 p_b_20 p_l_6 p_r_6"><div class"w_100_ d_f jc_c"><textarea class"w_97_ h_86 fs_16 resize_none outline_none" oninput"oninputF(event)">…

OpenAI重磅官宣ChatGPT安卓版本周发布,现已开启下载预约,附详细预约教程

7月22号&#xff0c;OpenAI 突然宣布&#xff0c;安卓版 ChatGPT 将在下周发布&#xff01;换句话说&#xff0c;本周安卓版 ChatGPT正式上线&#xff01; 最早&#xff0c;ChatGPT仅有网页版。 今年5月&#xff0c;iOS版ChatGPT正式发布&#xff0c;当时OpenAI表示Android版将…

中缀表达式转后缀表达式,使用逆波兰计算。可以计算小数

1、使用方法 传递一个分开保存符号与数字的List即可&#xff1a;List SumNumber; 获取参数的构造方法如下&#xff1a; public ReversePolish(List<String> sumNumber) {SumNumber sumNumber;}要求的List保存数据的方式如下&#xff1a; 例如&#xff1a;123 然后使用…

【C++】详解多态的底层原理

文章目录 前言1. 虚函数表指针与虚函数表2. 子类的虚函数表&#xff08;单继承&#xff09;3. 多态的实现原理3.1 多态是如何实现的3.2 多态调用与非多态调用的区别3.3 为什么父类的对象不能实现多态 4. 其它多态相关问题的理解4.1 虚函数是存在哪里的&#xff1f;4.2 子类新增…

手机照片误删除?无需担忧,点击这里,即可轻松恢复

手机照片误删除&#xff1f;无需担忧&#xff0c;点击这里&#xff0c;即可轻松恢复 开头&#xff1a;在数字化时代&#xff0c;手机已成为我们生活中不可或缺的伙伴。随着手机摄影的普及&#xff0c;我们记录了许多珍贵的瞬间和回忆。然而&#xff0c;有时候我们不小心误删除…

项目经理必备的5种管理能力

作为中层管理者&#xff0c;需要同时完成上级的任务安排和照顾下属的情绪&#xff0c;这是职场中最具挑战性的管理能力。项目经理必备能力中&#xff0c;计划制定、有效授权、高效沟通、化解冲突、项目跟踪是至关重要的。 1、计划制定是项目管理的关键。 作为项目经理&#…

Tribon二次开发- tbbatchjob

在Tribon安装目录下C:\Tribon\M3\Bin里面有许多未知用途的exe,有的双击后时一个DOS终端,有的一闪而过,有的需要按照提示输入信息,有的需要提前在指定的目录配置文件,该如何使用呢? 这些exe大多可以在Tribon以外通过.NET来使用,有的可以通过添加.NET项目引用来使用,有的…

聊聊spring中bean的作用域

前言 今天分享一下spring bean的作用域&#xff0c;理解bean的作用域能够在使用过程中避免一些问题&#xff0c;bean的作用域也是spring bean创建过程中一个重要的点。 Spring bean的作用域类型 singleton&#xff08;单例模式&#xff09;&#xff1a;在整个应用程序的生命周…

成都爱尔蔡裕:泡在“糖”里的脆弱血管,暴露在眼睛深处

糖尿病是一组由多病因引起的以慢性高血糖为特征的终身性代谢性疾病。长期血糖增高&#xff0c;大血管、微血管受损并危及心、脑、肾、周围神经、眼睛、足等。医生临床数据显示&#xff0c;糖尿病发病后10年左右&#xff0c;将有30%&#xff5e;40%的患者至少会发生一种并发症&a…

【TypeScript】对函数类型的约束定义

导读 函数是JavaScript 中的 一等公民 概念&#xff1a;函数类型的概念是指给函数添加类型注解&#xff0c;本质上就是给函数的参数和返回值添加类型约束 文章目录 声明式函数:表达式函数&#xff1a;箭头函数可选参数和默认参数&#xff1a;参数默认值&#xff1a;过剩参数的处…

脚本 打开 cmd 跳转到某个文件夹并执行某些命令

很多时候我们需要启动windows安装的redis、nacos等。 通常我们可以打开安装软件的目录&#xff0c;在文件夹目录那一栏输入cmd,再执行相关启动命令但是这样比较麻烦&#xff0c;现在我们写一个bat脚本&#xff0c;直接启动脚本就可以实现启动程序了。 例如&#xff0c; 1&…

docker入门讲解

目录 第 1 章 Docker核心概念与安装 为什么使用容器&#xff1f; Docker是什么 Docker设计目标 Docker基本组成 容器 vs 虚拟机 Docker应用场景 Linux 安装 Docker 第 2 章 Docker镜像管理 Docker 容器管理 Docker 容器数据持久化 Docker 容器网络 Dockerfile 定制…

JAVA的数据类型与变量

目录 1. 字面常量 2. 数据类型 3. 变量 3.2 长整型变量 3.3 短整型变量 3.4 字节型变量 3.5双精度浮点型 3.6 单精度浮点型 3.7字符型变量 3.8布尔型变量 4.类型转换 4.1自动类型转换(隐式) 4.2强制类型转换(显式) 5.字符串类型 1. 字面常量 字面常量的分类&am…

深度学习之梯度下降算法

0.1 学习视频源于&#xff1a;b站&#xff1a;刘二大人《PyTorch深度学习实践》 0.2 本章内容为自主学习总结内容&#xff0c;若有错误欢迎指正&#xff01; 1 线性模型 1.1 通过简单的线性模型来举例&#xff1a; 1.2 如图&#xff0c;简单的一个权重的线性模型&#xff0c…

透明屏的应用范围广吗?

透明屏是一种新型的显示技术&#xff0c;它可以使屏幕显示的内容透明&#xff0c;让用户可以同时看到屏幕上的图像和背后的物体。 透明屏的应用领域非常广泛&#xff0c;可以用于商业广告、展览展示、智能家居等多个领域。 透明屏的原理是利用透明材料和光学技术&#xff0c;…

通过el-tab切换Echarts图表显示不全问题

一、背景 在让日常开发中很多时候会通过el-tab选项卡方式去分类统计数据&#xff0c;本文我们主要是针对统计中用到了echarts图表&#xff0c;在刚接触时可能会遇到默认选项卡可以正常显示图表数据&#xff0c;但是切换选项卡以后会出现图表大小出现问题&#xff0c;当然原因就…

第2集丨webpack 江湖 —— 创建一个简单的webpack工程demo

目录 一、创建webpack工程1.1 新建 webpack工程目录1.2 项目初始化1.3 新建src目录和文件1.4 安装jQuery1.5 安装webpack1.6 配置webpack1.6.1 创建配置文件&#xff1a;webpack.config.js1.6.2 配置dev脚本1.7 运行dev脚本 1.8 查看效果1.9 附件1.9.1 package.json1.9.2 webpa…