opencv对相机进行畸变矫正,及从矫正后的图像坐标反求原来的对应坐标

1.背景

目前有个项目,需要用到热成像相机。但是这个热成像相机它的畸变比较厉害,因此需要用标定板进行标定,从而消除镜头畸变。

同时需要实现用户用鼠标点击矫正后的画面后,显示用户点击位置的像素所代表的温度。

2.难点

消除镜头畸变后,就不能直接使用热成像sdk提供的函数来查询像素对应的温度。

因为在查询函数中,有个像素坐标的形参,要求传入原来的热成像图像A的像素坐标,函数返回此像素位置的温度。

而我们经过畸变消除后,得到画面B。B上面的特定像素所处的坐标和原图不一定一样。

因此,假如用户想查询画面B上的某个像素点的有效温度,就必须要取得此像素点在原图A上的位置坐标。

3.解决方案

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

3.1.相机标定取得内参

在经过 findChessboardCorners、calibrateCamera之后,我们就已经获得了相机矩阵cameraMatrix、畸变矩阵distCoeffs。

3.2.畸变矫正

然后,我们利用getOptimalNewCameraMatrix,获得了一个相对容易控制画面取舍的新相机矩阵newCamMatrix。

接下来,就有两种方式对画面进行矫正:

a、直接undistort。

b、先利用initUndistortRectifyMap得到map1、map2,然后再利用remap进行画面矫正。

后面的代码把两种都演示了。

3.3.使用的方式

其实,我们真正需要的是第二种。

关键就在于map1、map2。

这两个矩阵是什么玩意呢?

其实你先看看他们的尺寸、通道数,再查阅一下资料就知道了:

map1、map2的尺寸与目标图像(矫正后的图像)的尺寸一致,而通道数为1(这个其实不一定,与其他参数有关,暂时先这样认为)。其中map1中存储了dst对应像素点的图像来源自源图像的像素的坐标x,而map2中存储了坐标y。

虽然我描述得很混乱,但是你配合代码应该明白我在说什么。😁

所以,我们直接利用这个map1、map2就可以实现从消除畸变后的画面坐标转换到原画面的坐标了。

4.效果

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

5.代码

cpp 复制代码
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;

    // map1 map2中存储的分别是最终图像对应像素的x,y坐标
    Point dstPt(100, 70);
    double pt_x = map1.at<float>(dstPt);
    double pt_y = map2.at<float>(dstPt);

    cout << "dstPt:" << dstPt << "; " << "origin pt:" << pt_x << ","<< pt_y << endl;

    remap(testImg, undistortedImg2, map1, map2, INTER_LINEAR);

    cv::rectangle(undistortedImg2, validROI, Scalar(255, 0, 0));
    cv::circle(undistortedImg2, Point(400, 109), 8, Scalar(255,0, 0), 2);
    imshow("remap", undistortedImg2);

    cv::circle(testImg, Point(pt_x, pt_y), 5, Scalar(0, 255, 0), 2);
    imshow("origin image", testImg);

    cout << "calibration completed\r\n";
}

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

相关推荐
跟着珅聪学java11 分钟前
Apache OpenNLP简介
人工智能·知识图谱
AwhiteV42 分钟前
利用图数据库高效解决 Text2sql 任务中表结构复杂时占用过多大模型上下文的问题
数据库·人工智能·自然语言处理·oracle·大模型·text2sql
Black_Rock_br1 小时前
AI on Mac, Your Way!全本地化智能代理,隐私与性能兼得
人工智能·macos
☺����2 小时前
实现自己的AI视频监控系统-第一章-视频拉流与解码2
开发语言·人工智能·python·音视频
fsnine2 小时前
机器学习——数据清洗
人工智能·机器学习
小猿姐3 小时前
KubeBlocks AI:AI时代的云原生数据库运维探索
数据库·人工智能·云原生·kubeblocks
算法_小学生3 小时前
循环神经网络(RNN, Recurrent Neural Network)
人工智能·rnn·深度学习
吱吱企业安全通讯软件3 小时前
吱吱企业通讯软件保证内部通讯安全,搭建数字安全体系
大数据·网络·人工智能·安全·信息与通信·吱吱办公通讯
盲盒Q3 小时前
《频率之光:共振之战》
人工智能·硬件架构·量子计算
飞哥数智坊4 小时前
DeepSeek V3.1 发布:我们等的 R2 去哪了?
人工智能·deepseek