精华内容
下载资源
问答
  • 各位好,最近在学习双目立体视觉的相关知识,有没有大神能讲解一下利用双目相机的原始图、生成的视差图和相机内参生成点云的方法和 demo code,还有我在利用BM算法生成的深度图用照片查看器无法打开,提示无效的位图...

    各位好,最近在学习双目立体视觉的相关知识,有没有大神能讲解一下利用双目相机的原始图、生成的视差图和相机内参生成点云的方法和 demo code,还有我在利用BM算法生成的深度图用照片查看器无法打开,提示无效的位图文件或不支持文件的格式,代码如下,不知道什么原因?

    #include

    #include

    #include

    #include

    #include

    #include "time.h"

    using namespace std;

    using namespace cv;

    int main()

    {

    IplImage* img_r, *img_l;//定义两个图像指针 img_l = cvLoadImage("left_4.png",0);

    img_r = cvLoadImage("right_4.png",0);

    CvSize size = cvGetSize(img_l);

    CvMat* disparity_left = cvCreateMat(size.height,size.width,CV_16S);

    CvMat* disparity_right = cvCreateMat(size.height,size.width,CV_16S);

    CvStereoBMState *BMState = cvCreateStereoBMState();

    assert(BMState != 0);

    BMState->preFilterSize=41;

    BMState->preFilterCap=31;

    BMState->SADWindowSize=15;

    BMState->minDisparity=0;//最小视差,默认值为 0, 可以是负值,int 型 BMState->numberOfDisparities=32;

    BMState->textureThreshold=10;

    BMState->uniquenessRatio=15;//该参数不能为负值,一般5-15左右的值比较合适15 BMState->speckleWindowSize =100;

    BMState->speckleRange = 32;

    cvFindStereoCorrespondenceBM(img_l,img_r,disparity_left,BMState);

    cvReleaseStereoBMState(&BMState);

    CvMat* disparity_left_visual = cvCreateMat(size.height,size.width,CV_8UC1);

    cvNormalize(disparity_left,disparity_left_visual,0,256,CV_MINMAX);

    cvSave("disparity.png",disparity_left_visual);

    cvShowImage("disparity",disparity_left_visual);

    cvWaitKey(0);

    cvDestroyWindow("disparity");

    return 0;

    }

    展开全文
  • 0.绪论这篇文章主要为了研究双目立体视觉的最终目标——三维重建,系统的介绍了三维重建的整体步骤。双目立体视觉的整体流程包括:图像获取,摄像机标定,特征提取(稠密匹配中这一步可以省略),立体匹配,三维重建。...

    0.绪论

    这篇文章主要为了研究双目立体视觉的最终目标——三维重建,系统的介绍了三维重建的整体步骤。双目立体视觉的整体流程包括:图像获取,摄像机标定,特征提取(稠密匹配中这一步可以省略),立体匹配,三维重建。我在做双目立体视觉问题时,主要关注的点是立体匹配,本文主要关注最后一个步骤三维重建中的:三角剖分和纹理贴图以及对应的OpenCV+OpenGL代码实现。

    1.视差计算

    1.1基于视差信息的三维重建

    特征提取

    由双目立体视觉进行三位重建的第一步是立体匹配,通过寻找两幅图像中的对应点获取视差。OpenCV 中的features2d库中包含了很多常用的算法,其中特征点定位的算法有FAST, SIFT, SURF ,MSER, HARRIS等,特征点描述算法有SURF, SIFT等,还有若干种特征点匹配算法。这三个步骤的算法可以任选其一,自由组合,非常方便。经过实验,选择了一种速度、特征点数量和精度都比较好的组合方案:FAST角点检测算法+SURF特征描述子+FLANN(Fast Library for Approximate Nearest Neighbors) 匹配算法。

    在匹配过程中需要有一些措施来过滤误匹配。一种比较常用的方法是比较第一匹配结果和第二匹配结果的得分差距是否足够大,这种方法可以过滤掉一些由于相似造成的误匹配。还有一种方法是利用已经找到的匹配点,使用RANSAC算法求得两幅视图之间的单应矩阵,然后将左视图中的坐标P用单应矩阵映射到右视图的Q点,观察与匹配结果Q’的欧氏距离是否足够小。当然由于图像是具有深度的,Q与Q’必定会有差距,因此距离阈值可以设置的稍微宽松一些。我使用了这两种过滤方法。

    另外,由于图像有些部分的纹理较多,有些地方则没有什么纹理,造成特征点疏密分布不均匀,影响最终重建的效果,因此我还采取了一个措施:限制特征点不能取的太密。如果新加入的特征点与已有的某一特征点距离太小,就舍弃之。最终匹配结果如下图所示,精度和均匀程度都较好。

    代码:

    // choose the corresponding points in the stereo images for 3d reconstruction

    void GetPair( Mat &imgL, Mat &imgR, vector &ptsL, vector &ptsR )

    {

    Mat descriptorsL, descriptorsR;

    double tt = (double)getTickCount();

    Ptr detector = FeatureDetector::create( DETECTOR_TYPE ); // factory mode

    vector keypointsL, keypointsR;

    detector->detect( imgL, keypointsL );

    detector->detect( imgR, keypointsR );

    Ptr de = DescriptorExtractor::create( DESCRIPTOR_TYPE );

    //SurfDescriptorExtractor de(4,2,true);

    de->compute( imgL, keypointsL, descriptorsL );

    de->compute( imgR, keypointsR, descriptorsR );

    tt = ((double)getTickCount() - tt)/getTickFrequency(); // 620*555 pic, about 2s for SURF, 120s for SIFT

    Ptr matcher = DescriptorMatcher::create( MATCHER_TYPE );

    vector> matches;

    matcher->knnMatch( descriptorsL, descriptorsR, matches, 2 ); // L:query, R:train

    vector passedMatches; // save for drawing

    DMatch m1, m2;

    vector ptsRtemp, ptsLtemp;

    for( size_t i = 0; i < matches.size(); i++ )

    {

    m1 = matches[i][0];

    m2 = matches[i][1];

    if (m1.distance < MAXM_FILTER_TH * m2.distance)

    {

    ptsRtemp.push_back(keypointsR[m1.trainIdx].pt);

    ptsLtemp.push_back(keypointsL[i].pt);

    passedMatches.push_back(m1);

    }

    }

    Mat HLR;

    HLR = findHomography( Mat(ptsLtemp), Mat(ptsRtemp), CV_RANSAC, 3 );

    cout<

    Mat ptsLt;

    perspectiveTransform(Mat(ptsLtemp), ptsLt, HLR);

    vector matchesMask( passedMatches.size(), 0 );

    int cnt = 0;

    for( size_t i1 = 0; i1 < ptsLtemp.size(); i1++ )

    {

    Point2f prjPtR = ptsLt.at((int)i1,0); // prjx = ptsLt.at((int)i1,0), prjy = ptsLt.at((int)i1,1);

    // inlier

    if( abs(ptsRtemp[i1].x - prjPtR.x) < HOMO_FILTER_TH &&

    abs(ptsRtemp[i1].y - prjPtR.y) < 2) // restriction on y is more strict

    {

    vector::iterator iter = ptsL.begin();

    for (;iter!=ptsL.end();iter++)

    {

    Point2f diff = *iter - ptsLtemp[i1];

    float dist = abs(diff.x)+abs(diff.y);

    if (dist < NEAR_FILTER_TH) break;

    }

    if (iter != ptsL.end()) continue;

    ptsL.push_back(ptsLtemp[i1]);

    ptsR.push_back(ptsRtemp[i1]);

    cnt++;

    if (cnt%1 == 0) matchesMask[i1] = 1; // don't want to draw to many matches

    }

    }

    Mat outImg;

    drawMatches(imgL, keypointsL, imgR, keypointsR, passedMatches, outImg,

    Scalar::all(-1), Scalar::all(-1), matchesMask, DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    char title[50];

    sprintf_s(title, 50, "%.3f s, %d matches, %d passed", tt, matches.size(), cnt);

    imshow(title, outImg);

    waitKey();

    }

    p.s. 源代码中的基于特征点的视差计算有点问题,还在调试中,希望有经验的大牛共同解决一下。

    1.2基于块匹配的视差计算

    上面提取特征点的过程中实际上忽略了一个辅助信息:对应点应当是取在对应极线上的一个区间内的。利用这个信息可以大幅简化对应点的匹配,事实上只要用L1距离对一个像素周围的block计算匹配距离就可以了,也就是OpenCV中实现的块匹配算法的基本思路。比起特征点匹配,这是一种“稠密”的匹配算法,精度也可以接受。下图中浅色表示视差较大,对应深度较浅。左侧有一块区域是左右视图不相交的部分,因此无法计算视差。

    可以发现视差计算结果中有很多噪声。事实上在纹理平滑的区域,还有左右视图中不同遮挡的区域,是很难计算视差的。因此我利用最近邻插值和数学形态学平滑的方法对视差图进行了修复(见cvFuncs2.cpp中的FixDisparity函数):

    // roughly smooth the glitches on the disparity map

    void FixDisparity( Mat_ & disp, int numberOfDisparities )

    {

    Mat_ disp1;

    float lastPixel = 10;

    float minDisparity = 23;// algorithm parameters that can be modified

    for (int i = 0; i < disp.rows; i++)

    {

    for (int j = numberOfDisparities; j < disp.cols; j++)

    {

    if (disp(i,j) <= minDisparity) disp(i,j) = lastPixel;

    else lastPixel = disp(i,j);

    }

    }

    int an = 4; // algorithm parameters that can be modified

    copyMakeBorder(disp, disp1, an,an,an,an, BORDER_REPLICATE);

    Mat element = getStructuringElement(MORPH_ELLIPSE, Size(an*2+1, an*2+1));

    morphologyEx(disp1, disp1, CV_MOP_OPEN, element);

    morphologyEx(disp1, disp1, CV_MOP_CLOSE, element);

    disp = disp1(Range(an, disp.rows-an), Range(an, disp.cols-an)).clone();

    }

    对应点的选取

    上面提到,为了获得较好的重构效果,特征点最好取在深度变化较大的区域。基于这种猜想,我首先对上面的视差图求梯度,然后找到梯度最大的点,观察梯度的方向,如果是偏x方向,就在该点左右若干像素各取一个点;否则就在上下若干像素各取一个点。然后根据这两个点的视差值就可以计算出另外一个视图中的对应点的坐标。特征点还不能分布过密,因此我取完一对特征点后,将其周围一圈像素的梯度置零,然后在寻找下一个梯度最大值,这样一直下去,直到取够特征点数。

    特征点也不能全取在深度变化剧烈的区域,在平坦的区域也可以取一些。最终我取的特征点如下图:

    其中紫色的点是在较平坦的区域取到的,其他颜色是在边界区域取到的。这些算法实现在ChooseKeyPointsBM函数中。

    2.计算世界坐标

    一般双目立体视觉中使用的实验图像都是经过外极线矫正的,计算3D坐标也比较方便,其实利用外极线约束(以及其他的约束条件)可以极大的降低立体匹配的计算量。见下图:

    如果(x1,y1),(x2,y2)用各自图像上的像素坐标表示,L和(X,Y,Z)用毫米表示,f用像素表示的话,用相似三角形的知识就可以推出:

    其中W和H是图像的宽高(像素数),y是y1和y2的均值,Z加负号是为了保持右手坐标系,而Y加负号是由于图像成像过程中上下发生了倒转。三维世界原点取为左摄像机的焦点。计算的代码见cvFunc.cpp中的StereoTo3D函数。

    // calculate 3d coordinates.

    // for rectified stereos: pointLeft.y == pointRight.y

    // the origin for both image is the top-left corner of the left image.

    // the x-axis points to the right and the y-axis points downward on the image.

    // the origin for the 3d real world is the optical center of the left camera

    // object -> optical center -> image, the z value decreases.

    void StereoTo3D( vector ptsL, vector ptsR, vector &pts3D,

    float focalLenInPixel, float baselineInMM, Mat img,

    Point3f &center3D, Vec3f &size3D) // output variable, the center coordinate and the size of the object described by pts3D

    {

    vector::iterator iterL = ptsL.begin(),

    iterR = ptsR.begin();

    float xl, xr, ylr;

    float imgH = float(img.rows), imgW = float(img.cols);

    Point3f pt3D;

    float minX = 1e9, maxX = -1e9;

    float minY = 1e9, maxY = -1e9;

    float minZ = 1e9, maxZ = -1e9;

    Mat imgShow = img.clone();

    char str[100];

    int ptCnt = ptsL.size(), showPtNum = 30, cnt = 0;

    int showIntv = max(ptCnt/showPtNum, 1);

    for ( ; iterL != ptsL.end(); iterL++, iterR++)

    {

    xl = iterL->x;

    xr = iterR->x; // need not add baseline

    ylr = (iterL->y + iterR->y)/2;

    //if (yl-yr>5 || yr-yl>5) // may be wrong correspondence, discard. But vector can't be changed during iteration

    //{}

    pt3D.z = -focalLenInPixel * baselineInMM / (xl-xr); // xl should be larger than xr, if xl is shot by the left camera

    pt3D.y = -(-ylr + imgH/2) * pt3D.z / focalLenInPixel;

    pt3D.x = (imgW/2 - xl) * pt3D.z / focalLenInPixel;

    minX = min(minX, pt3D.x); maxX = max(maxX, pt3D.x);

    minY = min(minY, pt3D.y); maxY = max(maxY, pt3D.y);

    minZ = min(minZ, pt3D.z); maxZ = max(maxZ, pt3D.z);

    pts3D.push_back(pt3D);

    if ((cnt++)%showIntv == 0)

    {

    Scalar color = CV_RGB(rand()&64,rand()&64,rand()&64);

    sprintf_s(str, 100, "%.0f,%.0f,%.0f", pt3D.x, pt3D.y, pt3D.z);

    putText(imgShow, str, Point(xl-13,ylr-3), FONT_HERSHEY_SIMPLEX, .3, color);

    circle(imgShow, *iterL, 2, color, 3);

    }

    }

    imshow("back project", imgShow);

    waitKey();

    center3D.x = (minX+maxX)/2;

    center3D.y = (minY+maxY)/2;

    center3D.z = (minZ+maxZ)/2;

    size3D[0] = maxX-minX;

    size3D[1] = maxY-minY;

    size3D[2] = maxZ-minZ;

    }

    3.三角剖分

    3.1 三角剖分简介

    三角剖分是为了之后的纹理贴图,我用了OpenCV中的Delaunay三角剖分函数,这种剖分算法的可以使所形成的三角形的最小角最大。剖分的示例如下:

    OpenCV使用Delaunay算法将平面分割成小的三角形区域(该三角形确保包括所有的分割点)开始不断迭代完成。在这种情况下,对偶划分就是输入的二维点集的Voronoi图表。这种划分可以用于对一个平面进行三维分段变换、形态变换、平面点的快速 定位以及建立特定的图结构(如NNG,RNG)。

    同时由表可以看出,三角网生成法的时间效率最低,分治算法的时间效率最高,逐点插入法效率居中。

    3.2 Bowyer-Watson算法

    目前采用逐点插入方式生成的Delaunay三角网的算法主要基于Bowyer-Watson算法,Bowyer-Watson算法的主要步骤如下:

    1)建立初始三角网格:针对给定的点集V,找到一个包含该点集的矩形R,我们称R为辅助窗口。连接R的任意一条对角线,形成两个三角形,作为初始Delaunay三角网格。

    2)逐点插入:假设目前已经有一个Delaunay三角网格T,现在在它里面再插入一个点P,需要找到该点P所在的三角形。从P所在的三角形开始,搜索该三角形的邻近三角形,并进行空外接圆检测。找到外接圆包含点P的所有的三角形并删除这些三角形,形成一个包含P的多边形空腔,我们称之为Delaunay空腔。然后连接P与Delaunay腔的每一个顶点,形成新的Delaunay三角网格。

    3)删除辅助窗口R:重复步骤2),当点集V中所有点都已经插入到三角形网格中后,将顶点包含辅助窗口R的三角形全部删除。

    在这些步骤中,快速定位点所在的三角形、确定点的影响并构建Delaunay腔的过程是每插入一个点都会进行的。随着点数的增加,三角形数目增加很快,因此缩短这两个过程的计算时间,是提高算法效率的关键。

    算法执行图示如下:

    3.3 三角剖分代码分析

    三角剖分的代码见cvFuncs.cpp中的TriSubDiv函数,我将特征点存储到一个vector变量中,剖分结果存储到一个vector变量中,Vec3i中存储的是3个表示顶点编号的整数。

    我们需要存储Delaunay的内存空间和一个外接矩形(该矩形盒子用来确定虚拟三角形)

    // STORAGE AND STRUCTURE FOR DELAUNAY SUBDIVISION //存储和结构 for三角剖分

    //

    CvRect rect = { 0, 0, 600, 600 }; //Our outer bounding box //我们的外接边界盒子

    CvMemStorage* storage; //Storage for the Delaunay subdivsion //用来存储三角剖分

    storage = cvCreateMemStorage(0); //Initialize the storage //初始化存储器

    CvSubdiv2D* subdiv; //The subdivision itself // 细分

    subdiv = init_delaunay( storage, rect); //See this function below //函数返回CvSubdiv类型指针

    init_delaunay函数如下,它是一个OpenCV函数,是一个包含一些OpenCV函数的函数包。

    //INITIALIZATION CONVENIENCE FUNCTION FOR DELAUNAY SUBDIVISION //为三角剖分初始化便利函数

    //

    CvSubdiv2D* init_delaunay(CvMemStorage* storage,CvRect rect) {

    CvSubdiv2D* subdiv;

    subdiv = cvCreateSubdiv2D(CV_SEQ_KIND_SUBDIV2D,sizeof(*subdiv),sizeof(CvSubdiv2DPoint),sizeof(CvQuadEdge2D),storage);//为数据申请空间

    cvInitSubdivDelaunay2D( subdiv, rect ); //rect sets the bounds

    return subdiv;//返回申请空间的指针

    }

    我们知道三角剖分是对散点集进行处理的,我们知道了散点集就可以获得点集的三角剖分。如何传入(插入)散点集呢?

    这些点必须是32位浮点型,并通过下面的方式插入点:

    CvPoint2D32f fp; //This is our point holder//这是我们点的持有者(容器)

    for( i = 0; i < as_many_points_as_you_want; i++ ) {

    // However you want to set points //如果我们的点集不是32位的,在这里我们将其转为CvPoint2D32f,如下两种方法。

    //

    fp = your_32f_point_list[i];

    cvSubdivDelaunay2DInsert( subdiv, fp );

    }

    转换为CvPoint2D32f的两种方法:

    1)通过宏cvPoint2D32f(double x,double y)

    2)通过cxtype.h下的cvPointTo32f(CvPoint point)函数将整形点方便的转换为32位浮点型。

    当可以通过输入点(散点集)得到Delaunay三角剖分后,接下来,我们用一下两个函数设置和清除相关的Voronoi划分:

    cvCalcSubdivVoronoi2D( subdiv ); // Fill out Voronoi data in subdiv //在subdiv中填充Vornoi的数据

    cvClearSubdivVoronoi2D( subdiv ); // Clear the Voronoi from subdiv//从subdiv中清除Voronoi的数据

    CvSubdiv2D结构如下:

    #define CV_SUBDIV2D_FIELDS() \

    CV_GRAPH_FIELDS() \

    int quad_edges; \

    int is_geometry_valid; \

    CvSubdiv2DEdge recent_edge; \

    CvPoint2D32f topleft; \

    CvPoint2D32f bottomright;

    typedef struct CvSubdiv2D

    {

    CV_SUBDIV2D_FIELDS()

    }

    CvSubdiv2D;

    #define CV_GRAPH_FIELDS() \

    CV_SET_FIELDS() /* set of vertices */ \

    CvSet *edges; /* set of edges */

    #define CV_SET_FIELDS() \

    CV_SEQUENCE_FIELDS() /*inherits from [#CvSeq CvSeq] */ \

    struct CvSetElem* free_elems; /*list of free nodes */

    整体代码如下:

    void TriSubDiv( vector &pts, Mat &img, vector &tri )

    {

    CvSubdiv2D* subdiv;//The subdivision itself // 细分

    CvMemStorage* storage = cvCreateMemStorage(0); ;//Storage for the Delaunay subdivsion //用来存储三角剖分

    Rect rc = Rect(0,0, img.cols, img.rows); //Our outer bounding box //我们的外接边界盒子

    subdiv = cvCreateSubdiv2D( CV_SEQ_KIND_SUBDIV2D, sizeof(*subdiv),

    sizeof(CvSubdiv2DPoint),

    sizeof(CvQuadEdge2D),

    storage );//为数据申请空间

    cvInitSubdivDelaunay2D( subdiv, rc );//rect sets the bounds

    //如果我们的点集不是32位的,在这里我们将其转为CvPoint2D32f,如下两种方法。

    for (size_t i = 0; i < pts.size(); i++)

    {

    CvSubdiv2DPoint *pt = cvSubdivDelaunay2DInsert( subdiv, pts[i] );

    pt->id = i;

    }

    CvSeqReader reader;

    int total = subdiv->edges->total;

    int elem_size = subdiv->edges->elem_size;

    cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );

    Point buf[3];

    const Point *pBuf = buf;

    Vec3i verticesIdx;

    Mat imgShow = img.clone();

    srand( (unsigned)time( NULL ) );

    for( int i = 0; i < total; i++ )

    {

    CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);

    if( CV_IS_SET_ELEM( edge ))

    {

    CvSubdiv2DEdge t = (CvSubdiv2DEdge)edge;

    int iPointNum = 3;

    Scalar color = CV_RGB(rand()&255,rand()&255,rand()&255);

    //bool isNeg = false;

    int j;

    for(j = 0; j < iPointNum; j++ )

    {

    CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );

    if( !pt ) break;

    buf[j] = pt->pt;

    //if (pt->id == -1) isNeg = true;

    verticesIdx[j] = pt->id;

    t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );

    }

    if (j != iPointNum) continue;

    if (isGoodTri(verticesIdx, tri))

    {

    //tri.push_back(verticesIdx);

    polylines( imgShow, &pBuf, &iPointNum,

    1, true, color,

    1, CV_AA, 0);

    //printf("(%d, %d)-(%d, %d)-(%d, %d)\n", buf[0].x, buf[0].y, buf[1].x, buf[1].y, buf[2].x, buf[2].y);

    //printf("%d\t%d\t%d\n", verticesIdx[0], verticesIdx[1], verticesIdx[2]);

    //imshow("Delaunay", imgShow);

    //waitKey();

    }

    t = (CvSubdiv2DEdge)edge+2;

    for(j = 0; j < iPointNum; j++ )

    {

    CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );

    if( !pt ) break;

    buf[j] = pt->pt;

    verticesIdx[j] = pt->id;

    t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );

    }

    if (j != iPointNum) continue;

    if (isGoodTri(verticesIdx, tri))

    {

    //tri.push_back(verticesIdx);

    polylines( imgShow, &pBuf, &iPointNum,

    1, true, color,

    1, CV_AA, 0);

    //printf("(%d, %d)-(%d, %d)-(%d, %d)\n", buf[0].x, buf[0].y, buf[1].x, buf[1].y, buf[2].x, buf[2].y);

    //printf("%d\t%d\t%d\n", verticesIdx[0], verticesIdx[1], verticesIdx[2]);

    //imshow("Delaunay", imgShow);

    //waitKey();

    }

    }

    CV_NEXT_SEQ_ELEM( elem_size, reader );

    }

    //RemoveDuplicate(tri);

    char title[100];

    sprintf_s(title, 100, "Delaunay: %d Triangles", tri.size());

    imshow(title, imgShow);

    waitKey();

    }

    平面划分是将一个平面分割为一组不重叠的、能够覆盖整个平面的区域。结构CvSubdiv2D描述了建立在二维点集上的划分结构,其中点集互相连接且构成平面图形,该图形通过结合一些无线连接外部划分点(称为凸形点)的边缘,将一个平面用按照其边缘划分成很多小区域。

    对于每一个划分操作,都有一个对偶划分与之对应。对偶的意思是小区域与点(划分的顶点)变换角色,即在对偶划分中,小区域被当做一个顶点(以下称为虚拟点)而原始的划分顶点被当做小区域。如下图所示,原始的划分用实线表示,而对偶划分用虚线表示。

    4.三维重构

    为了保证三维重建的效果,一般地要对深度图像进行后续处理。要从深度图像中恢复高质量的视差图,对深度图像的要求有:

    ①深度图像中,物体的边界必需与图像中物体的边界对齐;

    ②在场景图中,深度图像要尽可能均勻和平滑,即对图像进行平滑处理。

    三维重构的思路很简单,用OpenGL中纹理贴图功能,将平面图像中的三角形逐个贴到计算出的三维坐标上去就可以了。为了便于观察3D效果,我还设计了交互功能:用方向键可以上下左右旋转重构的模型,用鼠标滚轮可以放大或缩小。用gluLookAt函数可以实现视点旋转的功能。三维重构的代码实现在glFuncs.cpp中。

    纹理贴图:

    GLuint Create3DTexture( Mat &img, vector &tri,

    vector pts2DTex, vector &pts3D,

    Point3f center3D, Vec3f size3D )

    {

    GLuint tex = glGenLists(1);

    int error = glGetError();

    if (error != GL_NO_ERROR)

    cout << "An OpenGL error has occured: " << gluErrorString(error) << endl;

    if (tex == 0) return 0;

    Mat texImg;

    cvtColor(img, img, CV_BGR2RGB);

    resize(img, texImg, Size(512,512)); // seems no need to do this

    glNewList(tex, GL_COMPILE);

    vector::iterator iterTri = tri.begin();

    //vector::iterator iterPts3D = pts3D.begin();

    Point2f pt2D[3];

    Point3f pt3D[3];

    glDisable(GL_BLEND);

    glEnable(GL_TEXTURE_2D);

    for ( ; iterTri != tri.end(); iterTri++)

    {

    Vec3i &vertices = *iterTri;

    int ptIdx;

    for (int i = 0; i < 3; i++)

    {

    ptIdx = vertices[i];

    if (ptIdx == -1) break;

    //else cout<

    pt2D[i].x = pts2DTex[ptIdx].x / img.cols;

    pt2D[i].y = pts2DTex[ptIdx].y / img.rows;

    pt3D[i] = (pts3D[ptIdx] - center3D) * (1.f / max(size3D[0],size3D[1]));

    //pt3D[i].z -= offset;

    }

    if (ptIdx != -1)

    {

    MapTexTri(texImg, pt2D, pt3D);

    //cout<

    }

    }

    glDisable(GL_TEXTURE_2D);

    glEndList();

    return tex;

    }

    效果展示及不足

    Cloth图像是重构效果比较好的一组:

    可以比较明显的看出3D效果,也比较符合直觉。然而其他图像效果就差强人意了:

    仔细分析造成这种效果的原因,一方面,特征点的匹配可能有些误差,造成3D坐标的计算不太精确,但大部分坐标还是准确的。另一方面,左右视图可能会有不同的遮挡、偏移等情况,因此匹配得到的特征点可能实际上并不是3维世界中的同一点,这种误差是无法消除的。但造成效果变差的最重要的原因,还是图像中深度变化较大,而特征点选取的比较稀疏,因此正面看还比较正常,一旦旋转纹理就显得扭曲变形了。为了解决这个问题,应当试图把特征点取到深度变化较剧烈的地方,一般是图像中的边界处。然而特征点检测一般都检测出的是角点和纹理密集的区域,因此可以考虑更换对应点匹配的方法。

    如果要进一步改进效果,可以先对视差图像进行分割,将图像分成视差比较连续的几块区域分别贴图,视差变化剧烈的区域就不必把扭曲的纹理贴上去了。我尝试了以下分割的效果,如下图所示,应该可以达到更好的效果,不过由于时间所限,就没有进一步实现下去了。

    关于上面实现的两种求取视差的算法,在main函数的前面设置了一个变量g_algo,可以用来切换不同的算法。

    参考文献:

    代码下载

    展开全文
  • 基于双目视觉三维重建 基于双目视觉三维重建
  • 双目视觉三维重建

    2015-04-08 16:44:42
    这是双目视觉三维重建代码,希望对大家有所帮助
  • 原文:http://blog.csdn.NET/chenyusiyuan/article/details/5970799在获取到视差数据后,利用OpenCV的 reProjectImageTo3D 函数结合 Bouquet 校正方法得到的 Q 矩阵就可以得到环境的三维坐标数据,然后利用OpenGL 来...

    原文:

    http://blog.csdn.NET/chenyusiyuan/article/details/5970799

    在获取到视差数据后,利用 OpenCV 的 reProjectImageTo3D 函数结合 Bouquet 校正方法得到的 Q 矩阵就可以得到环境的三维坐标数据,然后利用

    OpenGL 来实现三维重构。 OpenCV 与 OpenGL 的编程范例,我在 学习笔记( 15 )中有详细的讨论,这里就不重复了,下面补充一些细节问题:

    .

    .

    1.             reProjectImageTo3D 是怎样计算出三维坐标数据的?

    图 22

    .

    相信看过 OpenCV 第 12 章的朋友对上图中的 Q 矩阵不会陌生,根据以上变换公式,按理说 OpenCV 应该也是通过矩阵运算的方式来计算出三维坐标数据的,但实际上仔细查看源代码,会发现 cvReprojectImageTo3D 用了比较奇怪的方法来实现,主要代码如下:

    02737     for( y = 0; y 

    02738     {

    02739         const float* sptr = (const float*)(src->data.ptr + src->step*y);   // 视差矩阵指针

    02740         float* dptr0 = (float*)(dst->data.ptr + dst->step*y), *dptr = dptr0;   // 三维坐标矩阵指针

    // 每一行运算开始时,用 当前行号y 乘以Q阵第2列、再加上Q阵第4列,作为初始值

    // 记 qq=[qx, qy, qz, qw]’

    02741         double qx = q[0][1]*y + q[0][3], qy = q[1][1]*y + q[1][3];

    02742         double qz = q[2][1]*y + q[2][3], qw = q[3][1]*y + q[3][3];

    // 每算完一个像素的三维坐标,向量qq 累加一次q阵第1列

    // 即:qq = qq + q(:,1)

    02769         for( x = 0; x 

    02770         {

    02771             double d = sptr[x];

    // 计算当前像素三维坐标

    // 将向量qq 加上 Q阵第3列与当前像素视差d的乘积,用所得结果的第4元素除前三位元素即可

    // [X,Y,Z,W]’ = qq + q(:,3) * d;   iW = 1/W; X=X*iW; Y=Y*iW; Z=Z*iW;

    02772             double iW = 1./(qw + q[3][2]*d);

    02773             double X = (qx + q[0][2]*d)*iW;

    02774             double Y = (qy + q[1][2]*d)*iW;

    02775             double Z = (qz + q[2][2]*d)*iW;

    02776             if( fabs(d-minDisparity) <= FLT_EPSILON )

    02777                 Z = bigZ;   // 02713     const double bigZ = 10000.;

    02778

    02779             dptr[x*3] = (float)X;

    02780             dptr[x*3+1] = (float)Y;

    02781             dptr[x*3+2] = (float)Z;

    02782         }

    OpenCV 的这种计算方式比较令人费解,我的理解是可能这种方式的计算速度比较快。理论上,直接通过矩阵 Q 与向量 [x,y,d,1]’ 的乘积就可以得到相同的结果,下面用 Matlab 来验证一下两种方式是异曲同工的,用 Matlab 按照 OpenCV 计算方式得到的结果称为“ OpenCV method ”,直接按公式计算得到的结果称为“ Equation method ”,用 OpenCV 本身算出的三维坐标作为参考,程序代码如下 :

    close all;clear all;clc

    im = imread('C:/Stereo IO Data/lfFrame_01.jpg');

    data = importdata('C:/Stereo IO Data/disparity_01.txt');

    r = data(1);    % 行数

    c = data(2);    % 列数

    disp = data(3:end); % 视差

    vmin = min(disp);

    vmax = max(disp);

    disp = reshape(disp, [c,r])'; % 将列向量形式的 disp 重构为 矩阵形式

    %  OpenCV 是行扫描存储图像,Matlab 是列扫描存储图像

    %  故对 disp 的重新排列是首先变成 c 行 r 列的矩阵,然后再转置回 r 行 c 列

    img = uint8( 255 * ( disp - vmin ) / ( vmax - vmin ) );

    q = [1. 0. 0. -1.5690376663208008e+002;...

    0. 1. 0. -1.4282237243652344e+002;...

    0. 0. 0. 5.2004731331639300e+002;...

    0. 0. 1.0945105843175637e-002 0.]; % q(4,3) 原为负值,现修正为正值

    big_z = 1e5;

    pos1 = zeros(r,c,3);

    pos2 = zeros(r,c,3);

    for i = 1:r

    qq = q*[0 i 0 1]';

    for j = 1:c

    if disp(i,j)>0

    % OpenCV method

    vec = qq + q(:,3)*disp(i,j);

    vec = vec/vec(4);

    pos1(i,j,:) = vec(1:3);

    % Textbook method

    tmp = q*[j,i,disp(i,j),1]'; % j 是列数,i 是行数,分别对应公式中的 x 和 y

    pos2(i,j,:) = tmp(1:3)/tmp(4);

    else

    pos1(i,j,3) = big_z;

    pos2(i,j,3) = big_z;

    end

    qq = qq + q(:,1);

    end

    end

    subplot(221);

    imshow(im); title('Left Frame');

    subplot(222);

    imshow(img); title('Disparity map');

    % Matlab按OpenCV计算方式得到的三维坐标

    x = pos1(:,:,1);

    y = -pos1(:,:,2);  % 图像坐标系Y轴是向下为正方向,因此需添加负号来修正

    z = pos1(:,:,3);

    ind = find(z>10000);  % 以毫米为量纲

    x(ind)=NaN; y(ind)=NaN; z(ind)=NaN;

    subplot(234);

    mesh(x,z,y,double(im),'FaceColor','texturemap');  % Matlab 的 mesh、surf 函数支持纹理映射

    colormap(gray);

    axis equal;

    axis([-1000 1000 0 9000 -500 2000]);

    xlabel('Horizonal');ylabel('Depth');zlabel('Vertical'); title('OpenCV method');

    view([0 0]);  % 正视图

    % view([0 90]);   % 俯视图

    % view([90 0]);   % 侧视图

    % Matlab 按公式直接计算得到的三维坐标

    x = pos2(:,:,1);

    y = -pos2(:,:,2);

    z = pos2(:,:,3);

    ind = find(z>10000);  % 以毫米为量纲

    x(ind)=NaN; y(ind)=NaN; z(ind)=NaN;

    subplot(235);

    mesh(x,z,y,double(im),'FaceColor','texturemap');

    colormap(gray);

    axis equal;

    axis([-1000 1000 0 9000 -500 2000]);

    xlabel('Horizonal');ylabel('Depth');zlabel('Vertical'); title('Equation method');

    view([0 0]);

    % 读入OpenCV计算保存到本地的三维坐标作为参考

    data=importdata('C:/Stereo IO Data/xyz.txt');

    x=data(:,1); y=data(:,2); z=data(:,3);

    ind=find(z>1000);  % 以厘米为量纲

    x(ind)=NaN; y(ind)=NaN; z(ind)=NaN;

    x=reshape(x,[352 288])'; % 数据写入时是逐行进行的,而Matlab是逐列读取

    y=-reshape(y,[352 288])';

    z=reshape(z,[352 288])';

    subplot(236)

    mesh(x,z, y,double(im),'FaceColor','texturemap');

    colormap(gray);

    axis equal;axis([-100 100 0 900 -50 200]);

    xlabel('Horizonal');ylabel('Depth');zlabel('Vertical'); title('OpenCV result');

    view([0 0]);

    图 23

    .

    .

    2.             为什么利用修正了的 Q 矩阵所计算得到的三维数据中, Y 坐标数据是正负颠倒的?

    图 24

    .

    这个问题我觉得可以从图像坐标系与摄像机坐标系的关系这一角度来解释。如上图所示,一般图像坐标系和摄像机坐标系都是以从左至右为 X 轴正方向,从上至下为 Y 轴正方向,摄像机坐标系的

    Z 轴正方向则是从光心到成像平面的垂线方向。因此,我们得到的三维坐标数据中 Y 轴数据的正负与实际是相反的,在应用时要添加负号来修正。

    .

    .

    3.             如何画出三维重建图像和景深图像?

    .

    利用 cvReprojectImageTo3D 计算出的三维坐标数据矩阵一般是三通道浮点型的,需要注意的是这个矩阵存储的是三维坐标数据,而不是 RGB 颜色值,所以是不能调用 cvShowImage() 或者 OpenCV2.1 版的 imshow() 等函数来显示这个矩阵,否则就会看到这种图像:

    .

    图 25

    .

    这里出现的明显的四个色块,其实应该是由三维坐标数据中的 X 轴和 Y 轴数据造成,不同象限的数据形成相应的色块。

    要画出正确的三维重建图像,可以结合 OpenGL (可参考我的 学习笔记( 15 ))或者 Matlab (例如保存三维数据到本地然后用 Matlab 的 mesh 函数画出,例程见本文问题

    1 ;也可以考虑在 OpenCV 中调用 Matlab 混合编程)来实现。

    深度图像的显示相对比较简单,只要从三维坐标数据中分离出来(可用 cvSplit() 函数),经过适当的格式转换(例如转换为 CV_8U 格式),就可用 cvShowImage() 或者 OpenCV2.1 版的 imshow() 等函数来显示了,伪彩色的深度图 也可以参考我的 学习笔记(

    18 )问题 6 给出的例程稍作修改即可实现。

    .

    .

    4.             怎样把 OpenGL 窗口的图像复制到 OpenCV 中用 IplImage 格式显示和保存?

    .

    在 学习笔记( 15 )中详细给出了将 OpenCV 生成的 IplImage 图像和三维坐标数据复制到 OpenGL 中显示的例程,而在应用中,我们有时候也需要把 OpenGL 实时显示的三维图像复制到

    OpenCV 中,用 IplImage 格式保存,以便和其它图像组合起来显示或保存为视频文件。这里给出相应的例程以供参考:

    首先在创建 OpenGL 窗口时,显示模式要如下设置:

    //***OpenGL Window

    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);

    glutInitWindowPosition(10,420);

    glutInitWindowSize(glWinWidth, glWinHeight);

    glutCreateWindow("3D disparity image");

    在循环中的调用:

    //

    // OpenGL显示

    img3dIpl = img3d;

    load3dDataToGL(&img3dIpl);      // 载入需要显示的图像(视差数据)

    loadTextureToGL(&img1roi);      // 显示纹理

    glutReshapeFunc (reshape);          // 窗口变化时重绘图像

    glutDisplayFunc(renderScene);       // 显示三维图像

    glutPostRedisplay();                // 刷新画面(不用此语句则不能动态更新图像)

    loadPixel2IplImage(imgGL);          // 将 OpenGL 生成的像素值存储到 IplImage 中

    loadGLPixelToIplImage 函数定义:

    //

    // 将OpenGL窗口像素存储到 IplImage 中

    void  loadGLPixelToIplImage(IplImage* img)

    {

    const int n = 3*glWinWidth*glWinHeight;

    float *pixels = (float *)malloc(n * sizeof(GL_FLOAT));

    IplImage *tmp = cvCreateImage(cvSize(glWinWidth, glWinHeight), 8, 3);

    tmp->origin = CV_ORIGIN_BL;

    /* 后台缓存的图像数据才是我们需要复制的,若复制前台缓存会把可能的叠加在OpenGL窗口上的对象(其它窗口或者鼠标指针)也复制进去*/

    glReadBuffer(GL_BACK);

    glReadPixels(0, 0, glWinWidth, glWinHeight, GL_RGB, GL_FLOAT, pixels);

    int k = 0;

    for(int i = 0 ; i 

    {

    for(int j = 0 ; j 

    {

    CvPoint pt = {j, glWinHeight - i - 1};

    uchar* temp_ptr = &((uchar*)(tmp->imageData + tmp->widthStep*pt.y))[pt.x*3];

    //OpenGL采用的是BGR格式,所以,读出来以后,还要换一下RB,才能得到RGB

    temp_ptr[0] = pixels[k+2] * 255; //blue

    temp_ptr[1] = pixels[k+1] * 255; //green

    temp_ptr[2] = pixels[k] * 255;   //red

    }

    }

    cvResize(tmp, img);

    // 释放内存

    free(pixels);

    cvReleaseImage(&tmp);

    }

    显示效果如下:

    图26

    展开全文
  • 计算三维重建的方法称为SfM(Structure from Motion).\ 假设计算机已经标定,计算重建的部分可以分为下面四个步骤:\ (1)、检测特征点,然后在两幅图间进行特征点匹配。\ (2)、有匹配算出基础矩阵。\ (3)、由...
  • 该方法首先根据双目标定理论获取左右摄像机的内外参数和畸变系数,再进行双目图像校正与匹配,然后根据左右图像中目标点的不同坐标得出视差,最后利用三维重建方程组得出障碍物的距离。实验结果表明:该方法对汽车...

    目前无人驾驶汽车以及汽车安全辅助驾驶技术是近几年的研究热点。针对汽车在城市拥堵道路上低速运行时完全自动驾驶安全性问题,重点研究了基于双目立体视觉行车中障碍距离检测方法。

    该方法首先根据双目标定理论获取左右摄像机的内外参数和畸变系数,再进行双目图像校正与匹配,然后根据左右图像中目标点的不同坐标得出视差,最后利用三维重建方程组得出障碍物的距离。实验结果表明:该方法对汽车前方 5 米左右的障碍物检测精度高,距离误差小,能在城市复杂交通环境中及时作出相应的反应,有效避免交通事故的发生。

    视觉作为汽车重要的环境感知手段已经得到了越来越多的研究。视觉感知的研究主要包括基于视觉的定位,基于视觉的道路和交通标志检测与识别和基于视觉的避撞技术。单目视觉测距系统相对于双目视觉测距系统而言,由于单目获取的信息量比较少,仅靠标定后的单目相机来测距并不能准确获取目标距离,因此对于双目测距国内外的研究也越来越多。

    1.双目立体视觉测距系统,更好的模拟人眼功能

    在国外,就是利用双目视觉获取被检测车辆的深度信息,再用 Kalmam 滤波对被检测车辆在图像坐标系和世界坐标系中进行跟踪。基于双目视觉系统,提出了对运动目标的检测方法,并能很好测出运动目标深度信息和速度大小,该方法能够较好获得运动目标的轮廓和速度。

    通过双目立体视觉系统对牌照特征进行提取以及获取中心坐标来匹配相应的牌照对,然后利用 3D 重建方程组来获取前车位置参数。而在国内,在双目视觉基础上,应用 MeanShift 跟踪算法和双目立体视觉中空间点定位算法,设计了对运动目标的跟踪和测量系统。

    利用双目立体视觉系统实现了对目标的识别与定位,从而控制机器人手臂完成抓取操作。利用双目立体视觉算法获得路标信息,进而辅助全球卫星导航系统(GNSS)。

    然而尽管国内外的研究人员在测量前车距离方面做了很多研究,但是目前还是基于单目视觉的测距系统居多,由于单目视觉图像的距离感知精度偏低,因此,重点研究了双目立体视觉测距系统,它能够很好地模拟人眼功能,对三维世界进行感知。

    dc72d6d42e5ea06e8912b81ed8fc3f19.png

    双目立体视觉测距系统主要包括摄像机标定、图像分割、立体匹配和三维重建四个部分。对摄像机标定部分做了一定的改进,舍弃了原有的世界坐标系,提出了一种基于摄像机安装位置的汽车坐标系,这样就能够得出运动目标与汽车的相对位置,然后对汽车做出相应的控制处理,达到安全行驶的目的。具体流程,如图 1 所示。该方法能够有效提高障碍物检测精度,精确测得与前方物体的相对位置信息,具有一定的实用价值。

    2.双目视觉的原理

    2.1 摄像机标定

    摄像机标定是获取摄像机内外参数的一个过程,其目的是建立一个从客观世界坐标系到图像平面坐标系之间的一个对应过程。现在应用比较广泛的是张正友教授提出的标定法,它是基于传统标定法和自标定法之间的一种优化方法。而所选用的标定方法就是改进的张正友标定法。

    2.1.1 单目标定

    单目标定是摄像机双目标定的前提,只有两个摄像机的内外参数都知道后才可以进行双目标定,首先建立坐标投影模型,明确现实中的点在世界坐标系下,经过旋转、平移怎样映射到图像平面上,如图 2 所示。

    b4f24270a5aec8d34e01b4a9c67788b8.png 1b41214aa9818b591e3b7fa56a2b2e3b.png 3b8ceafe9403626150a10c3e64ea9d40.png 2d797d543ca1283207064522cb5f51ef.png

    2.2.2 双目标定

    782503bea8efcddcf1bb13df81d80319.png

    2.3 改进的标定算法

    双目标定中涉及到的世界坐标系是客观存在的,使用张正友标定法标定完后,内外参数一旦确定,则世界坐标系与汽车之间的位置关系也就相对静止了,若汽车在运动,则世界坐标系也在做了相同的运动。

    考虑到这个因素,而且在这个系统中,只考虑到汽车与障碍物的相对位置即可,因此,选择一个相对不变的坐标系,该坐标系选在了两个摄像机所在的竖直平面内,具体的改进方法如下:

    选择一个新的坐标系,把它称为汽车坐标系(X,Y,Z),它的原点位置选在了两台摄像机的中点。则齐次坐标关系为:

    4161f5acb57ad856e380c2c1b68e1490.png

    分别为左右摄像机内参数矩阵的逆。这样只要得出运动目标在左右摄像机图像上的坐标,再联立式(5)和式(10)就可以解得与汽车之间的相对位置。

    3.双目立体视觉算法

    3.1 图像滤波

    在实际拍摄过程中,由于受设备和环境等因素的影响,图像信号在记录以及传输过程中,经常会受到各种噪声的影响,使得图像质量下降,目标特征不突出,因此对以后的图像分割、特征点提取以及立体匹配等过程产生影响。

    所以必须对图像进行降噪处理,即对图像进行滤波,其中中值滤波属于非线性滤波,它对输入信号的响应是非线性的,因此会使某一点的噪声近似映射为零,而只保留信号的主要特征。所以本实验采用的是非线性滤波技术中的中值滤波。

    3.2 图像分割

    图像分割就是把图像细分为构成它的子区域或者对象,而进行分割的目的就是将我们所关注的图像目标从背景中提取出来,以便进一步分析和处理。

    本实验采用的 Otsu 阈值分割法,根据周围环境光照强度的不同,使类间方差最大,从而能够自动确定阈值,大于阈值的像素用白色表示,其它阈值的像素就用黑色表示,这样就将原图像转化为黑白的二值图像。

    cb8e80f8dcfe312b326ed3c6519b67df.png

    3.3 双目校正与匹配

    在获得摄像机内外参数后,再对双目图像进行校正处理,校正步骤有两个,一是消除镜头对图像产生的畸变,可以采用上节相机标定获得摄像机参数后,根据相机模型来进行消除;二是使双目图像共面并且严格行对齐,可以通过 Bouguet 重投影算法[11]将双目图像进行重投影。

    双目匹配就是将物体上的一个投影点在左右两相机中同一时刻所拍摄的两幅图像上进行匹配,就是将一幅图像上的目标点与另一幅图像上的目标点对应匹配起来。

    所使用的匹配算法是快速双目视觉立体匹配算法,经过该匹配算法处理后,可以将左右两摄像机图像中的

    3.4 三维重建

    08dd19d89d9fae946081b3edf506bd48.png

    4.实验验证与分析

    4.1 实验的软硬件资源

    试验所用的双目视觉测距系统包括两个 USB 摄像头,一台 PC 机,以及一个支架组成,如图 4 所示。软件环境为 VC++6.0,在此基础上载入 OpenCV 视觉库,可以完成整个系统的开发,系统主界面,如图 3 所示。但是由于使用 MATLB 对摄像机进行标定精度比较高,因此标定部分利用了 MATLB 软件。

    314b89114adc27aa9d72896833383c30.png

    4.2 摄像机标定

    摄像机的标定有许多办法可以实现,可以用 Opencv 提供的标定函数进行标定,也可以用 MatlabCalibrationToolbox 进行标定,由于我们本次实验采用双目摄像机为固定状态,因此只需要标定一次就可以,虽然用 Matlab 标定工具箱进行标定需要手动操作,使得速度慢,但是标定的精度较高,因此采用了 MATLAB 进行标定。

    首先打印一张(7X9)的黑白棋盘格的标定图片,如图 5 所示。并贴附在一个比较平整的水平板上,每个方格的大小为 28mmX28mm。

    f6932cbc2a083cdbfbc5afdcd9023454.png 92fe9df915fced290c59e6e530ae0395.png df08e98a3c7cebc4d138fe85ee5323e7.png 553662c8365a07b34b9e21a0fa1adced.png

    4.3 图像分割与立体匹配

    在匹配之前先对图像进行预处理,即对图像进行分割得到二值化图像,如图 9 所示。由于分割后的二值化图像仍有一定噪声,对特征点提取不利,所以再进行形态学腐蚀或者膨胀处理,图 10 所示为膨胀处理图像。通过对比可以看出,膨胀处理图像比二值化图像特征点更突出。

    27ab7368d4313002cc06ba53ea2b6e16.png 5d773e7cbcced0217e33134b10646cd2.png

    立体匹配是本次实验的重点,在匹配之前我们要先进行图像的去畸变处理,再将两图片进行极线约束。图 11 就表示了去畸变以后的极线约束,这样两图片所对应的匹配点就在同一条直线上了。左右图像的视差图,如图 12 所示。

    0f55d47e674e2fb31cff065b3900485f.png

    5.结语

    主要研究了基于双目立体视觉测距的汽车前方障碍物的测量方法,为了完成测距,先后研究了双目立体视觉原理,摄像机标定,图像滤波与图像分割等预处理,双目校正与匹配,三维重建等关键技术,并且利用了 MATLAB OpenCV 相结合的方法完成了测距。

    好让汽车进一步作出相应的配合。经过实验证明,该方法能够有效的测得前方障碍物的距离信息,提高汽车的感知精度。汽车在复杂环境下行驶时,如何自动提取多个目标物并准确完成距离测量工作将是无人驾驶汽车下一步研究的重点。

    展开全文
  • python双目视觉标定及三维重建

    千次阅读 热门讨论 2021-04-10 15:07:16
    写在前面的话: 一个机器视觉的课程作业,是自行采集一组双目图像,完成立体视觉相关流程:包括相机标定(内参和外参)、畸变校正、基本矩阵估算、视差图计算(需要先进行图像矫正)、恢复并画出3D点坐标。...
  • 基于Python三维重建开源代码,包括特征提取,SFM,PMVS以及CMVS等相关功能!
  • 本发明为双目立体视觉三维重建方法,属于图像处理与机器视觉领域,涉及halcon软件,具体涉及空间场景的三维重建。背景技术:三维重建技术作为计算机视觉领域的一个重要分支,在人工智能、虚拟现实、非接触无损检测...
  • 双目三维对好像就听过:Bundler和CMVS-PMVS但是三维重建开源项目就很多了1、Meshroom ⭐4,474Meshroom是一款基于AliceVision摄影测量计算机视觉框架的免费开源三维重建软件。...
  • 数字图像处理领域中基于双目立体视觉三维重建
  • // 前三维为xyz,第四维为颜色 // 根据双目模型计算 point 的位置 double x = (u - cx) / fx; double y = (v - cy) / fy; double depth = fx * b / (disparity.at(v, u)); point[0] = x * depth; point[1] = y * ...
  • 文件名称: code下载 收藏√ [5 4 3 2 1]开发工具: Visual C++文件大小: 7733 KB上传时间: 2015-12-29下载次数: 0提 供 者: 赵凡详细说明:三维重建源码,由双目立体视觉进行三位重建。并给出了几种重建方法的介绍,...
  • OpenCV Using Python——单目视觉三维重建

    万次阅读 多人点赞 2015-03-11 08:40:32
     单目视觉三维重建是根据单个摄像头的运动模拟双目视觉获得物体在空间中的三维视觉信息。已知单个摄像头在两个不同时间点上同时在空间中两个不同位置的图像等价于已知两个摄像头同一时间在空间两个不同位置的图像。...
  • 本博客将实现Python版本的双目三维重建系统,项目代码实现包含:`双目标定`,`立体校正(含消除畸变)`,`立体匹配`,`视差计算`和`深度距离计算/3D坐标计算` 的知识点。限于篇幅,本博客不会过多赘述算法原理,而是...
  • 双目相机标定的原理可太多了,反而对于标定结果的解析却很少。在这里对相机的内参和双目标定的内参详细解释。... 2)标定板图像占界面分之一左右,太小绝对不行,标定板要整个出现在图像里。 3)单目标定的图要...
  • 2015年09月05 -三维重建一直是机器视觉研究的热门方向,比如,基于双目视觉,单目视觉,多视几何,光场三维重建等等。每一种方法都有其有点和局限性。单目视觉需要拍摄多幅图像,并且在拍摄过程中需要不断的调整相机...
  • 一种基于OpenCV三维重建实现方案

    千次阅读 2020-12-30 17:03:04
    摘要以计算机视觉三维重建技术为研究对象,分析了开放计算机视觉函数库OpenCV中的三维重建模型,通过六个步骤,特别是摄像机标定和立体匹配中极线约束方法的使用,给出了基于OpenCV的三维重建算法。该算法充分发挥了...
  • 双目视觉/双目标定源码/图片集标定匹配三维重建坐标计算OpenCV 1、双目立体视觉源代码(包括标定,匹配,三维重建) 2、双目视觉实验图片集(双目立体视觉中使用的标准实验图,适合初学者进 行实验使用) 3、双目...
  • 一、什么是视觉三维重建?我们知道,照相机的原理是将一个三维场景投影到二维平面。所谓视觉三维重建,顾名思义就是从已有的二维图像中复原原始三维场景。三维重建的原理大致如下:首先,通过多角度拍摄或者从视频中...
  • Evision双目视觉关于双目视觉的一些总结相机模型标定视差算法:立体匹配重投影:测量,三维重建,重投影约束三维重建示例程序 关于双目视觉的一些总结 笔者2013年进入吉林大学软件学院,2014年开始写自己的第一个完整的...
  • 三维重建一直是机器视觉研究的热门方向,比如,基于双目视觉,单目视觉,多视几何,光场三维重建等等。 每一种方法都有其有点和局限性。单目视觉需要拍摄多幅图像,并且在拍摄过程中需要不断的调整相机的聚焦位置,...
  • 点击上方“机器学习与生成对抗网络”,关注"星标"获取有趣、好玩的前沿干货!三维重建意义三维重建作为环境感知的关键技术之一,可用于自动驾驶、虚拟现实、运动目标监测、行为分析...
  • python 双目标定

    2020-03-30 19:44:52
    刚写的确定好用 oprncv+python 请大家多多指教 在网上整理了一下解释挺全的,希望对大家有所帮助。 000000000000
  • 使用 OpenMVG+PMVS实现视觉三维重建

    千次阅读 2020-11-27 07:00:00
    点击上方“3D视觉工坊”,选择“星标”干货第一时间送达一、什么是视觉三维重建?我们知道,照相机的原理是将一个三维场景投影到二维平面。所谓视觉三维重建,顾名思义就是从已有的二维图像中复原原...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 697
精华内容 278
关键字:

双目视觉三维重建python

python 订阅