图像获取与单目定标

2023-10-13 20:20
文章标签 图像 获取 定标 单目

本文主要是介绍图像获取与单目定标,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://blog.csdn.net/chenyusiyuan/article/details/5961769


双目测距的基本原理

clip_image002[10]

如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差clip_image004 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,f的量纲是像素点,T的量纲由定标板棋盘格的实际尺寸和用户输入值确定,一般是以毫米为单位(当然为了精度提高也可以设置为0.1毫米量级),d=xl-xr的量纲也是像素点。因此分子分母约去,Z的量纲与T相同。 

clip_image006

假设目标点在左视图中的坐标为(x,y),在左右视图上形成的视差为d,目标点在以左摄像头光心为原点的世界坐标系中的坐标为(X,Y,Z),则存在上图所示的变换矩阵Q,使得 Q*[x y d 1]’ = [X Y Z W]’。

“@scyscyao :为了精确地求得某个点在三维空间里的距离Z,我们需要获得的参数有焦距f、视差d、摄像头中心距Tx。如果还需要获得X坐标和Y坐标的话,那么还需要额外知道左右像平面的坐标系与立体坐标系中原点的偏移cx和cy。其中f, Tx, cx和cy可以通过立体标定获得初始值,并通过立体校准优化,使得两个摄像头在数学上完全平行放置,并且左右摄像头的cx, cy和f相同(也就是实现图2中左右视图完全平行对准的理想形式)。而立体匹配所做的工作,就是在之前的基础上,求取最后一个变量:视差d(这个d一般需要达到亚像素精度)。从而最终完成求一个点三维坐标所需要的准备工作。在清楚了上述原理之后,我们也就知道了,所有的这几步:标定、校准和匹配,都是围绕着如何更精确地获得 f, d, Tx, cx 和cy 而设计的 。 ”

 

一、图像的获取

1. 如何打开两个或多个摄像头?

可以通过OpenCV的capture类函数或者结合DirectShow来实现双摄像头的捕获,具体可见我的读书笔记《OpenCV学习笔记(6)基于 VC+OpenCV+DirectShow 的多个摄像头同步工作 》。文中曾提及不能用cvCreateCameraCapture 同时读取两个摄像头,不过后来一位研友来信讨论说只要把摄像头指针的创建代码按照摄像头序号降序执行,就可以顺利打开多个摄像头 ,例如:

 

  
[c-sharp] view plaincopy
  1. CvCapture* capture2 = cvCreateCameraCapture( 1 );  
  2. CvCapture* capture1 = cvCreateCameraCapture( 0 );  

采用DirectShow的方式读入时:

  
[c-sharp] view plaincopy
  1. camera2.OpenCamera(1, false, 640,480);  
  2. camera1.OpenCamera(0, false, 640,480);  

 

这样就可以同时采集两个摄像头。我也验证过这种方法确实有效,而且还解决了我遇到的cvSetCaptureProperty调整帧画面大小速度过慢的问题。当摄像头的打开或创建代码按照摄像头序号从0开始以升序编写执行时,使用cvSetCaptureProperty就会出现第一个摄像头(序号为0)的显示窗口为灰色(即无图像)、且程序运行速度缓慢的现象。而改为降序编写执行后,则能正常、实时地显示各摄像头的画面。具体原因有待分析讨论。

 

2. 如何实现多个摄像头帧画面的同步抓取?

在单摄像头情况下用 cvQueryFrame 即可抓取一帧画面,实际上这个函数是由两个routine组成的:cvGrabFrame和cvRetrieveFrame(详见Learning OpenCV第103页)。cvGrabFrame将摄像头帧画面即时复制到内部缓存中,然后通过cvRetrieveFrame把我们预定义的一个IplImage型空指针指向缓存内的帧数据。注意这时我们并没有真正把帧数据取出来,它还保存在OpenCV的内部缓存中,下一次读取操作就会被覆盖掉。所以一般我们要另外定义一个IplImage来复制所抓取的帧数据,然后对这个新IplImage进行操作。

由上面的解释也可以看出,cvGrabFrame的作用就是尽可能快的将摄像头画面数据复制到计算机缓存,这个功能就方便我们实现对多个摄像头的同步抓取,即首先用cvGrabFrame依次抓取各个CvCapture*,然后再用cvRetrieveFrame把帧数据取出来。例如:

  
[c-sharp] view plaincopy
  1. cvGrabFrame( lfCam );  
  2. cvGrabFrame( riCam );  
  3. frame1 = cvRetrieveFrame( lfCam );  
  4. frame2 = cvRetrieveFrame( riCam );  
  5. if( !frame1|| !frame2) break;  
  6. cvCopyImage(frame1, image1);   
  7. cvCopyImage(frame2, image2);  

