鱼眼相机成像模型以及基于OpenCV标定鱼眼镜头(C++)

news2024/11/24 19:01:13

opencv系列


文章目录

  • opencv系列
  • 一、鱼眼镜头模型
  • 二、投影函数
    • 等距投影模型
    • 等立体角投影模型
    • 正交投影模型
    • 体视投影模型
  • 三、OpenCV中的鱼眼相机模型
  • 四、标定(C++)实现
    • 使用的函数
    • 采集标定图像
    • 标定代码
    • 标定结果


一、鱼眼镜头模型

鱼眼镜头一般是由十几个不同的透镜组合而成的,在成像的过程中,入射光线经过不同程度的折射,投影到尺寸有限的成像平面上,使得鱼眼镜头与普通镜头相比起来拥有了更大的视野范围。下图表示出了鱼眼相机的一般组成结构。最前面的两个镜头发生折射,使入射角减小,其余的镜头相当于一个成像镜头,这种多元件的构造结构使对鱼眼相机的折射关系的分析变得相当复杂。
在这里插入图片描述
研究表明鱼眼相机成像时遵循的模型可以近似为单位球面投影模型。可以将鱼眼相机的成像过程分解成两步:第一步,三维空间点线性地投影到一个球面上,它是一个虚拟的单位球面,它的球心与相机坐标系的原点重合;第二步,单位球面上的点投影到图像平面上,这个过程是非线性的。下图表示出了鱼眼相机的成像过程。
在这里插入图片描述
我们知道,普通相机成像遵循的是针孔相机模型,在成像过程中实际场景中的直线仍被投影为图像平面上的直线。但是鱼眼相机如果按照针孔相机模型成像的话,投影图像会变得非常大,当相机视场角达到180°时,图像甚至会变为无穷大。所以,鱼眼相机的投影模型为了将尽可能大的场景投影到有限的图像平面内,允许了相机畸变的存在。并且由于鱼眼相机的径向畸变非常严重,所以鱼眼相机主要的是考虑径向畸变,而忽略其余类型的畸变。

二、投影函数

为了将尽可能大的场景投影到有限的图像平面内,鱼眼相机会按照一定的投影函数来设计。根据投影函数的不同,鱼眼相机的设计模型大致能被分为四种:等距投影模型、等立体角投影模型、正交投影模型和体视投影模型。下面的四种鱼眼相机的投影模型反映出了空间中的一点P是如何投影到球面上,然后到图像平面上成像的。

等距投影模型

在这里插入图片描述
在这里插入图片描述

上述式子中,rd表示鱼眼图像中的点到畸变中心的距离,是鱼眼相机的焦距,是入射光线与鱼眼相机光轴之间的夹角,即入射角。

等立体角投影模型

在这里插入图片描述

在这里插入图片描述

正交投影模型

在这里插入图片描述
在这里插入图片描述

体视投影模型

在这里插入图片描述

在这里插入图片描述

三、OpenCV中的鱼眼相机模型

OpenCV中使用的模型是由Kannala提出的一种鱼眼相机的一般近似模型。在等距投影模型的基础上提出来的。下面来详细分析其鱼眼相机模型的提出过程。我们可以将鱼眼相机模型的形式统一以等距投影模型的形式来表示,即
在这里插入图片描述

对实际的鱼眼镜头来说,它们不可能精确地按照投影模型来设计,所以为了方便鱼眼相机的标定,Kannala提出了一种鱼眼相机的一般多项式近似模型。通过前面的四个模型,可以发现 θd是θ的奇函数,而且将这些式子按泰勒级数展开,发现 θd可以用θ 的奇次多项式表示,即
在这里插入图片描述

为了实际计算的方便,需要确定式中 θd取到的次幂数。Kannala提出取式的前五项即取到的九次方,就给出了足够的自由度来很好地近似各种投影模型。 θd的一次项系数可以为1,于是OpenCV中使用的鱼眼相机模型为:
在这里插入图片描述

上式表示的模型是根据四种鱼眼相机投影模型得出的一种通用鱼眼相机多项式模型。这种模型根据θ能够得到 θd ,即通过无畸变图像中的点能够计算出鱼眼图像中的畸变点。这种模型在OpenCV的鱼眼相机标定方法中是适用的,因为OpenCV借助标定板对鱼眼相机进行标定。从空间点到鱼眼图像上的点的变换过程可用式子表示为:
在这里插入图片描述

上面式子中, X表示空间点,Xc表示相机坐标系下对应的空间点, Rt分别是两个坐标系之间的旋转矩阵和平移向量, (u,v)T 表示投影到鱼眼图像上的对应点。OpenCV中对鱼眼相机的标定步骤能够分成四步:

(1)初始化内参数;
(2)初始化外参数;
(3)使用LM算法最小化定位的图像点和投影的图像点之间的投影误差;
(4)确定结果。

四、标定(C++)实现

使用的函数

由于鱼眼镜头和针孔镜头的模型不一样,对于鱼眼镜头的模型在之前的博客中已经做了详细介绍,这里直接使用OpenCV中的cv::fisheye::calibrate()函数进行标定。函数原型如下,需要输入目标点集,图像点集、图像尺寸。函数输出相机内参,畸变系数,旋转矩阵和平移向量,以及反投影误差。

 CV_EXPORTS double calibrate(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, const Size& image_size,
        InputOutputArray K, InputOutputArray D, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags = 0,
            TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON));

采集标定图像

采集若干拍摄有标定棋盘格的图像,并使棋盘格出现在画面的各个位置,特别是边缘位置。如下图所示:
在这里插入图片描述

标定代码

#include "stdio.h"
#include <iostream>
#include <fstream>
#include <io.h>

#include "opencv2/opencv.hpp"
#include <opencv2/core/core.hpp>
#include "opencv2/calib3d/calib3d.hpp"
#include <opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

void getFiles(string path, vector<string>& files)
{
    //文件句柄
    intptr_t hFile = 0;
    //文件信息
    struct _finddata_t fileinfo;
    string p;
    if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
    {
        do
        {
            //如果是目录,迭代之
            //如果不是,加入列表
            if ((fileinfo.attrib &  _A_SUBDIR))
            {
                if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
                    getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
            }
            else
            {
                files.push_back(p.assign(path).append("\\").append(fileinfo.name));
            }
        } while (_findnext(hFile, &fileinfo) == 0);
        _findclose(hFile);
    }
}

int main(int argc, char** argv)
{   
    string filePath = ".\\720PPcalib\\front";
    vector<string> files;

    获取该路径下的所有文件
    getFiles(filePath, files);

    const int board_w = 6;
    const int board_h = 4;
    const int NPoints = board_w * board_h;//棋盘格内角点总数
    const int boardSize = 30; //mm
    Mat image,grayimage;
    Size ChessBoardSize = cv::Size(board_w, board_h);
    vector<Point2f> tempcorners;

    int flag = 0;
    flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC;
    //flag |= cv::fisheye::CALIB_CHECK_COND;
    flag |= cv::fisheye::CALIB_FIX_SKEW;
    //flag |= cv::fisheye::CALIB_USE_INTRINSIC_GUESS;

    vector<Point3f> object;
    for (int j = 0; j < NPoints; j++)
    {
        object.push_back(Point3f((j % board_w) * boardSize, (j / board_w) * boardSize, 0)); 
    }

    cv::Matx33d intrinsics;//z:相机内参
    cv::Vec4d distortion_coeff;//z:相机畸变系数

    vector<vector<Point3f> > objectv;
    vector<vector<Point2f> > imagev;

    Size corrected_size(1280, 720);
    Mat mapx, mapy;
    Mat corrected;

    ofstream intrinsicfile("intrinsics_front1103.txt");
    ofstream disfile("dis_coeff_front1103.txt");
    int num = 0;
    bool bCalib = false;
    while (num < files.size())
    {
        image = imread(files[num]);

        if (image.empty())
            break;
        imshow("corner_image", image);
        waitKey(10);
        cvtColor(image, grayimage, CV_BGR2GRAY);
        IplImage tempgray = grayimage;
        bool findchessboard = cvCheckChessboard(&tempgray, ChessBoardSize);

        if (findchessboard)
        {
            bool find_corners_result = findChessboardCorners(grayimage, ChessBoardSize, tempcorners, 3);
            if (find_corners_result)
            {
                cornerSubPix(grayimage, tempcorners, cvSize(5, 5), cvSize(-1, -1), cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
                drawChessboardCorners(image, ChessBoardSize, tempcorners, find_corners_result);
                imshow("corner_image", image);
                cvWaitKey(100);

                objectv.push_back(object);
                imagev.push_back(tempcorners);
                cout << "capture " << num << " pictures" << endl;
            }
        }
        tempcorners.clear();
        num++;
    }

    cv::fisheye::calibrate(objectv, imagev, cv::Size(image.cols,image.rows), intrinsics, distortion_coeff, cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6));  
    fisheye::initUndistortRectifyMap(intrinsics, distortion_coeff, cv::Matx33d::eye(), intrinsics, corrected_size, CV_16SC2, mapx, mapy);

    for(int i=0; i<3; ++i)
    {
        for(int j=0; j<3; ++j)
        {
            intrinsicfile<<intrinsics(i,j)<<"\t";
        }
        intrinsicfile<<endl;
    }
    for(int i=0; i<4; ++i)
    {
        disfile<<distortion_coeff(i)<<"\t";
    }
    intrinsicfile.close();
    disfile.close();

    num = 0;
    while (num < files.size())
    {
        image = imread(files[num++]);

        if (image.empty())
            break;
        remap(image, corrected, mapx, mapy, INTER_LINEAR, BORDER_TRANSPARENT);

        imshow("corner_image", image);
        imshow("corrected", corrected);
        cvWaitKey(200); 
    }

    cv::destroyWindow("corner_image");
    cv::destroyWindow("corrected");

    image.release();
    grayimage.release();
    corrected.release();
    mapx.release();
    mapy.release();

    return 0;
}


