双目摄像头协同工作.docx
《双目摄像头协同工作.docx》由会员分享,可在线阅读,更多相关《双目摄像头协同工作.docx(14页珍藏版)》请在冰豆网上搜索。
双目摄像头协同工作
#include"stdafx.h"
#include"camerads.h"
#include
//单窗口显示多幅图像的函数
voidcvShowMultiImages(char*title,intnArgs,...)
{
//略,详见学习笔记(5)
}
intmain(intargc,char**argv)
intcam_count;
//仅仅获取摄像头数目
cam_count=CCameraDS:
:
CameraCount();
printf("Thereare%dcameras./n",cam_count);
//获取所有摄像头的名称
for(inti=0;i{charcamera_name[1024];intretval=CCameraDS::CameraName(i,camera_name,sizeof(camera_name));if(retval>0)printf("Camera#%d'sNameis'%s'./n",i,camera_name);elseprintf("CannotgetCamera#%d'sname./n",i);}if(cam_count==0)return-1;//创建2个摄像头类CCameraDScamera1;CCameraDScamera2;//打开第一个摄像头//if(!camera.OpenCamera(0,true))//弹出属性选择窗口if(!camera1.OpenCamera(0,false,320,240))//不弹出属性选择窗口,用代码制定图像宽和高{fprintf(stderr,"Cannotopencamera./n");return-1;}//打开第二个摄像头camera2.OpenCamera(1,false,320,240); cvNamedWindow("MultipleCameras");//初始化在子图像中显示字符的字体格式CvFonttFont;cvInitFont(&tFont,CV_FONT_HERSHEY_COMPLEX,0.5f,0.7f,0,1,8);charcam1str[]="Camera#1";charcam2str[]="Camera#2";//为读取系统时间信息分配内存chartimestr[25];memset(timestr,0,25*sizeof(char));while(1){//获取一帧IplImage*pFrame1=camera1.QueryFrame();IplImage*pFrame2=camera2.QueryFrame();//获取当前帧的灰度图IplImage*frame_gray_1=cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,1);IplImage*frame_gray_2=cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,1);cvCvtColor(pFrame1,frame_gray_1,CV_RGB2GRAY);cvCvtColor(pFrame2,frame_gray_2,CV_RGB2GRAY);//对灰度图像进行Canny边缘检测//然后将图像通道数改为三通道IplImage*frame_canny_1=cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,1);IplImage*frame_canny_2=cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,1);IplImage*frame1=cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,pFrame1->nChannels);IplImage*frame2=cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,pFrame2->nChannels);cvCanny(frame_gray_1,frame_canny_1,20,75,3);cvCanny(frame_gray_2,frame_canny_2,20,75,3);cvCvtColor(frame_canny_1,frame1,CV_GRAY2BGR);cvCvtColor(frame_canny_2,frame2,CV_GRAY2BGR); //获取系统时间信息time_trawtime;structtm*timeinfo;rawtime=time(NULL);timeinfo=localtime(&rawtime);char*p=asctime(timeinfo);//字符串p的第25个字符是换行符'/n'//但在子图像中将乱码显示//故仅读取p的前24个字符for(inti=0;i<24;i++){timestr[i]=*p;p++;}p=NULL;//在每个子图像上显示摄像头序号以及系统时间信息cvPutText(pFrame1,cam1str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));cvPutText(pFrame2,cam2str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));cvPutText(frame1,cam1str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));cvPutText(frame2,cam2str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));cvPutText(pFrame1,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));cvPutText(pFrame2,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));cvPutText(frame1,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));cvPutText(frame2,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));//显示实时的摄像头视频cvShowMultiImages("MultipleCameras",4,pFrame1,pFrame2,frame1,frame2); //cvWaitKey(33);intkey=cvWaitKey(33);if(key==27)break;cvReleaseImage(&frame1);cvReleaseImage(&frame2);cvReleaseImage(&frame_gray_1);cvReleaseImage(&frame_gray_2);cvReleaseImage(&frame_canny_1);cvReleaseImage(&frame_canny_2);}camera1.CloseCamera();//可不调用此函数,CCameraDS析构时会自动关闭摄像头camera2.CloseCamera();cvDestroyWindow("MultipleCameras");return0;}说到双摄像头测距,首先要复习一下测距原理,把LearningOpenCV翻到416和418页,可以看到下面两幅图图1.双摄像头模型俯视图图2,双摄像头模型立体视图 图1解释了双摄像头测距的原理,书中Z的公式如下: 在OpenCV中,f的量纲是像素点,Tx的量纲由定标棋盘格的实际尺寸和用户输入值确定,一般总是设成毫米,当然为了精度提高也可以设置为0.1毫米量级,d=xl-xr的量纲也是像素点。因此分子分母约去,z的量纲与Tx相同 图2解释了双摄像头获取空间中某点三维坐标的原理。 可以看到,实际的坐标计算利用的都是相似三角形的原理,其表达式就如同Q矩阵所示。 空间中某点的三维坐标就是(X/W,Y/W,Z/W)。 因此,为了精确地求得某个点在三维空间里的距离,我们需要获得的参数有焦距f、视差d、摄像头中心距Tx。如果还需要获得X坐标和Y坐标的话,那么还需要额外知道左右像平面的坐标系与立体坐标系中原点的偏移cx和cy。其中f,Tx,cx和cy可以通过立体标定获得初始值,并通过立体校准优化,使得两个摄像头在数学上完全平行放置,并且左右摄像头的cx,cy和f相同(也就是实现图2中左右视图完全平行对准的理想形式)。而立体匹配所做的工作,就是在之前的基础上,求取最后一个变量:视差d(这个d一般需要达到亚像素精度)。从而最终完成求一个点三维坐标所需要的准备工作。 在清楚了上述原理之后,我们也就知道了,所有的这几步:标定、校准和匹配,都是围绕着如何更精确地获得f,d,Tx,cx和cy而设计的。 双目测距的原理就说到这里,为了避免大家看到大段纯叙述性的文字头晕,下面的行文将会以FAQ的形式围绕着实现双摄像头测距过程中碰到的几点疑惑展开。当然,其中的解答也只是我的个人理解,如有不当,敬请指正。Q1:标定时棋盘格的大小如何设定,对最后结果有没有影响?A:当然有。在标定时,需要指定一个棋盘方格的长度,这个长度(一般以毫米为单位,如果需要更精确可以设为0.1毫米量级)与实际长度相同,标定得出的结果才能用于实际距离测量。一般如果尺寸设定准确的话,通过立体标定得出的Translation的向量的第一个分量Tx的绝对值就是左右摄像头的中心距。一般可以用这个来验证立体标定的准确度。比如我设定的棋盘格大小为270(27mm),最终得出的Tx大小就是602.8(60.28mm),相当精确。 Q2:通过立体标定得出的Tx符号为什么是负的?A:这个其实我也不是很清楚。个人的解释是,立体标定得出的T向量指向是从右摄像头指向左摄像头(也就是Tx为负),而在OpenCV坐标系中,坐标的原点是在左摄像头的。因此,用作校准的时候,要把这个向量的三个分量符号都要换一下,最后求出的距离才会是正的。但是这里还有一个问题,就是LearningOpenCV中Q的表达式,第四行第三列元素是-1/Tx,而在具体实践中,求出来的实际值是1/Tx。这里我和maxwellsdemon讨论下来的结果是,估计书上Q表达式里的这个负号就是为了抵消T向量的反方向所设的,但在实际写OpenCV代码的过程中,那位朋友却没有把这个负号加进去。(一家之言,求更详细的解释) Q3:cvFindStereoCorrespondenceBM的输出结果好像不是以像素点为单位的视差?A:在OpenCV2.0中,BM函数得出的结果是以16位符号数的形式的存储的,出于精度需要,所有的视差在输出时都扩大了16倍(2^4)。其具体代码表示如下:dptr[y*dstep]=(short)(((ndisp-mind-1+mindisp)*256+(d!=0?(p-n)*128/d:0)+15)>>4);可以看到,原始视差在左移8位(256)并且加上一个修正值之后又右移了4位,最终的结果就是左移4位因此,在实际求距离时,cvReprojectTo3D出来的X/W,Y/W,Z/W都要乘以16(也就是W除以16),才能得到正确的三维坐标信息Q4:利用双摄像头进行测距的时候世界坐标的原点究竟在哪里? A:世界坐标系的原点是左摄像头凸透镜的光心。说起这个,就不得不提到针孔模型。如图3所示,针孔模型是凸透镜成像的一种简化模型。当物距足够远时(远大于两倍焦距),凸透镜成像可以看作是在焦距处的小孔成像。(ref:图3.针孔模型在实际计算过程中,为了计算方便,我们将像平面翻转平移到针孔前,从而得到一种数学上更为简单的等价形式(方便相似三角形的计算),如图4所示。图4.针孔模型的数学等价形式 因此,对应图2就可以知道,世界坐标系原点就是左摄像头针孔模型的针孔,也就是左摄像头凸透镜的光心 Q5:f和d的单位是像素,那这个像素到底表示什么,它与毫米之间又是怎样换算的?A:这个问题也与针孔模型相关。在针孔模型中,光线穿过针孔(也就是凸透镜中心)在焦距处上成像,因此,图3的像平面就是摄像头的CCD传感器的表面。每个CCD传感器都有一定的尺寸,也有一定的分辨率,这个就确定了毫米与像素点之间的转换关系。举个例子,CCD的尺寸是8mmX6mm,分辨率是640X480,那么毫米与像素点之间的转换关系就是80pixel/mm。在实际运用中,我们在数学上将这个像平面等效到小孔前(图4),这样就相当于将在透镜中心点之前假设了一块虚拟的CCD传感器。 Q6:为什么cvStereoRectify求出的Q矩阵cx,cy,f都与原来的不同?A:这个在前文有提到过。在实际测量中,由于摄像头摆放的关系,左右摄像头的f,cx,cy都是不相同的。而为了使左右视图达到完全平行对准的理想形式从而达到数学上运算的方便,立体校准所做的工作事实上就是在左右像重合区域最大的情况下,让两个摄像头光轴的前向平行,并且让左右摄像头的f,cx,cy相同。因此,Q矩阵中的值与两个instrinsic矩阵的值不一样就可以理解了。 实验结果:实验下来,虽然BlockMatching算法本身对精度有所限制,但测距基本能达到能让人接受的精度,结果如下图5所示 图5.OpenCV双摄像头测距结果上图中,中、左、右三个物体分别被放在离摄像头50cm,75cm和90cm的位置。可以看出测距的结果相当不错。当然,上面这幅图是比较好的结果。由于BM算法的限制,同一点云中相同距离的点一般会有正负2厘米之内的误差。图6是利用双目摄像头测物体长宽的结果,可以看出结果似乎不太准确。。。图6.OpenCV双摄像头测边长结果其中,物体宽为117-88=29mm,但实际宽度为5.2cm,物体高位71-13=58mm,但实际高度为13cm。这方面的误差还是比较难以理解此外,还有一个问题至今尚未完全理解,就是双目摄像头的中心距,为什么采用Tx而不是T向量的长度。因为如果要左右视图重合区域最大化的话两个摄像头的光轴都要与T垂直才是(如图7),这样的话,校正后两个摄像头的中心距应该是T才对。不知道我这样的理解对不对?图7.双摄像头立体校准俯视图
charcamera_name[1024];
intretval=CCameraDS:
CameraName(i,camera_name,sizeof(camera_name));
if(retval>0)
printf("Camera#%d'sNameis'%s'./n",i,camera_name);
else
printf("CannotgetCamera#%d'sname./n",i);
if(cam_count==0)
return-1;
//创建2个摄像头类
CCameraDScamera1;
CCameraDScamera2;
//打开第一个摄像头
//if(!
camera.OpenCamera(0,true))//弹出属性选择窗口
if(!
camera1.OpenCamera(0,false,320,240))//不弹出属性选择窗口,用代码制定图像宽和高
fprintf(stderr,"Cannotopencamera./n");
//打开第二个摄像头
camera2.OpenCamera(1,false,320,240);
cvNamedWindow("MultipleCameras");
//初始化在子图像中显示字符的字体格式
CvFonttFont;
cvInitFont(&tFont,CV_FONT_HERSHEY_COMPLEX,0.5f,0.7f,0,1,8);
charcam1str[]="Camera#1";
charcam2str[]="Camera#2";
//为读取系统时间信息分配内存
chartimestr[25];
memset(timestr,0,25*sizeof(char));
while
(1)
//获取一帧
IplImage*pFrame1=camera1.QueryFrame();
IplImage*pFrame2=camera2.QueryFrame();
//获取当前帧的灰度图
IplImage*frame_gray_1=cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,1);
IplImage*frame_gray_2=cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,1);
cvCvtColor(pFrame1,frame_gray_1,CV_RGB2GRAY);
cvCvtColor(pFrame2,frame_gray_2,CV_RGB2GRAY);
//对灰度图像进行Canny边缘检测
//然后将图像通道数改为三通道
IplImage*frame_canny_1=cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,1);
IplImage*frame_canny_2=cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,1);
IplImage*frame1=cvCreateImage(cvGetSize(pFrame1),pFrame1->depth,pFrame1->nChannels);
IplImage*frame2=cvCreateImage(cvGetSize(pFrame2),pFrame2->depth,pFrame2->nChannels);
cvCanny(frame_gray_1,frame_canny_1,20,75,3);
cvCanny(frame_gray_2,frame_canny_2,20,75,3);
cvCvtColor(frame_canny_1,frame1,CV_GRAY2BGR);
cvCvtColor(frame_canny_2,frame2,CV_GRAY2BGR);
//获取系统时间信息
time_trawtime;
structtm*timeinfo;
rawtime=time(NULL);
timeinfo=localtime(&rawtime);
char*p=asctime(timeinfo);
//字符串p的第25个字符是换行符'/n'
//但在子图像中将乱码显示
//故仅读取p的前24个字符
for(inti=0;i<24;i++)
timestr[i]=*p;
p++;
p=NULL;
//在每个子图像上显示摄像头序号以及系统时间信息
cvPutText(pFrame1,cam1str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));
cvPutText(pFrame2,cam2str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));
cvPutText(frame1,cam1str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));
cvPutText(frame2,cam2str,cvPoint(95,15),&tFont,CV_RGB(255,0,0));
cvPutText(pFrame1,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));
cvPutText(pFrame2,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));
cvPutText(frame1,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));
cvPutText(frame2,timestr,cvPoint(5,225),&tFont,CV_RGB(255,0,0));
//显示实时的摄像头视频
cvShowMultiImages("MultipleCameras",4,pFrame1,pFrame2,frame1,frame2);
//cvWaitKey(33);
intkey=cvWaitKey(33);
if(key==27)break;
cvReleaseImage(&frame1);
cvReleaseImage(&frame2);
cvReleaseImage(&frame_gray_1);
cvReleaseImage(&frame_gray_2);
cvReleaseImage(&frame_canny_1);
cvReleaseImage(&frame_canny_2);
camera1.CloseCamera();//可不调用此函数,CCameraDS析构时会自动关闭摄像头
camera2.CloseCamera();
cvDestroyWindow("MultipleCameras");
return0;
说到双摄像头测距,首先要复习一下测距原理,把LearningOpenCV翻到416和418页,可以看到下面两幅图
图1.双摄像头模型俯视图
图2,双摄像头模型立体视图
图1解释了双摄像头测距的原理,书中Z的公式如下:
在OpenCV中,f的量纲是像素点,Tx的量纲由定标棋盘格的实际尺寸和用户输入值确定,一般总是设成毫米,当然为了精度提高也可以设置为0.1毫米量级,d=xl-xr的量纲也是像素点。
因此分子分母约去,z的量纲与Tx相同
图2解释了双摄像头获取空间中某点三维坐标的原理。
可以看到,实际的坐标计算利用的都是相似三角形的原理,其表达式就如同Q矩阵所示。
空间中某点的三维坐标就是(X/W,Y/W,Z/W)。
因此,为了精确地求得某个点在三维空间里的距离,我们需要获得的参数有焦距f、视差d、摄像头中心距Tx。
如果还需要获得X坐标和Y坐标的话,那么还需要额外知道左右像平面的坐标系与立体坐标系中原点的偏移cx和cy。
其中f,Tx,cx和cy可以通过立体标定获得初始值,并通过立体校准优化,使得两个摄像头在数学上完全平行放置,并且左右摄像头的cx,cy和f相同(也就是实现图2中左右视图完全平行对准的理想形式)。
而立体匹配所做的工作,就是在之前的基础上,求取最后一个变量:
视差d(这个d一般需要达到亚像素精度)。
从而最终完成求一个点三维坐标所需要的准备工作。
在清楚了上述原理之后,我们也就知道了,所有的这几步:
标定、校准和匹配,都是围绕着如何更精确地获得f,d,Tx,cx和cy而设计的。
双目测距的原理就说到这里,为了避免大家看到大段纯叙述性的文字头晕,下面的行文将会以FAQ的形式围绕着实现双摄像头测距过程中碰到的几点疑惑展开。
当然,其中的解答也只是我的个人理解,如有不当,敬请指正。
Q1:
标定时棋盘格的大小如何设定,对最后结果有没有影响?
A:
当然有。
在标定时,需要指定一个棋盘方格的长度,这个长度(一般以毫米为单位,如果需要更精确可以设为0.1毫米量级)与实际长度相同,标定得出的结果才能用于实际距离测量。
一般如果尺寸设定准确的话,通过立体标定得出的Translation的向量的第一个分量Tx的绝对值就是左右摄像头的中心距。
一般可以用这个来验证立体标定的准确度。
比如我设定的棋盘格大小为270(27mm),最终得出的Tx大小就是602.8(60.28mm),相当精确。
Q2:
通过立体标定得出的Tx符号为什么是负的?
这个其实我也不是很清楚。
个人的解释是,立体标定得出的T向量指向是从右摄像头指向左摄像头(也就是Tx为负),而在OpenCV坐标系中,坐标的原点是在左摄像头的。
因此,用作校准的时候,要把这个向量的三个分量符号都要换一下,最后求出的距离才会是正的。
但是这里还有一个问题,就是LearningOpenCV中Q的表达式,第四行第三列元素是-1/Tx,而在具体实践中,求出来的实际值是1/Tx。
这里我和maxwellsdemon讨论下来的结果是,估计书上Q表达式里的这个负号就是为了抵消T向量的反方向所设的,但在实际写OpenCV代码的过程中,那位朋友却没有把这个负号加进去。
(一家之言,求更详细的解释)
Q3:
cvFindStereoCorrespondenceBM的输出结果好像不是以像素点为单位的视差?
在OpenCV2.0中,BM函数得出的结果是以16位符号数的形式的存储的,出于精度需要,所有的视差在输出时都扩大了16倍(2^4)。
其具体代码表示如下:
dptr[y*dstep]=(short)(((ndisp-mind-1+mindisp)*256+(d!
=0?
(p-n)*128/d:
0)+15)>>4);
可以看到,原始视差在左移8位(256)并且加上一个修正值之后又右移了4位,最终的结果就是左移4位
因此,在实际求距离时,cvReprojectTo3D出来的X/W,Y/W,Z/W都要乘以16(也就是W除以16),才能得到正确的三维坐标信息
Q4:
利用双摄像头进行测距的时候世界坐标的原点究竟在哪里?
世界坐标系的原点是左摄像头凸透镜的光心。
说起这个,就不得不提到针孔模型。
如图3所示,针孔模型是凸透镜成像的一种简化模型。
当物距足够远时(远大于两倍焦距),凸透镜成像可以看作是在焦距处的小孔成像。
(ref:
图3.针孔模型
在实际计算过程中,为了计算方便,我们将像平面翻转平移到针孔前,从而得到一种数学上更为简单的等价形式(方便相似三角形的计算),如图4所示。
图4.针孔模型的数学等价形式
因此,对应图2就可以知道,世界坐标系原点就是左摄像头针孔模型的针孔,也就是左摄像头凸透镜的光心
Q5:
f和d的单位是像素,那这个像素到底表示什么,它与毫米之间又是怎样换算的?
这个问题也与针孔模型相关。
在针孔模型中,光线穿过针孔(也就是凸透镜中心)在焦距处上成像,因此,图3的像平面就是摄像头的CCD传感器的表面。
每个CCD传感器都有一定的尺寸,也有一定的分辨率,这个就确定了毫米与像素点之间的转换关系。
举个例子,CCD的尺寸是8mmX6mm,分辨率是640X480,那么毫米与像素点之间的转换关系就是80pixel/mm。
在实际运用中,我们在数学上将这个像平面等效到小孔前(图4),这样就相当于将在透镜中心点之前假设了一块虚拟的CCD传感器。
Q6:
为什么cvStereoRectify求出的Q矩阵cx,cy,f都与原来的不同?
这个在前文有提到过。
在实际测量中,由于摄像头摆放的关系,左右摄像头的f,cx,cy都是不相同的。
而为了使左右视图达到完全平行对准的理想形式从而达到数学上运算的方便,立体校准所做的工作事实上就是在左右像重合区域最大的情况下,让两个摄像头光轴的前向平行,并且让左右摄像头的f,cx,cy相同。
因此,Q矩阵中的值与两个instrinsic矩阵的值不一样就可以理解了。
实验结果:
实验下来,虽然BlockMatching算法本身对精度有所限制,但测距基本能达到能让人接受的精度,结果如下图5所示
图5.OpenCV双摄像头测距结果
上图中,中、左、右三个物体分别被放在离摄像头50cm,75cm和90cm的位置。
可以看出测距的结果相当不错。
当然,上面这幅图是比较好的结果。
由于BM算法的限制,同一点云中相同距离的点一般会有正负2厘米之内的误差。
图6是利用双目摄像头测物体长宽的结果,可以看出结果似乎不太准确。
。
图6.OpenCV双摄像头测边长结果
其中,物体宽为117-88=29mm,但实际宽度为5.2cm,物体高位71-13=58mm,但实际高度为13cm。
这方面的误差还是比较难以理解
此外,还有一个问题至今尚未完全理解,就是双目摄像头的中心距,为什么采用Tx而不是T向量的长度。
因为如果要左右视图重合区域最大化的话两个摄像头的光轴都要与T垂直才是(如图7),这样的话,校正后两个摄像头的中心距应该是T才对。
不知道我这样的理解对不对?
图7.双摄像头立体校准俯视图
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1