【传感器标定】路侧激光雷达和相机的标定(1)

2023-11-03 12:50

本文主要是介绍【传感器标定】路侧激光雷达和相机的标定(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.前言

        这里要描叙的场景比较简单,就是在路口矗立着路杆,杆子上面安装了多路摄像机,激光雷达和毫米波雷达。其中,摄像机用来提供目标的语义信息(如:行人,车辆等),激光雷达可以提供目标的位置和形状(三维)信息,而毫米波雷达可以比较准确地测量目标的速度。因此,采用以上多传感器融合,可以全方位地掌握路口目标的状态。在设计具体的融合算法之前要先进行传感器之间的联合标定。最近实验只需要用到激光雷达和摄像头,这里暂且值谈论激光雷达和相机的联合标定,更确切地说就是讨论激光雷达到图像的转换关系。这里的转换关系既涉及到激光雷达坐标系(三维)到相机坐标坐标系(三维)的转化,同时还设计到相机坐标系(三维)到图像(像素坐标系,二维)的转换。前一个转化我们说是激光雷达到相机的外参的标定,后一个转化是相机的内参的标定。通常认为相机的内参在出场之后是固定的,不会在使用过程中发生变化。

2. 坐标系转换

1). 相机坐标系到图像坐标系的转换        

        相机坐标系用来描叙物体相对相机的位置,相机固定以后相机坐标系的原点也就确定了,相机坐标系的原点为相机光心O点,相机坐标系中的坐标为(Xc,Yc,Zc)。图像坐标系是为了方便描叙成像过程中物体由相机坐标系(三维)到图像坐标系(二维)的投影投射关系而引入,而且可以方便进一步计算得到像素坐标系下的坐标,图像坐标系的单位同相机坐标系都为m。我们先引出小孔成像模型来描叙物体的成像过程。设O - x - y - z为相机坐标系,按右手法则,习惯上让z轴指向相机前方,x向右,y向下。O为摄像机的光心,也是针孔模型中的针孔。现实世界的空间点P,经过小孔O投影之后,落在物理成像平面O' - x' - y' - z'上,成像点为P' 。设P的坐标为[X,Y,Z] ^TP'[X',Y',Z'] ^T,并且设物理成像平面到小孔的距离为f(焦距)。根据三角形相似关系

\frac{Z}{f} = - \frac{X}{X'} = - \frac{Y}{Y'}

其中负号表示成的像是倒立的,为了让模型更符合实际(实际相机得到的像当然不是倒立的),可以等价地把成像平面对称地放置在相机的前方,这样可以把负号去掉。

\frac{Z}{f} = \frac{X}{X'} = \frac{Y}{Y'}

这里借用了一种数学模型的方式来描叙相机坐标系中的点与成像平面投影位置之间的关系。

2).图像坐标系到像素坐标系的转换

        在相机中,我们最终获得的是一个个的像素,而成像平面仍然是一个物理平面(单位为m),所以我们还需要在成像平面上对像进行采样和量化。为了描叙传感器将感受到的光线转换到图像像素的过程,设在物理成像平面上固定着一个像素平面 o−u−v。在像素平面得到了 P ′ 的像素坐标:[u,v] T 。其中像素坐标系 x 的定义方式是:原点 o ′ 位于图像的左上角,u 轴向右与 x 轴平行,v轴向下与 y 轴平行。像素坐标系与成像平面之间,相差了一个缩放和一个原点的平移。设像素坐标在 u 轴上缩放了 α 倍,在 v 上缩放了 β 倍。同时,原点平移了 [c x ,c y ] T 。那么,P ′ 的坐标与像素坐标 [u,v] T 的关系为

u = \alpha X' + c_x

v = \beta Y' + c_y

将上面两个式子合并

u = \alpha f\frac{X}{Z} + c_x = f_x\frac{X}{Z} + c_x

v = \beta f\frac{Y}{Z} + c_y = f_y\frac{Y}{Z} + c_y

其中,f 的单位为米,α,β 的单位为像素每米,所以 f x ,f y 的单位为像素。利用齐次坐标,将上式写成简洁的矩阵形式

或者

其中的K称之为相机的内参数矩阵,通常借助于"张氏标定法"来计算相机的内参。