二、摄像头定标

摄像头定标一般都需要一个放在摄像头前的特制的标定参照物(棋盘纸),摄像头获取该物体的图像,并由此计算摄像头的内外参数。标定参照物上的每一个特征点相对于世界坐标系的位置在制作时应精确测定,世界坐标系可选为参照物的物体坐标系。在得到这些已知点在图像上的投影位置后,可计算出摄像头的内外参数。

clip_image008

如上图所示,摄像头由于光学透镜的特性使得成像存在着径向畸变,可由三个参数k1,k2,k3确定;由于装配方面的误差,传感器与光学镜头之间并非完全平行,因此成像存在切向畸变,可由两个参数p1,p2确定。单个摄像头的定标主要是计算出摄像头的内参(焦距f和成像原点cx,cy、五个畸变参数(一般只需要计算出k1,k2,p1,p2,对于鱼眼镜头等径向畸变特别大的才需要计算k3))以及外参(标定物的世界坐标)。 OpenCV 中使用的求解焦距和成像原点的算法是基于张正友的方法( pdf ),而求解畸变参数是基于 Brown 的方法( pdf )。


1. 图像坐标系、摄像头坐标系和世界坐标系的关系

clip_image010 clip_image012

摄像头成像几何关系,其中Oc 点称为摄像头(透镜)的光心,Xc 轴和Yc 轴与图像的x轴和Y轴平行,Zc 轴为摄像头的光轴,它与图像平面垂直。光轴与图像平面的交点O1 ,即为图像坐标系的原点。由点Oc 与Xc 、Yc 、Zc 轴组成的坐标系称为摄像头坐标系,Oc O1 的距离为摄像头焦距,用f表示。

图像坐标系是一个二维平面,又称为像平面,“@scyscyao :实际上就是摄像头的CCD传感器的表面。每个CCD传感器都有一定的尺寸,也有一定的分辨率,这个就确定了毫米与像素点之间的转换关系。举个例子,CCD的尺寸是8mm X 6mm,帧画面的分辨率设置为640X480,那么毫米与像素点之间的转换关系就是80pixel/mm。”设CCD传感器每个像素点的物理大小为dx*dy,相应地,就有 dx=dy=1/80。


2. 进行摄像头定标时,棋盘方格的实际大小 square_size (默认为 1.0f )的设置对定标参数是否有影响?

“@scyscyao :当然有。在标定时,需要指定一个棋盘方格的长度,这个长度(一般以毫米为单位,如果需要更精确可以设为0.1毫米量级)与实际长度相同,标 定得出的结果才能用于实际距离测量。一般如果尺寸设定准确的话,通过立体标定得出的Translation向量的第一个分量Tx的绝对值就是左右摄像头的中心距。一般可以用这个来验证立体标定的准确度。比如我设定的棋盘格大小为270 (27mm),最终得出的Tx大小就是602.8 (60.28mm),相当精确。”

 

3. 定标所得的摄像头内参数,即焦距和原点坐标,其数值单位都是一致的吗?怎么把焦距数值换算为实际的物理量?

“@wobject :是的,都是以像素为单位。假设像素点的大小为k x l,单位为mm,则fx = f / k, fy = f / (l * sinA), A一般假设为 90°,是指摄像头坐标系的偏斜度(就是镜头坐标和CCD是否垂直)。摄像头矩阵(内参)的目的是把图像的点从图像坐标转换成实际物理的三维坐标。因此其中的fx, fy, cx, cy 都是使用类似上面的纲量。同样,Q 中的变量 f,cx, cy 也应该是一样的。”

 

4. 棋盘图像数目应该取多少对摄像头定标比较适宜?

OpenCV中文论坛上piao的帖子《在OpenCV中用cvCalibrateCamera2进行相机标定(附程序) 》中指出影响摄像头定标结果的准确性和稳定性的因素主要有三个:

(1) 标定板所在平面与成像平面(image plane)之间的夹角;

(2) 标定时拍摄的图片数目(棋盘图像数目);

(3) 图像上角点提取的不准确。

