精华内容
下载资源
问答
  • 双目视觉标定

    2014-05-05 16:55:57
    基于双目视觉的代码调试
  • 双目视觉标定整理

    千次阅读 2018-11-23 11:14:43
    看了好几天的双目视觉标定,还是没有完全掌握。现在把已经了解到的整理下,方便后面进一步的学习掌握。 双目视觉标定就是通过求解实际三维空间中坐标点和摄像机二维图像坐标点的对应关系,在双目视觉中,三维空间...

    看了好几天的双目视觉标定,还是没有完全掌握。现在把已经了解到的整理下,方便后面进一步的学习掌握。

    双目视觉标定就是通过求解实际三维空间中坐标点和摄像机二维图像坐标点的对应关系,在双目视觉中,三维空间坐标系一般是以左相机坐标系作为基准坐标系。利用棋盘板获取到的用于计算的二维图像坐标和三维空间的物理坐标,再通过一定的算法,求解出变换矩阵,则解决了基础的双目视觉标定的过程。

    实际标定过程中需要考虑镜头的畸变:包括径向畸变及切向畸变。因为切向畸变很小,所以通常主要考虑的就是径向畸变。求解畸变的方法后面再讨论。

    下面结合OpenCV自带双目标定的例程来学习掌握下双目视觉的标定过程:
    a. 输入准备:14(数字可调)对棋盘图、标定板的尺寸(widthxheight格数)及棋盘格物理尺寸、stereo_calib.xml(输入图片的列表)
    b. 立体标定。主要可分为4个部分:

    1. 输入检测与变量初始化
      角点存储矩阵
      vector<vector<Point2f> > imagePoints[2];//这个是左右图像中的二维点 vector<vector<Point3f> > objectPoints;//由上面的二维点得到的三维点
    2. 角点及亚像素角点检测,获取角点的2D图像坐标和3D物理坐标
      对左右相机的每一幅图像单独处理。要注意处理的图像必须含有相同的大小。此外,为了准确的检测出角点,部分图像可能需要进行scale缩放调整。
      findChessboardCorners()函数用于检测角点,需要输入目标图像,标定板大小,角点存储矩阵,及最后的算法设置变量,例程中为CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE表示使用直方图均衡算法和自适应二值化的法。返回bool值。具体的函数使用见
      棋盘格角点检测与绘制——cv::findChessboardCorners()与cv::drawChessboardCorners()详解

    cornerSubPix()函数根据已经得到的corners坐标计算更加精确的亚像素corners坐标。该函数需要输入目标图像,corners坐标矩阵,搜索窗口大小(Size()类型),死区窗口大小(Size()类型,目的是避免可能出现的自相关矩阵的奇点,不明白这一参数,可以设为默认值Size(-1,-1)),终止迭代的criteria,本文中为TermCriteria(TermCriteria::COUNT+TermCriteria::EPS,30, 0.01),表示当迭代次数大于30或当角点坐标变化小于0.01时停止。
    通过上述三个函数的使用,便可以准确的检测出所有图像的亚像素角点。
    然后根据输入的标定板大小和棋盘格格子的物理长度大小,便可以计算出角点的物理坐标,单位毫米。标定板的空间坐标系假设是以左上角第一个点为原点,棋盘格两边为x,y方向,垂直棋盘格为z方向。所以所有角点的空间坐标的z值均为0。
    3. 双目标定主模块,计算内参数矩阵,对标定结果进行验证
    stereoCalibrate()函数计算双目标定中的内参数矩阵,相对于1st相机的旋转平移矩阵,还有本征矩阵E和基础矩阵F。E包含在物理空间中两个摄像机相关的旋转和平移信息,F除了包含E的信息外还包括了两个摄像机的内参数。E是将左摄像机观测到的点P的物理坐标和右摄像机观察到的相同点的位置关联起来。F是将一台摄像机的像平面的点在像平面上的坐标和另一台摄像机的像平面上的点关联起来。
    关于本征矩阵和基础矩阵的详细介绍参考博客本征矩阵与基础矩阵

    立体标定完成后,通过对极几何约束公式m2TFm1可以检查校准的质量(检查图像上点与另一幅图像的极线的距离的远近来评定标定的精度),理想情况下点和线的点积为0,累计后的绝对距离形成了误差。可用于衡量标定结果。
    (这块不是很理解。。。。)

    1. 对标定后的结果进行立体校正,计算外参数矩阵

    利用stereoRectify()函数进行立体校正,这样做的目的是为了使得两个相机的光轴共面,极线平行。这样,就可以将二维的图像搜索简化为一维的图像检索,同时确保了极线匹配的精度。

    立体标定结束后,将相应的参数写入intrinsics.yml和extrinsics.yml中,则完成了双目视觉的标定。即双目标定最后获得的是相机内参和外参变换的文件。

    关于对极几何的相关知识可以参考博客对极几何
    Bouguet极线校正的方法

    源码如下,代码学习这块参考的是博客:
    https://blog.csdn.net/qq_35971623/article/details/78196399

    #include "stdafx.h"
    
    #include "opencv2/calib3d.hpp"
    #include "opencv2/imgcodecs.hpp"
    #include "opencv2/highgui.hpp"
    #include "opencv2/imgproc.hpp"
    
    #include <vector>
    #include <string>
    #include <algorithm>
    #include <iostream>
    #include <iterator>
    #include <stdio.h>
    #include <stdlib.h>
    #include <ctype.h>
    
    using namespace cv;
    using namespace std;
    
    
    //左图中的同一目标比右图中的同一目标偏右,即左右图命名不要弄反,与人眼视觉一致。
    
    static void
    StereoCalib(const vector<string>& imagelist, Size boardSize, float squareSize, bool displayCorners = true, bool useCalibrated=true, bool showRectified=true)
    //立体标定主程序:输入图片列表、棋盘图大小、面积大小、等控制标记
    {
    	//1. 输入检测及变量初始化
        if( imagelist.size() % 2 != 0 )//判断标定图片成对
        {
            cout << "Error: the image list contains odd (non-even) number of elements\n";
            return;
        }
    
        const int maxScale = 2;
    	const float squareSize = 26.f; //设置真实方格大小,1以毫米或者像素为单位的keypoint之间间隔距离,棋盘间隔1
        // ARRAY AND VECTOR STORAGE: //数组储存
    
        vector<vector<Point2f> > imagePoints[2];//这个是左右图像中的二维点
        vector<vector<Point3f> > objectPoints;//由上面的二维点得到的三维点
        Size imageSize;//图像大小
    
        int i, j, k, nimages = (int)imagelist.size()/2;//nimages是棋盘图对数,j是用于记录最后检测到了多少对棋盘图
    
        imagePoints[0].resize(nimages); //设置向量大小
        imagePoints[1].resize(nimages);
        vector<string> goodImageList;//检测到的棋盘图像列表(因为有的棋盘图像是检测不到的)
    
    	// 2.角点及亚像素角点检测,获取角点的2D图像坐标和3D物理坐标
        for( i = j = 0; i < nimages; i++ ) //单相机0-13幅图
        {
            for( k = 0; k < 2; k++ )//左右相机
            {
                const string& filename = imagelist[i*2+k];//图像文件名
                Mat img = imread(filename, 0);
                if(img.empty())
                    break;
                if( imageSize == Size() )
                    imageSize = img.size();
                else if( img.size() != imageSize )
                {
                    cout << "The image " << filename << " has the size different from the first image size. Skipping the pair\n";
                    break;
                }
                bool found = false;
                vector<Point2f>& corners = imagePoints[k][j];//左右图的第j幅图像的所有角点,通过findChessboardCorners对向量传参
    
    			//寻找角点,保存到imagePoints
                for( int scale = 1; scale <= maxScale; scale++ )
    			//通过scale是防止检测不到
                {
                    Mat timg;
                    if( scale == 1 )
                        timg = img;
                    else
                        resize(img, timg, Size(), scale, scale, INTER_LINEAR_EXACT);
                    found = findChessboardCorners(timg, boardSize, corners,
                        CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE);//如果找到角点就返回true
                    if( found )
                    {
                        if( scale > 1 )
                        {
                            Mat cornersMat(corners);//vector角点转化为Mat矩阵,方便计算
                            cornersMat *= 1./scale;//scale放大了,所以要缩放
                        }
                        break;
                    }
                }
                if( displayCorners )
                {
                    cout << filename << endl;
                    Mat cimg, cimg1;
                    cvtColor(img, cimg, COLOR_GRAY2BGR);
                    drawChessboardCorners(cimg, boardSize, corners, found);//棋盘格图像(8UC3)既是输入也是输出
                    double sf = 640./MAX(img.rows, img.cols);
                    resize(cimg, cimg1, Size(), sf, sf, INTER_LINEAR_EXACT);
                    imshow("corners", cimg1);
                    char c = (char)waitKey(500);
                    if( c == 27 || c == 'q' || c == 'Q' ) //Allow ESC to quit
                        exit(-1);
                }
                else
                    putchar('.');
                if( !found )
                    break;
    			//插值亚像素点,用来精确得到的corners坐标
                cornerSubPix(img, corners, 
    				Size(11,11), //为搜索窗口大小,区域大小为NxN,N = (winSize*2 + 1),搜索窗口边长的一半
    				Size(-1,-1),//Size(-1,-1)表示忽略,当值为(-1,-1)表示没有死区
                             TermCriteria(TermCriteria::COUNT+TermCriteria::EPS,
                                          30, 0.01)//停止优化的标准,当迭代次数大于30或当角点坐标变化小于0.01时停止
    			);
            }//k循环结束,k = 2
            if( k == 2 )//内层循环完后将j加 1 ,并把找到的棋盘图放入容器
            {
                goodImageList.push_back(imagelist[i*2]);
                goodImageList.push_back(imagelist[i*2+1]);
                j++;
            }
        }
        cout << j << " pairs have been successfully detected.\n";
        nimages = j;
        if( nimages < 2 )//如果图片数量少于一对,那么报错
        {
            cout << "Error: too little pairs to run the calibration\n";
            return;
        }
    
        imagePoints[0].resize(nimages);
        imagePoints[1].resize(nimages);
        objectPoints.resize(nimages);
    
    	//计算角点的3D物理坐标
        for( i = 0; i < nimages; i++ )
        {
            for( j = 0; j < boardSize.height; j++ )
                for( k = 0; k < boardSize.width; k++ )
                    objectPoints[i].push_back(Point3f(k*squareSize, j*squareSize, 0));//通过角点长宽以及squareSize每个角点的步长算出角点的位置
        }
    
        cout << "Running stereo calibration ...\n";
    
    	//3. 双目标定主模块,计算内参数矩阵,对标定结果进行验证
        Mat cameraMatrix[2], distCoeffs[2];
        cameraMatrix[0] = initCameraMatrix2D(objectPoints,imagePoints[0],imageSize,0);//定义3D到2D的初始化的摄像机变换矩阵
        cameraMatrix[1] = initCameraMatrix2D(objectPoints,imagePoints[1],imageSize,0);
        Mat R, T, E, F;//R 第一与第二相机坐标系之间的旋转矩阵
    				   //T 第一与第二相机坐标系之间的旋转矩阵平移向量
    				   //E 本征矩阵
    				   //F 基础矩阵
    
        double rms = stereoCalibrate(objectPoints, imagePoints[0], imagePoints[1],
                        cameraMatrix[0], distCoeffs[0],
                        cameraMatrix[1], distCoeffs[1],
                        imageSize, R, T, E, F,
                        CALIB_FIX_ASPECT_RATIO + //优化,确定的比值
                        CALIB_ZERO_TANGENT_DIST + //设置每个相机切向畸变系数为0且设为固定值
                        CALIB_USE_INTRINSIC_GUESS + //内参初始值可以设定
                        CALIB_SAME_FOCAL_LENGTH + //强制横纵方向焦距相同
                        CALIB_RATIONAL_MODEL + //启用参数k4,k5,k6。提供向后兼容性,这额外FLAG应该明确指定校正函数和返回8个系数。如果FLAG没有被设置,该函数计算并只返回5畸变系数。
                        CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5,//计算极线向量
                        TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-5)//迭代优化算法终止的标准(用来判断校准效果) 
    	);
        cout << "done with RMS error=" << rms << endl;
    
    	// CALIBRATION QUALITY CHECK
    	// because the output fundamental matrix implicitly
    	// includes all the output information,
    	// we can check the quality of calibration using the
    	// epipolar geometry constraint: m2^t*F*m1=0
    	/* 验证标定的效果:由于输出的基础矩阵包含所有的输出信息,所以这里可以用对极几何约束(m2^t*F*m1 = 0)来验证---- - 下面这段程序可以不要*/
    
    	/*
    	校准详细过程:
    	检查图像上点与另一幅图像的极线的距离的远近来评价标定的精度。
    	使用undistortPoints对原始点做去畸变处理。
    	使用computeCorrespondEpilines来计算极线。
    	然后,计算这些点和线的点积(理想情况,这些点积都为0)。
    	累计的绝对距离形成了误差。
    	*/
        double err = 0;
        int npoints = 0;
        vector<Vec3f> lines[2];//极线
        for( i = 0; i < nimages; i++ )
        {
            int npt = (int)imagePoints[0][i].size();//左相机图中所有角点数量
            Mat imgpt[2];
            for( k = 0; k < 2; k++ )
            {
                imgpt[k] = Mat(imagePoints[k][i]);//第i幅图的角点向量矩阵
    
                undistortPoints(imgpt[k], imgpt[k], cameraMatrix[k], distCoeffs[k], Mat(), cameraMatrix[k]);//计算校正后的角点坐标
    			
    
    			//计算对应点的外极线epilines是一个三元组(a,b,c),表示点在另一视图中对应的外极线ax+by+c=0;
                computeCorrespondEpilines(imgpt[k], k+1, F, lines[k]);//为一幅图像中的点计算其在另一幅图中对应的对极线
            }
            for( j = 0; j < npt; j++ )
            {
                double errij = fabs(imagePoints[0][i][j].x*lines[1][j][0] +
                                    imagePoints[0][i][j].y*lines[1][j][1] + lines[1][j][2]) +
                               fabs(imagePoints[1][i][j].x*lines[0][j][0] +
                                    imagePoints[1][i][j].y*lines[0][j][1] + lines[0][j][2]);
                err += errij;
            }
            npoints += npt;
        }
    
        cout << "average epipolar err = " <<  err/npoints << endl;
    
        // save intrinsic parameters
        FileStorage fs("intrinsics.yml", FileStorage::WRITE); //创建.yml文件
        if( fs.isOpened() )
        {
            fs << "M1" << cameraMatrix[0] << "D1" << distCoeffs[0] <<
                "M2" << cameraMatrix[1] << "D2" << distCoeffs[1];
            fs.release();
        }
        else
            cout << "Error: can not save the intrinsic parameters\n";
    
    	//4.对标定后的结果进行立体校正,计算外参数矩阵
    	//stereoRectify根据内参和畸变系数计算右相机相对左相机的旋转R和平移矩阵T
    	//并将旋转与平移矩阵分解为左右相机个旋转一般的旋转矩阵R1,R2和平移矩阵T1,T2
    	//这里用的是bougust极线校准方法
        Mat R1, R2, P1, P2, Q;//R1,R2两个相机的3x3旋转矩阵
    						  //P1,P2在第一/二台相机的矫正后的坐标系下的3x4投影矩阵
    						  //Q 深度视差映射矩阵
        Rect validRoi[2];
    
        stereoRectify(cameraMatrix[0], distCoeffs[0],
                      cameraMatrix[1], distCoeffs[1],
                      imageSize, R, T, R1, R2, P1, P2, Q,
                      CALIB_ZERO_DISPARITY, 1, imageSize, &validRoi[0], &validRoi[1]);//立体校正程序,适用标定过的摄像机
    
        fs.open("extrinsics.yml", FileStorage::WRITE);
        if( fs.isOpened() )//在.yml中写入矩阵参数
        {
            fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q;
            fs.release();
        }
        else
            cout << "Error: can not save the extrinsic parameters\n";
    
    	// OpenCV 可以处理左右放置和上下放置的相机
        bool isVerticalStereo = fabs(P2.at<double>(1, 3)) > fabs(P2.at<double>(0, 3));
    
    	// 校正映射
        if( !showRectified )
            return;
    
        Mat rmap[2][2];//校正映射:左右图像各两个
    // IF BY CALIBRATED (BOUGUET'S METHOD)
        if( useCalibrated )
        {
            // we already computed everything
        }
    // 否则使用HARTLEY'S METHOD校正
        else
    
     //使用每个相机的内部参数,但校正变换直接通过基础矩阵的计算得到
        {
            vector<Point2f> allimgpt[2];//拷贝的角点
            for( k = 0; k < 2; k++ )
            {
                for( i = 0; i < nimages; i++ )
                    std::copy(imagePoints[k][i].begin(), imagePoints[k][i].end(), back_inserter(allimgpt[k]));
            }
            F = findFundamentalMat(Mat(allimgpt[0]), Mat(allimgpt[1]), FM_8POINT, 0, 0);//计算基础矩阵
            Mat H1, H2;//计算单应矩阵
            stereoRectifyUncalibrated(Mat(allimgpt[0]), Mat(allimgpt[1]), F, imageSize, H1, H2, 3);
    
            R1 = cameraMatrix[0].inv()*H1*cameraMatrix[0];
            R2 = cameraMatrix[1].inv()*H2*cameraMatrix[1];
            P1 = cameraMatrix[0];
            P2 = cameraMatrix[1];
        }
    
        //显示校正后的图像
    	//计算左右视图的校正查找映射表
        initUndistortRectifyMap(cameraMatrix[0], distCoeffs[0], R1, P1, imageSize, CV_16SC2, rmap[0][0], rmap[0][1]);
        initUndistortRectifyMap(cameraMatrix[1], distCoeffs[1], R2, P2, imageSize, CV_16SC2, rmap[1][0], rmap[1][1]);
    
        Mat canvas; // 这个图像的长是棋盘图的两倍,高和棋盘图像一样(这是在水平放置的情况下)
        double sf;
        int w, h;
        if( !isVerticalStereo) //这个是水平放置的
        {
            sf = 600./MAX(imageSize.width, imageSize.height);
            w = cvRound(imageSize.width*sf);
            h = cvRound(imageSize.height*sf);
            canvas.create(h, w*2, CV_8UC3);
        }
        else
        {
            sf = 300./MAX(imageSize.width, imageSize.height);
            w = cvRound(imageSize.width*sf);
            h = cvRound(imageSize.height*sf);
            canvas.create(h*2, w, CV_8UC3);
        }
    
    	/*这里是画出校正后的棋盘图:对棋盘图进行校正、画出校正后的可用ROI、画出左右两边对极后的极线。如果执行这里,则要等这里检测到的所有棋盘图都画完之后才会执行后续操作*/
        for( i = 0; i < nimages; i++ )
        {
            for( k = 0; k < 2; k++ )
            {
                Mat img = imread(goodImageList[i*2+k], 0), rimg, cimg;
                remap(img, rimg, rmap[k][0], rmap[k][1], INTER_LINEAR);//对图像原进行重映射,映射输出为校正后的图像
                cvtColor(rimg, cimg, COLOR_GRAY2BGR);
                Mat canvasPart = !isVerticalStereo ? canvas(Rect(w*k, 0, w, h)) : canvas(Rect(0, h*k, w, h));//这里是得出校正两个ROI
                resize(cimg, canvasPart, canvasPart.size(), 0, 0, INTER_AREA);
                if( useCalibrated )
                {
                    Rect vroi(cvRound(validRoi[k].x*sf), cvRound(validRoi[k].y*sf),
                              cvRound(validRoi[k].width*sf), cvRound(validRoi[k].height*sf));
                    rectangle(canvasPart, vroi, Scalar(0,0,255), 3, 8);//将矩形画出来
                }
            }
    
            if( !isVerticalStereo )
                for( j = 0; j < canvas.rows; j += 16 ) //画极线
                    line(canvas, Point(0, j), Point(canvas.cols, j), Scalar(0, 255, 0), 1, 8);
            else
                for( j = 0; j < canvas.cols; j += 16 )
                    line(canvas, Point(j, 0), Point(j, canvas.rows), Scalar(0, 255, 0), 1, 8);
            imshow("rectified", canvas);
            char c = (char)waitKey();
            if( c == 27 || c == 'q' || c == 'Q' )
                break;
        }
    }
    
    
    static bool readStringList( const string& filename, vector<string>& l )//现在这里是读取每个图像文件名,把图像名放到imagelist 容器中
    {
        l.resize(0);
        FileStorage fs(filename, FileStorage::READ);
        if( !fs.isOpened() )
            return false;
        FileNode n = fs.getFirstTopLevelNode();//返回映射(mapping)顶层的第一个元素,及.xml文件第一个元素
        if( n.type() != FileNode::SEQ )
            return false;
        FileNodeIterator it = n.begin(), it_end = n.end();
        for( ; it != it_end; ++it )
            l.push_back((string)*it);//这里是把读取到的图像的名字放入容器
        return true;
    }
    
    int main(int argc, char** argv)
    {
        Size boardSize;//标定板尺寸
        string imagelistfn;
        bool showRectified = true;
        cv::CommandLineParser parser(argc, argv, "{w|9|}{h|6|}{s|1.0|}{nr||}{help||}{@input|E://Visual Studio 2015//Projects//refreCode1//standSample//stereo_calib.xml|}");
        if (parser.has("help"))
            return -1;
        showRectified = !parser.has("nr");
        imagelistfn = parser.get<string>("@input");
    	boardSize.width = parser.get<int>("w");
        boardSize.height = parser.get<int>("h");
        float squareSize = parser.get<float>("s");
        if (!parser.check())
        {
            parser.printErrors();
    		system("pause");
            return 1;
        }
        vector<string> imagelist;
        bool ok = readStringList(imagelistfn, imagelist);
        if(!ok || imagelist.empty())
        {
            cout << "can not open " << imagelistfn << " or the string list is empty" << endl;
            return -1;
        }
    
        StereoCalib(imagelist, boardSize, squareSize, false, true, showRectified);	
    
    	system("pause");
        return 0;
    }
    
    展开全文
  • 双目视觉标定棋盘格模板程序,有c++程序和生成的图像模板。要打印,最好300dpi以上 ,可用photoshop修改分辨率。程序生成的是72dpi.
  • 该文件为双目视觉标定所需要的数据图片,其中分为左相机图片和右相机图片
  • 国外的基于matalb的双目视觉标定的程序,http://www.vision.caltech.edu/bouguetj/calib_doc/
  • 标题 一个双目视觉标定的问题 当我们用calibration toolbox进行双目标定时,步骤是分别对左右两个相机进行标定,再对整个双目视觉系统进行标定,此时有时候会出现如下图所示的问题 在这里插入图片描述 出现此类问题...

    标题 一个双目视觉标定的问题

    当我们用calibration toolbox进行双目标定时,步骤是分别对左右两个相机进行标定,再对整个双目视觉系统进行标定,此时有时候会出现如下图所示的问题在这里插入图片描述
    在这里插入图片描述
    出现此类问题说明左右对应的图像不连续,此处的不连续指的是左右图像使用的不是同一个基准下的坐标系,为了解决这个问题,我们必须将左右两张对应棋盘格图片选用同一个点作为坐标原点,同时x和y轴也必须一样

    展开全文
  • 基于OpenCV的双目视觉标定程序的开发 基于OpenCV的双目视觉标定程序的开发
  • Opencv python 双目视觉标定问题小节前置知识具体实践未解决问题小结 这段时间导师让我们学一点关于双目视觉的东西,由此有了这篇文章,以来记录测试时遇到的各种问题以及如何解决。 前置知识 首先在了解视觉标定的...

    Opencv python 双目视觉标定问题小节

    这段时间导师让我们学一点关于双目视觉的东西,由此有了这篇文章,以来记录测试时遇到的各种问题以及如何解决。

    前置知识

    首先在了解视觉标定的相关基础知识时,我对视差如何计算有点不解,主要分不清xl-xr的时候用的是哪个坐标系,经过一番查找资料,最后知道了求xl-xr时其实用的是图片像素坐标,也就是图片校正完后的相关对应点的各自像素坐标相减。其实很简单的一个问题,但当时就是卡壳了以下,具体可参考此篇博客,写得相当详细:
    双目测距与三维重建的OpenCV实现问题集锦

    关于几大坐标系的转换以及相机相关参数的理解可参考以下博客:
    模型 16个相机参数(内参、外参、畸变参数)

    坐标系

    具体实践

    刚开始我把所有的步骤都集中在python + opencv上完成,但到了校正环节,发现遇到了与书上例子不同的情况,即校正完毕后,我的两张图片并没有行对齐,因此在这一环节卡了许久。首先我以为是自己的棋盘图片原因,因此用了官方例子上的棋盘图片,但还是不行,然后我又仔细看了一遍代码,发现也没问题,我就开始怀疑是否为python版本的opencv本身原因,想着换c版本的运行一遍,但正当我想换C时,在网上发现了这篇博客
    opencv实现立体匹配
    对于当时的我简直如获至宝,因为其遇到的问题跟我一样,他最后选择用matlab来获取标定数据,于是在百度云的重重网速限制下,我安装了matlab,经过尝试,果然最后能标定成功,因此得出结论为opencv自身标定方法确实有严重偏差。

    未解决问题

    当完成立体标定后,到了最后一步,获取视差图及深度并转换成世界坐标。老实说,这一步的函数没几个,但都令人头疼。
    我用的是SGBM算法, 即opencv中的StereoSGBM这个函数,从网上找了相关运行参数,觉得挺详细的,就码了下来

    SGBM_blockSize = 5  # 一个匹配块的大小,大于1的奇数
    SGBM_num = 2
    min_disp = 0  # 最小的视差值,通常情况下为0
    num_disp = SGBM_num * 16  # 192 - min_disp #视差范围,即最大视差值和最小视差值之差,必须是16的倍数。
    # blockSize = blockSize #匹配块大小(SADWindowSize),必须是大于等于1的奇数,一般为3~11
    uniquenessRatio = 6  # 视差唯一性百分比, 视差窗口范围内最低代价是次低代价的(1 + uniquenessRatio/100)倍时,最低代价对应的视差值才是该像素点的视差,否则该像素点的视差为 0,通常为5~15.
    speckleRange = 2  # 视差变化阈值,每个连接组件内的最大视差变化。如果你做斑点过滤,将参数设置为正值,它将被隐式乘以16.通常,1或2就足够好了
    speckleWindowSize = 60  # 平滑视差区域的最大尺寸,以考虑其噪声斑点和无效。将其设置为0可禁用斑点过滤。否则,将其设置在50-200的范围内。
    disp12MaxDiff = 200  # 左右视差图的最大容许差异(超过将被清零),默认为 -1,即不执行左右视差检查。
    P1 = 600  # 惩罚系数,一般:P1=8*通道数*SADWindowSize*SADWindowSize,P2=4*P1
    P2 = 2400  # p1控制视差平滑度,p2值越大,差异越平滑
    
    SGBM_stereo = cv.StereoSGBM_create(
                minDisparity=min_disp,  # 最小的视差值
                numDisparities=num_disp,  # 视差范围
                blockSize=SGBM_blockSize,  # 匹配块大小(SADWindowSize)
                uniquenessRatio=uniquenessRatio,  # 视差唯一性百分比
                speckleRange=speckleRange,  # 视差变化阈值++++++
                speckleWindowSize=speckleWindowSize,
                disp12MaxDiff=disp12MaxDiff,  # 左右视差图的最大容许差异
                P1=P1,  # 惩罚系数
                P2=P2![在这里插入图片描述](https://img-blog.csdnimg.cn/2019102817164578.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Nseng1NTEx,size_16,color_FFFFFF,t_70)
            )
    

    当然还有BM算法:

    num = cv.getTrackbarPos("num", "depth")
    blockSize = cv.getTrackbarPos("blockSize", "depth")
    if blockSize % 2 == 0:
         blockSize += 1
    if blockSize < 5:
         blockSize = 5
    stereo = cv.StereoBM_create(numDisparities=16 * num, blockSize=blockSize)
    

    两个算法我都试了下,最后得出的实时视差图显示都不理想,在这里插入图片描述总之跟马赛克一样,,跟网上的例子相差许远,问了师兄,师兄说是相机问题,他也是同样的情况,换个好点的相机就好一点了,,

    然后在返回世界坐标这一步,用了reprojectImageTo3D这个函数,在查看返回的坐标系时发现极其不精准,有负数的,有距离极其夸张的,甚至在获取相同点在离摄像机不同的距离的情况下其返回的坐标是不变的。
    在这里插入图片描述
    这一点到目前为止都不清楚是怎么回事。。

    为此我简化过程根据Z = K/D 这个公式,手动测试了下不同距离下的棋盘图片两个相对的角点的距离比:
    在这里插入图片描述首先根据校正所求的Q矩阵获取参数f,Tx。
    后来我一想因为我只想获取对应的距离关系,所以简化公式为Z = 1/D;
    在这里插入图片描述

    对两组距离比为1.5的棋盘图片做了测试,结果还算满意:
    在这里插入图片描述
    在这里插入图片描述
    确实为1.5倍的样子,同时也说明了视差就是像素坐标相减。

    小结

    其实就是一些简单的概念加函数使用问题,但最后的坐标转换还是未解决,先记录下,日后再说吧.

    展开全文
  • opencv双目视觉标定、匹配和测量

    千次阅读 2017-11-23 16:15:31
    1. OpenCV双目标定2.... 平行双目视觉标定7. 十.双目视觉系统标定8. 使用opencv做双目测距(相机标定+立体匹配+测距)9. opencv 模板匹配目标检测10. 【计算机视觉】双目测距(二)--双目标定与矫正更多相关
    展开全文
  • 双目视觉的一些原理,包括双目视觉的系统标定双目视觉的一些成功案例应用已经双目视觉当前研究难点热点
  • opencv双目视觉标定、匹配和测量 (附代码)

    万次阅读 多人点赞 2016-10-10 16:11:07
    本文主要记录我自己在双目视觉标定,立体匹配,测量中遇到的问题和解决方法,并附有代码,文末有代码下载的地址,欢迎交流。博主使用的相机是USB双目免驱相机,相机驱动见另外一篇博客: USB免驱摄像头采集图像...
  • 分析了双目视觉传感器的数学模型,提出了一种基于同心圆合成图像匹配的双目视觉传感器的标定方法。在测量范围内任意多次摆放同心圆靶标,由两台摄像机拍摄靶标图像。根据摄像机模型与已知同心圆在靶标坐标系上的位置...
  • 基于OpenCV的双目视觉标定程序的开发.kdh论文
  • 这个系列博客旨在捋顺一下已标定双目视觉中的数学主线。数学推导是有着几分枯燥的,但奇妙的计算机视觉世界是建立在严密的数学架构之上的。所以对数学框架的理解是理解双目视觉的必由之路。不过请大家放心,接...
  • 为了提高大视场、远距离的双目摄像机标定精度,提出一种基于位姿约束的摄像机标定算法。该方法利用双目摄像机之间的三维位姿关系是刚体变换这一属性,标定出左、右摄像机相对位姿的外部参数。利用相对位姿为约束条件...
  • opencv双目视觉标定、匹配和测量 (附代码) 双目视觉原理方面参照《学习Opencv》和大牛博客 http://blog.csdn.net/chenyusiyuan/article/details/5970799中16-19系列博客。本文主要记录我自己在双目视
  • 双目视觉标定,矫正,深度图(Vs +OpenCV C++ Python实现) *****
  • 双目视觉标定,激光结构光提取,指定特征点获取世界坐标标定方面校正结构光提取二维点转换为三维点总结 这学期在做双目视觉方面的事情,因为没人带,自己一个人踩了很多坑,因此在这写一点自己的总结心得。 标定方面...
  • 针对现有变焦镜头标定方法难度大、动态精度低等问题, 提出一种基于单应性矩阵的动态变焦双目内外参数估计方法和平面快速重建方法。利用双目图像匹配点及变焦前后的匹配点进行两类单应性矩阵估计;基于变焦数学模型和...
  • 双目视觉 标定+矫正 (基于MATLAB)

    千次阅读 多人点赞 2019-01-24 13:54:29
    准备双目摄像头: 准备标定标定的开始阶段最需要用到的标定板,可以直接从opencv官网上能下载到:http://docs.opencv.org/2.4/_downloads/pattern.png 具体如下: 然后,建议固定到盒子上,方便等下...
  • yxq双目视觉标定总结

    2020-09-01 21:14:10
    加粗样式@TOC11111111111111111111111 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法...
  • ///////张正友标定法opencv实现/////// #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include &lt;opencv2/calib3d.hpp&gt; #include &lt;...
  • 写在前面的话: 一个机器视觉的课程作业,是自行采集一组双目图像,完成立体视觉相关流程:包括相机标定(内参和外参)、畸变校正、基本矩阵估算、视差图计算(需要先进行图像矫正)、恢复并画出3D点坐标。...
  • Bouguet的Matlab标定工具箱,详细使用教程,中文。
  • 平行双目视觉标定

    2010-09-06 17:54:48
    平行双目视觉 当使用同一个相机时,即会满足平行双目视觉的条件 同一相机在两个不同位置拍摄得到两幅图像,空间某一点在这两幅图像上的图像坐标为P1(u1,v1)和P2(u2,v2) %空间点p坐标为(x,y,z) %基线长度为b % ...

空空如也

空空如也

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

双目视觉标定