void LoadCoordMap(std::string calibrationFile, cv::Size image_size, cv::OutputArray mapx, cv::OutputArray &mapy)
{cv::FileStorage fs1(calibrationFile, cv::FileStorage::READ);cv::Mat intrinsic = cv::Mat(3, 3, CV_32FC1);cv::Mat distCoeffs;fs1["intrinsic"] >> intrinsic;fs1["distCoeffs"] >> distCoeffs;double rate = 1;cv::Size outputSize(image_size.width * rate, image_size.height * rate);//cout << image_size << endl;//cout << outputSize << endl;cv::Mat R = cv::Mat::eye(3, 3, CV_32FC1);//cout << files.size() << "," << image_Seq.size() << endl;//assert(files.size() == image_Seq.size());cv::Mat intrinsic_mat(intrinsic), new_intrinsic_mat;intrinsic_mat.copyTo(new_intrinsic_mat);float p1 = 0.993;new_intrinsic_mat.at<float>(0, 0) *= p1;new_intrinsic_mat.at<float>(1, 1) *= p1;//new_intrinsic_mat.at<double>(0, 2) = 0.5 * image_size.width;//new_intrinsic_mat.at<double>(1, 2) = 0.5 * image_size.height;cv::initUndistortRectifyMap(intrinsic, distCoeffs, R,getOptimalNewCameraMatrix(new_intrinsic_mat, distCoeffs, image_size, 1, outputSize, 0),outputSize, CV_32FC1, mapx, mapy);
}void CorrectPicturesDir(const std::string &calibParamsFile, const std::string &inputDir, const std::string &outputDir, cv::Size inputSize) {std::vector<cv::String> files;cv::glob(inputDir, files);// Size image_size = imread(files[0]).size();cv::Size image_size = inputSize;cv::Mat mapx = cv::Mat(image_size, CV_32FC1);cv::Mat mapy = cv::Mat(image_size, CV_32FC1);LoadCoordMap(calibParamsFile, image_size, mapx, mapy);//initUndistortRectifyMap();for (int i = 0; i < files.size(); i++){cv::Mat dst;cv::Mat image = cv::imread(files[i]);cv::resize(image,image,image_size);//undistort(image, dst, intrinsic1, distCoeffs1);//cv::initUndistortRectifyMap();cv::remap(image, dst, mapx, mapy, cv::INTER_LINEAR, cv::BORDER_CONSTANT);char buf[256] = {0};sprintf(buf,"%d_u.jpg",i);std::string dstImgName = buf;cv::imwrite(outputDir + "/" + dstImgName, dst);//waitKey(1000);}
}int Calibration(std::string calibPicPath,cv::Size chessBoardSize,std::string calibParamsFile,cv::Size inputSize) {// load image filesstd::vector<cv::String> files;cv::glob(calibPicPath, files);std::vector<cv::Point3f> obj;int numCornersHor = chessBoardSize.width - 1;int numCornersVer = chessBoardSize.height - 1;int numSquares = (numCornersHor-1)*(numCornersVer-1);for (int i = 0; i < numCornersHor; i++) {for (int j = 0; j < numCornersVer; j++) {//world coordinate??obj.push_back(cv::Point3f((float)j * numSquares, (float)i * numSquares, 0));}}std::vector<std::vector<cv::Point2f>> imagePoints;std::vector<std::vector<cv::Point3f>> objectPoints;cv::TermCriteria criteria = cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 30, 0.001);for (int i = 0; i < files.size(); i++) {printf("image file : %s \n", files[i].c_str());cv::Mat image = cv::imread(files[i]);cv::resize(image,image,inputSize);cv::Mat gray;cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);std::vector<cv::Point2f> corners;//detect the corners of the chessboardbool ret = cv::findChessboardCorners(gray,cv::Size(numCornersVer,numCornersHor), //cv::Size(cols,rows)corners,cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS);if (ret) {cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1), criteria);cv::drawChessboardCorners(image, cv::Size(numCornersVer, numCornersHor), corners, ret); //draw corners in the pictureimagePoints.push_back(corners);objectPoints.push_back(obj);std::string imageFileName;std::stringstream strStm;strStm << "./tmp";strStm << i + 1;strStm >> imageFileName;imageFileName += "_corns.jpg";cv::imwrite(imageFileName.c_str(),image);cv::imshow("calibration-demo", image);if (cv::waitKey(-1) > 0) {continue;};}elseprintf("ret:%d;findChessboardCorners fail !!! \n",ret);}if (0 == imagePoints.size()) {std::cout << "no picture can find chessboard coners ... " << std::endl;return -1;}//https://nodyoung.blog.csdn.net/article/details/52287829cv::InputArrayOfArrays IMGPOINT = imagePoints;printf("%d \n", (int)IMGPOINT.total());cv::InputArrayOfArrays OBJPOINT = objectPoints;printf("%d \n", (int)OBJPOINT.total());for (int i = 0; i < 3; ++i) {printf("%d \n", OBJPOINT.getMat(i).checkVector(3, CV_32F));printf("%d \n", IMGPOINT.getMat(i).checkVector(2, CV_32F));}printf("%d pass\n",__LINE__);cv::Mat distCoeffs;cv::Mat intrinsic = cv::Mat(3, 3, CV_32FC1);std::vector<cv::Mat> rvecs; //rotation vectorstd::vector<cv::Mat> tvecs; //offset vectorintrinsic.ptr<float>(0)[0] = 1;intrinsic.ptr<float>(1)[1] = 1;cv::calibrateCamera(objectPoints, imagePoints, inputSize, intrinsic, distCoeffs, rvecs, tvecs);cv::FileStorage fs(calibParamsFile, cv::FileStorage::WRITE);fs << "distCoeffs" << distCoeffs;fs << "intrinsic" << intrinsic;fs << "inputSize" << inputSize;fs.release();return 0;
}int main() {cv::Size chessBoardSize(8, 7);cv::Size inputSize(1920,1080);std::string calibParamsFile = "../hikvision_6mm/params/hikvision_6mm.xml";std::string calibPicPath = "../hikvision_6mm/pictures"; //if (0 == Calibration(calibPicPath, chessBoardSize,calibParamsFile,inputSize)) {CorrectPicturesDir(calibParamsFile, calibPicPath, "./tmp", inputSize);} else {std::cout << "calibration error!!!" << std::endl;}return 0;
}