标定结果

使用标定的结果进行畸变校正后的结果如下所示,可以看到,原本弯曲的曲线已经变直。

在这里插入图片描述

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

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

相关文章

新能源充电桩4G无线物联网解决方案|4G路由器ZR2000

日常生活中新能源汽车已随处可见&#xff0c;新能源也逐渐普遍&#xff0c;绿色出行、低碳生活的环保概念也随着科普深入人心&#xff0c;新能源汽车必备的充电桩行业随之崛起&#xff0c;为保证用户体验及运营管理&#xff0c;充电桩需要通过网络实现数据传输、远程监控、位置…

19-递归的理解、场景

一、递归 &#x1f32d;&#x1f32d;&#x1f32d;在函数内部&#xff0c;可以调用其他函数。如果一个函数在内部调用自身本身&#xff0c;这个函数就是递归函数 核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解 一般来说&#xff0c;递归…

Azure-FunctionApp入门实战

介绍 FuntionApp 是微软云推出的一款serverless产品服务&#xff0c;作为coder可以无需关心程序部署所需的infra信息&#xff0c;只需要focus自己的业务代码实现即可 使用场景 是不是所有的业务都可以使用serverless产品呢&#xff1f;理论上是可以的&#xff0c;但是从实际…

初识mysql数据库之mysql数据库安装(centos)

目录 一、卸载不需要的环境 二、安装mysql yum源 三、安装mysql 四、登录mysql 1. 直接登录 2. 设置免密码登录 五、配置my.cnf 六、mysql登录时的一些选项介绍 一、卸载不需要的环境 要注意&#xff0c;在安装mysql数据库时&#xff0c;最好将用户切换为root&#xf…

Maven配置仓库、阿里云镜像、环境变量(史上最全最详细)

Maven配置仓库、阿里云镜像、环境变量&#xff08;史上最全最详细&#xff09; 一、前言 为了解决在maven的pom.xml文件中填入了某个依赖以后&#xff0c;maven却一直在转圈&#xff0c;非常慢的情况&#xff0c;我们需要进行配置阿里云镜像&#xff0c;这样会提升开发速度。…

Yolov5/Yolov7优化:引入Soft-NMS,提升密集遮挡场景检测精度

1.Soft-NMS介绍 论文地址:https://arxiv.org/pdf/1704.04503.pdf NMS需要优化的参数: IoU 的阈值是一个可优化的参数,一般范围为0~0.5,可以使用交叉验证来选择最优的参数。 R-CNN会从一张图片中找出n个可能是物体的矩形框,然后为每个矩形框为做类别分类概率: 就…

解密车载SOA架构原理,构建汽车通信的核心技术

车载SOA架构原理 车载SOA架构&#xff08;Service-Oriented Architecture&#xff0c;面向服务的架构&#xff09;是一种设计思想&#xff0c;旨在构建可扩展、灵活和可维护的车载系统。以下是车载SOA架构的一些原理和特点&#xff1a; 服务导向&#xff1a;车载SOA架构将车载…

探索ChatGPT:了解语言模型在对话系统中的应用

第一章&#xff1a;引言 在当今数字化时代&#xff0c;人工智能技术的迅猛发展使得对话系统成为一个备受关注的领域。随着语言模型的进步&#xff0c;像ChatGPT这样的模型正在改变我们与计算机进行交流的方式。本文将探索ChatGPT作为一种语言模型在对话系统中的应用&#xff0…

数据结构——顺序表(万字讲解)

