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

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学习笔记》-- 重映射】

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

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

相关文章

fastadmin框架重定向

由于&#xff0c;我们一打开fastadmin框架就进入到前端页面很麻烦&#xff0c;下面这种方法可以解决这个问题。 首先我们找到这个路径 找到重定向&#xff0c; application》index》controller》index 原本文件是这个样子&#xff1a; <?phpnamespace app\index\controll…

【ArcGIS Pro二次开发】(53):村规制表、制图【福建省】

这篇算是村规入库的一个延续。 村庄规划中有一些图纸是需要严格按照规范制图&#xff0c;或形成一定规范格式的。 这些图纸的制作基本算是机械式的工作&#xff0c;可以用工具来代替人工。 一、要实现的功能 如上图所示&#xff0c;在【村庄规划】组&#xff0c;新增了两个工…

配置代理——解决跨域问题(详解)

之前写项目的时候总会遇到配置代理的问题&#xff0c;可是配置了之后有时有用&#xff0c;有时就没有用&#xff0c;自己之前学的也是懵懵懂懂&#xff0c;于是专门花了一个小时去了解了如何配置代理跨域&#xff0c;然后在此记录一下&#xff0c;方便自己以后查阅。 一、 常用…

pytorch实现梯度下降算法例子

如题&#xff0c;利用pytorch&#xff0c;通过代码实现机器学习中的梯度下降算法&#xff0c;求解如下方程&#xff1a; f ′ ( x , y ) x 2 20 y 2 {f}(x,y) x^2 20 y^2 f′(x,y)x220y2 的最小值。 Latex语法参考&#xff1a;https://blog.csdn.net/ViatorSun/article/d…

推荐系统(十)用户行为序列建模-Pooling 路线

对推荐系统而言&#xff0c;准确捕捉用户兴趣是其面临的核心命题。不管是样本、特征还是模型结构等方面的优化&#xff0c;本质上做的事情都是在提高推荐系统对用户兴趣的捕捉能力&#xff0c;因此如何提高这种能力&#xff0c;对推荐效果的提升有重要作用&#xff0c;也是算法…

tp6 实现excel 导入功能

在项目根目录安装 composer require phpoffice/phpspreadsheet 我们看一下郊果图&#xff0c;如下 点击导入excel表格数据 出现弹窗选择文件&#xff0c;控制台打开输出文档内容 前端layui代码 <form id"uploadForm" class"form-horizontal" encty…

7.25 Qt

制作一个登陆界面 login.pro文件 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on …

如何高效地查询IP归属地

高效识别IP归属地是网络安全领域中的一项重要工作。准确地识别IP的归属地不仅可以帮助网络管理员追踪和定位潜在的网络攻击者&#xff0c;还可以用于网络流量分析、地理定位服务等方面。 以下将介绍几种高效识别IP归属地的方法。 使用IP归属地数据库 IP归属地数据库是一种存储…

Clion开发STM32之W5500系列(综合实验)

说明 此为w5500模块的综合实验测试模块,包含dhcp、dns、ntp以上三个模块的驱动参考之前的文章&#xff0c;本篇不做说明.使用的开发芯片 stm32f103vet6系列,外设接口使用的spi2 实验内容: 通过dhcp动态获取ip,通过dns解析NTP服务域名的ip通过NTP服务ip获取时间 w5500配置驱…

国密SSL优势及应用场景

国密SSL的优势主要有以下几点&#xff1a; 更高的安全性&#xff1a;国密算法采用的是国家密码管理局推荐的算法&#xff0c;相对于传统的SSL协议更加安全。 更好的性能&#xff1a;国密算法是国家密码管理局推荐的算法&#xff0c;其加密效率与密钥长度相比传统算法更高。 更…

微服务架构演变

微服务架构筑基 软件架构演进 软件架构的发展经历了从单体结构、垂直架构、SOA架构到微服务架构的过程. 什么是微服务&#xff1f;&#xff1f; Spring Cloud Netfilx Spring Cloud Alibaba service Mesh 架构的发展&#xff1a;基于某一种因素 技术是服务于业务的 业务又是…

Vue mixin 混入

可以复用的组件&#xff0c;我们一般会抽离&#xff0c;写成公共的模块。 可以复用的方法&#xff0c;我们一般会抽离&#xff0c;写成公共的函数。 那么 在 Vue 中&#xff0c;如果 某几个组件实例 VueComponent 中、或者 整个 Vue 项目中 都存在相同的配置&#xff0c;那就…

数据结构(二)

目录 Trie树 并查集 堆 Trie树 作用:用来高效地存储和查找字符串集合的数据结构 基本形式: 模板代码如下: #include<iostream> using namespace std;const int N 100010;//idx代表当前用到哪个下标 //既是根节点&#xff0c;又是空节点 //cnt存储的是以当前点结尾的…

Mac 系统钥匙串证书不受信任

Mac 系统钥匙串证书不受信任 解决办法 通过尝试安装 Apple PKI 的 Worldwide Developer Relations - G4 (Expiring 12/10/2030 00:00:00 UTC) 解决该异常问题 以上便是此次分享的全部内容&#xff0c;希望能对大家有所帮助!

移动端商品详情页设计

效果图 代码如下 页面设计 <div class"container"><!--商品详情 start--><van-image class"goods-item-image" :src"goods.goodsHeadImg"></van-image><div class"goods-price">&#xffe5;<span&…

【VUE】npm打包报错 Syntax Error: Error: Cannot find module ‘imagemin-gifsicle‘

一. Syntax Error: Error: Cannot find module ‘imagemin-gifsicle’ npm run build 报错&#xff0c;报错如下 原因 这个错误消息显示缺少了 imagemin-gifsicle 模块&#xff0c;而它是 image-webpack-loader 的依赖项&#xff0c;导致构建失败。解决 &#xff08;1&#xf…

安全—01day

文章目录 1. 编码1.1 ASCLL编码1.2 URL编码1.3 Unicode编码1.4 HTML编码1.5 Base64编码 2. form表单2.1 php接收form表单2.2 python接收form表单 1. 编码 1.1 ASCLL编码 ASCII 是基于拉丁字母的一套电脑编码系统&#xff0c;主要用于显示现代英语和其他西欧语言。它是最通用的…

电容触摸屏(TP)的工艺结构

液晶显示屏(LCM),触摸屏(TP) “GG、GP、GF”这是结构分类&#xff0c;第一个字母表面材质&#xff08;又称为上层&#xff09;&#xff0c;第二个字母是触摸屏的材质&#xff08;又称为下层&#xff09;&#xff0c;两者贴合在一起。 G玻璃&#xff0c;FFILM&#xff0c;“”贴…

Stable Diffusion - 扩展 SegmentAnything 和 GroundingDINO 实例分割算法 插件的配置与使用

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131918652 Paper and GitHub&#xff1a; Segment Anything: SAM - Segment Anything GitHub: https://github.com/facebookresearch/s…

蓝桥杯专题-真题版含答案-【牌型种数】【煤球数目】【寒假作业】【奖券数目】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…