角点检测效果图:

3). 雷达坐标系到相机坐标系的转换

        雷达坐标系和相机坐标系都处于三维空间下,但是他们的坐标原点和各个坐标轴的方向不同(这个对于写代码很重要),所以进行刚体变换就可以了。所谓刚体变换是指在三维空间中,当物体不发生形变时,对一个几何物体做旋转,平移运动。我所用的激光雷达和摄像机坐标系都是右手坐标系(大拇指指向轴线的方向,四指的方向为旋转的正方向),可以通过刚体变换的方式将激光雷达坐标系转换到相机坐标系。下图为激光雷达坐标系到相机坐标系刚体变换的数学表达式,R表示3x3的旋转矩阵,T表示3x1的平移矩阵。

https://img-blog.csdnimg.cn/20201021101203391.png#pic_center

上面用了齐次坐标的表达方式,拆开来看如下。

直接摆出旋转矩阵R并不直观,很难想象出这个旋转矩阵究竟是什么样子。当他们变换时也不知道物体是在向那个方向转动。借助于欧拉角可以让问题变得更加直观,它使用3个分离的转角来描叙旋转,把一个旋转分解成3次绕不同轴的转动。最终的R就是这3次转动叠加之后的效果,用数学表达时表示就是3个旋转矩阵的乘积。例如,激光雷达坐标系分别绕X轴旋转角度roll(翻滚角),绕Y轴旋转角度pitch(俯仰角),绕z轴旋转角度yaw(偏航角)。如此三个维度的旋转矩阵分别是:

而R=RxRyRz:

旋转之后的激光雷达坐标系与相机坐标系同向,但是坐标原点的位置并没有变,所以还要再加上一个平移T。这里的旋转矩阵和平移向量的求解即我们要做的由激光雷达到相机的外参标定。怎么标呢?网上提供了不少的思路。比较常见的比如使用Autoware标定工具,使用solvePnp求解等。这里为了理解标定的具体原理采用手动标定的方式,通过按键移动激光雷达坐标系的位置以及旋转的角度来对齐激光雷达和相机坐标系下的参照物。这种方式虽然存在一定的误差,而且相对比较依赖经验,但是在标定的过程中比较有参与感,对于理解标定原理很有意义。

【参考文献】

https://blog.csdn.net/floatinglong/article/details/116175325

https://blog.csdn.net/learning_tortosie/article/details/82347694 #激光雷达和相机的联合标定

https://blog.csdn.net/qq_29462849/article/details/88748028#commentBox #激光雷达和相机的联合标定的实现

https://blog.csdn.net/fireflychh/article/details/82352710 #坐标轴旋转

https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf #计算欧拉角

 