单向链表&#xff08;又名单链表、线性链表&#xff09;是链表的一种&#xff0c;其特点是链表的链接方向是单向的&#xff0c;对链表的访问要通过从头部开始&#xff0c;依序往下读取。 //单链表的打印 void SLTPrint(SLTNode* phead) {SLTNode* cur phead;while (cur){pri…

python:使用Scikit-image库对单波段遥感图像做纹理特征提取(texture)

作者:CSDN @ _养乐多_ 本文将介绍使用Scikit-image库对单波段遥感图像做纹理特征提取的代码。包括:计算灰度共生矩阵(greycomatrix),计算局部二值模式(LBP)特征,计算方向梯度直方图(HOG)特征,使用Gabor滤波器提取纹理特征,计算图像纹理能量,在不同尺度上计算图像…

Android问题笔记-集成AndroidUSBCamera开源框架出现 “libjpeg-turbo1500.so“ not found

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

【干货】Android系统定制基础篇:第十五部分(Android支持鼠标右键返回、GPIO 控制方案、属性标识USB摄像头的VID与PID)

1、修改 frameworks/native/services/inputflinger/InputReader.cpp 如下&#xff1a; diff --git a/frameworks/native/services/inputflinger/InputReader.cpp b/frameworks/native/services/inputflinger/Inp index 7207a83..2721800 100755 --- a/frameworks/native/servi…

数字图像处理 基于matlab、opencv计算图像的梯度方向和梯度幅值

一、图像的梯度 1、简述 图像可以被视为标量场(即二维函数)。 通过微分将标量场转换为矢量场。 梯度是一个向量,描述了在x或y方向上移动时,图像变化的速度。我们使用导数来回答这样的问题,图像梯度的大小告诉图像变化的速度,而梯度的方向告诉图像变化最…

两轮车造爆款,爱玛的时尚战略胜算几何?

市场越卷&#xff0c;爆款的意义越大。 电动车行业就是这样&#xff08;本文仅指两轮电动车&#xff0c;如电动自行车、电动摩托车等出行工具&#xff09;&#xff0c;在CR2>45%、CR8>80%的市场格局下&#xff0c;行业竞争早已进入巷战阶段。 对头部的几个品牌&#xf…

leetcode 2090. K Radius Subarray Averages(半径为k的子数组的平均)

k半径长度的子数组表示以数组下标 i 为中心&#xff0c;[i-k, ik]范围内的子数组。 返回和数组nums一样长度的数组res&#xff0c;res[i] [i-k, ik]范围内的元素和 / 元素个数2k1 如果 i-k 或者 ik 超出了数组范围&#xff0c;res[i] -1. 思路&#xff1a; 如果[i - k, ik]…

荔枝集团出席扬帆出海PAGC 探讨AI在音频场景落地技术

近年来&#xff0c;中国企业的身影正不断活跃在全球商业版图上&#xff0c;无论是新兴技术的运用还是创新模式的尝试&#xff0c;其全球化的步履不停。近日,由扬帆出海主办的 2023产品与增长大会(简称PAGC)在广州广交会展馆召开&#xff0c;共同探索未来出海趋势和机遇&#xf…

6月20日作业

我实现的功能&#xff0c;执行一次应用程序led灯 亮&#xff0c;再次执行应用程序led灯灭。 内核模块&#xff1a; #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h&…

中职网络搭建(服务器)—Linux LVM(标准答案)

题目要求如下 使用fdisk-l查看磁盘信息 我们添加的磁盘分别是sdb和sdc Fdisk /dev/sdb 依次输入n,p,1&#xff0c;回车,2G &#xff08;新建2G的主分区&#xff09; 依次输入n,e,2&#xff0c;回车&#xff0c;回车&#xff08;使用剩余的全部空间建立扩展分区&#xff09; 依…

软件工程——第2章可行性研究知识点整理

本专栏是博主个人笔记&#xff0c;主要目的是利用碎片化的时间来记忆软工知识点&#xff0c;特此声明&#xff01; 文章目录 1.可行性研究的目的&#xff1f; 2.可行性研究的实质&#xff1f; 3.从哪些方面研究逻辑模型的解法可行性&#xff1f; 4.可行性研究最根本的任务是…

6个免费音效、配乐素材网站,无版权,可商用。

分享几个网站&#xff0c;配乐、音效素材都有&#xff0c;还是免费可商用的&#xff0c;希望能帮到大家&#xff0c;建议收藏起来~ 菜鸟图库 https://www.sucai999.com/audio.html?vNTYxMjky 虽然这是一个设计素材网站&#xff0c;但涵盖的素材非常广泛&#xff0c;想视频素材…