感觉OpenCV1.2以后对图像角点的提取准确度是比较高的,cvFindChessboardCorners 和 cvFindCornerSubPix结合可以获得很好的角点检测效果(hqhuang1在《[HQ]角点检测(Corner Detection) cvFindCornerSubPix 使用范例 》中给出了相关的应用范例)。因此,影响定标结果较大的就是标定板与镜头的夹角和棋盘图像数目,在实际定标过程中,我感觉棋盘图像数目应该大于20张,每成功检测一次完整的棋盘角点就要变换一下标定板的姿态(包括角度、距离) 

 

5. 单目定标函数cvCalibrateCamera2采用怎样的 flags 比较合适?

由于一般镜头只需要计算k1,k2,p1,p2四个参数,所以我们首先要设置 CV_CALIB_FIX_K3;其次,如果所用的摄像头不是高端的、切向畸变系数非常少的,则不要设置 CV_CALIB_ZERO_TANGENT_DIST,否则单目校正误差会很大;如果事先知道摄像头内参的大概数值,并且cvCalibrateCamera2函数的第五个参数intrinsic_matrix非空,则也可设置 CV_CALIB_USE_INTRINSIC_GUESS ,以输入的intrinsic_matrix为初始估计值来加快内参的计算;其它的 flag 一般都不需要设置,对单目定标的影响不大。

 

P.S. 使用OpenCV进行摄像机定标虽然方便,但是定标结果往往不够准确和稳定,最好是使用 Matlab标定工具箱 来进行定标,再将定标结果取回来用于立体匹配和视差计算。工具箱的使用官方主页 有图文并茂的详细说明,此外,有两篇博文也进行了不错的总结,推荐阅读:

(1)分享一些OpenCV实现立体视觉的经验

(2)Matlab标定工具箱使用的一些注意事项

这篇关于图像获取与单目定标的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

Android Environment 获取的路径问题

1. 以获取 /System 路径为例 /*** Return root of the "system" partition holding the core Android OS.* Always present and mounted read-only.*/public static @NonNull File getRootDirectory() {return DIR_ANDR

Verybot之OpenCV应用一:安装与图像采集测试

在Verybot上安装OpenCV是很简单的,只需要执行:         sudo apt-get update         sudo apt-get install libopencv-dev         sudo apt-get install python-opencv         下面就对安装好的OpenCV进行一下测试,编写一个通过USB摄像头采

JS和jQuery获取节点的兄弟,父级,子级元素

原文转自http://blog.csdn.net/duanshuyong/article/details/7562423 先说一下JS的获取方法,其要比JQUERY的方法麻烦很多,后面以JQUERY的方法作对比。 JS的方法会比JQUERY麻烦很多,主要则是因为FF浏览器,FF浏览器会把你的换行也当最DOM元素。 <div id="test"><div></div><div></div

vcpkg子包路径批量获取

获取vcpkg 子包的路径,并拼接为set(CMAKE_PREFIX_PATH “拼接路径” ) import osdef find_directories_with_subdirs(root_dir):# 构建根目录下的 "packages" 文件夹路径root_packages_dir = os.path.join(root_dir, "packages")# 如果 "packages"

Weex入门教程之4,获取当前全局环境变量和配置信息(屏幕高度、宽度等)

$getConfig() 获取当前全局环境变量和配置信息。 Returns: config (object): 配置对象;bundleUrl (string): bundle 的 url;debug (boolean): 是否是调试模式;env (object): 环境对象; weexVersion (string): Weex sdk 版本;appName (string): 应用名字;

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

android两种日志获取log4j

android   log4j 加载日志使用方法; 先上图: 有两种方式: 1:直接使用架包 加载(两个都要使用); 架包:android-logging-log4j-1.0.3.jar 、log4j-1.2.15.jar  (说明:也可以使用架包:log4j-1.2.17.jar)  2:对架包输入日志的二次封装使用; 1:直接使用 log4j 日志框架获取日志信息: A:配置 日志 文

17 通过ref代替DOM用来获取元素和组件的引用

重点 ref :官网给出的解释是: ref: 用于注册对元素或子组件的引用。引用将在父组件的$refs 对象下注册。如果在普通DOM元素上使用,则引用将是该元素;如果在子组件上使用,则引用将是组件实例: <!-- vm.$refs.p will be the DOM node --><p ref="p">hello</p><!-- vm.$refs.child will be the c

react笔记 8-19 事件对象、获取dom元素、双向绑定

1、事件对象event 通过事件的event对象获取它的dom元素 run=(event)=>{event.target.style="background:yellowgreen" //event的父级为他本身event.target.getAttribute("aid") //这样便获取到了它的自定义属性aid}render() {return (<div><h2>{