这篇关于【传感器标定】路侧激光雷达和相机的标定(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/338274

相关文章

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

海鸥相机存储卡格式化如何恢复数据

在摄影的世界里,‌每一张照片都承载着独特的记忆与故事。‌然而,‌当我们不慎将海鸥相机的存储卡格式化后,‌那些珍贵的瞬间似乎瞬间消逝,‌让人心急如焚。‌但请不要绝望,‌数据恢复并非遥不可及。‌本文将详细介绍在海鸥相机存储卡格式化后,‌如何高效地恢复丢失的数据,‌帮助您重新找回那些宝贵的记忆。‌ 图片来源于网络,如有侵权请告知 一、‌回忆备份情况 ‌海鸥相机存储卡格式化如何恢复数据?在意

解析apollo纵向控制标定表程序

百度apollo采用标定表描述车辆速度、加速度与油门/刹车之间的关系。该表可使无人车根据当前车速与期望加速度得到合适的油门/刹车开合度。除了文献《Baidu Apollo Auto-Calibration System - An Industry-Level Data-Driven and Learning based Vehicle Longitude Dynamic Calibrating

【LVI-SAM】激光雷达点云处理特征提取LIO-SAM 之FeatureExtraction实现细节

激光雷达点云处理特征提取LIO-SAM 之FeatureExtraction实现细节 1. 特征提取实现过程总结1.0 特征提取过程小结1.1 类 `FeatureExtraction` 的整体结构与作用1.2 详细特征提取的过程1. 平滑度计算(`calculateSmoothness()`)2. 标记遮挡点(`markOccludedPoints()`)3. 特征提取(`extractF

机器视觉硬件选型根据某项目相机镜头

一 项目总需求 1、大视野检测需求: (1)大视野: ①产品尺寸15.6寸屏幕,产品大小:350mm x 225mm; ②产品料盘尺寸大小:565mm x 425mm; ③工作距离:880mm;检测精度:500μm; 1、大视野检测需求: (1)大视野: ①产品尺寸15.6寸屏幕,产品大小:350mm x 225mm; ②产品料盘尺寸大小:565mm x 425mm; 工作距离:

005:VTK世界坐标系中的相机和物体

VTK医学图像处理---世界坐标系中的相机和物体 左侧是成像结果                                                    右侧是世界坐标系中的相机与被观察物体 目录 VTK医学图像处理---世界坐标系中的相机和物体 简介 1 在三维空间中添加坐标系 2 世界坐标系中的相机 3 世界坐标系中vtkImageData的参数 总结:

人体红外传感器简介

人体红外传感器的工作原理是利用热释电效应,将人体发出的特定波长的红外线转化为电信号,从而实现对人体的检测和感知。               具体来说,人体红外传感器主要由滤光片、热释电探测元和前置放大器组成。滤光片的作用是使特定波长的红外辐射选择性地通过,到达热释电探测元,而在其截止范围外的红外辐射则不能通过。热释电探测元是传感器的核心元件,当它受到非恒定强度的红外光照射时,会产生

独立双端App《瓦格相机》的开发过程分享

前言 Hello大家好,我是灯灯,独立开发者灯灯,也是天天学藏语的灯灯,哈哈哈... 好了屁话少说,今天和大家分享一下最近自己独立制作一款应用的经验历程,希望能对刚刚起步的新手们、还有独立开发者们有所帮助。 什么样的应用 我想做的是一款能够将照片转换成文字拼成的图片应用,也就是,图片中的每一个像素点都将会被文字取代,同时对应色彩、密集程度等。 之所以想做这样的应用是因为早在我高中的时候,

相机检查内参 外参

目录 检查内参 外参 像素点投影到世界坐标系,再投回到2d坐标系: 检查内参 外参 import cv2import numpy as np# 假设我们有以下相机内参K = np.array([[418.96369417, 0.0, 489.16315478],[0.0, 419.04813353, 267.88796254],[0.0, 0.0, 1.0]], dtype=n

相机拍摄时最重要的三个参数——光圈、快门、ISO

如果你对相机只有很少了解,那么看这篇文章再好不过啦,我结合很多资料,力图用最通俗易懂的方式进行讲解。 相机拍摄时最重要的3个参数就是——光圈、快门、ISO 次重要的参数有——焦距、景深、曝光   在介绍光圈、快门、ISO之前,必须先介绍曝光。曝光准确的照片:   过曝的照片:   欠曝的照片:   我们把一张完美曝光的照片理解成一桶刚刚装满的水,不