使用OpenCV 2.3.1和C ++校准单个摄像头

3

我正在尝试使用OpenCV 2.3.1和Visual Studio 2010(c++控制台应用程序)来校准网络摄像头。我正在使用这个类:

class CameraCalibrator{
private:
   std::vector<std::vector<cv::Point3f>> objectPoints;
   std::vector<std::vector<cv::Point2f>> imagePoints;
   //Square Lenght
   float squareLenght;
   //output Matrices
   cv::Mat cameraMatrix; //intrinsic
   cv::Mat distCoeffs;
   //flag to specify how calibration is done
   int flag;
   //used in image undistortion
   cv::Mat map1,map2;
   bool mustInitUndistort;
public:
    CameraCalibrator(): flag(0), squareLenght(36.0), mustInitUndistort(true){};
    int addChessboardPoints(const std::vector<std::string>& filelist,cv::Size& boardSize){
        std::vector<std::string>::const_iterator itImg;
        std::vector<cv::Point2f> imageCorners;
        std::vector<cv::Point3f> objectCorners;
        //initialize the chessboard corners in the chessboard reference frame
        //3d scene points
        for(int i = 0; i<boardSize.height; i++){
            for(int j=0;j<boardSize.width;j++){
                objectCorners.push_back(cv::Point3f(float(i)*squareLenght,float(j)*squareLenght,0.0f));
            }
        }
        //2D Image points:
        cv::Mat image; //to contain chessboard image
        int successes = 0;
        //cv::namedWindow("Chess");
        for(itImg=filelist.begin(); itImg!=filelist.end(); itImg++){
            image = cv::imread(*itImg,0);
            bool found = cv::findChessboardCorners(image, boardSize, imageCorners);
            //cv::drawChessboardCorners(image, boardSize, imageCorners, found);
            //cv::imshow("Chess",image);
            //cv::waitKey(1000);
            cv::cornerSubPix(image, imageCorners, cv::Size(5,5),cv::Size(-1,-1),
                cv::TermCriteria(cv::TermCriteria::MAX_ITER+cv::TermCriteria::EPS,30,0.1));
            //if we have a good board, add it to our data
            if(imageCorners.size() == boardSize.area()){
                addPoints(imageCorners,objectCorners);
                successes++;
            }
        }
        return successes;
    }
    void addPoints(const std::vector<cv::Point2f>& imageCorners,const std::vector<cv::Point3f>& objectCorners){
        //2D image point from one view
        imagePoints.push_back(imageCorners);
        //corresponding 3D scene points
        objectPoints.push_back(objectCorners);
    }
    double calibrate(cv::Size &imageSize){
        mustInitUndistort = true;
        std::vector<cv::Mat> rvecs,tvecs;
        return
            cv::calibrateCamera(objectPoints, //the 3D points
                imagePoints,
                imageSize, 
                cameraMatrix, //output camera matrix
                distCoeffs,
                rvecs,tvecs,
                flag);
    }
    void remap(const cv::Mat &image, cv::Mat &undistorted){
        std::cout << cameraMatrix;
        if(mustInitUndistort){ //called once per calibration
            cv::initUndistortRectifyMap(
                cameraMatrix,
                distCoeffs,
                cv::Mat(),
                cameraMatrix,
                image.size(),
                CV_32FC1,
                map1,map2);
            mustInitUndistort = false;
        }
        //apply mapping functions
        cv::remap(image,undistorted,map1,map2,cv::INTER_LINEAR);
    }
};

我正在使用10张640x480分辨率的棋盘图像(假设这已足够进行校准)。主要函数如下:

int main(){
    CameraCalibrator calibrateCam;
    std::vector<std::string> filelist;
    filelist.push_back("img10.jpg");
    filelist.push_back("img09.jpg");
    filelist.push_back("img08.jpg");
    filelist.push_back("img07.jpg");
    filelist.push_back("img06.jpg");
    filelist.push_back("img05.jpg");
    filelist.push_back("img04.jpg");
    filelist.push_back("img03.jpg");
    filelist.push_back("img02.jpg");
    filelist.push_back("img01.jpg");

    cv::Size boardSize(8,6);
    double calibrateError;
    int success;
    success = calibrateCam.addChessboardPoints(filelist,boardSize);
    std::cout<<"Success:" << success << std::endl;
    cv::Size imageSize;
    cv::Mat inputImage, outputImage;
    inputImage = cv::imread("img10.jpg",0);
    outputImage = inputImage.clone();
    imageSize = inputImage.size();
    calibrateError = calibrateCam.calibrate(imageSize);
    std::cout<<"Calibration error:" << calibrateError << std::endl;
    calibrateCam.remap(inputImage,outputImage);
    cv::namedWindow("Original");
    cv::imshow("Original",inputImage);
    cv::namedWindow("Undistorted");
    cv::imshow("Undistorted",outputImage);
    cv::waitKey();
    return 0;
}

一切都没有错误。cameraMatrix大致如下:

685.65 0 365.14
0 686.38 206.98
0 0 1

校准误差为0.310157,这是可以接受的。

但是当我使用remap时,输出图像比原始图像更糟糕。以下是示例:

原始图像:Original image]

无畸变图像:Undistorted image]

所以问题是,在校准过程中我是否做错了什么?10张不同的棋盘图像是否足够进行校准?您有什么建议吗?


可能是OpenCV使用棋盘进行转换的重复问题。 - karlphillip
嗨,我正在使用这个类。对我来说完全可以正常工作。唯一的问题是如何读取相机内参。我试图使用“.at<float>(0,0)”这个东西,但是我得到了错误信息…… - user1388142
可能类型错误,请尝试使用.at<double>(0,0)。 - Banana
2个回答

1
相机矩阵不会矫正镜头,这4个值仅是焦距(水平和垂直)以及图像中心(X和Y)。在您的代码中还有另一个3或4个值的行矩阵(称为distCoeffs),其中包含镜头映射-请参见Karl的答案以获取示例代码。

是的,我明白了。我看到了Karl的答案中的示例代码。正如您在我的代码中所看到的,distCoeffs包含在函数cv::initUndistortRectifyMap中,我想知道我是否正确编写了该函数,因为在使用cv::remap后,我得到的结果比原始结果更差,就像您在我的图片样本中所看到的那样。感谢您的帮助。 - Banana
@Banana - 检查distcoefs中的值,它们是否为空或奇怪(我知道很难知道哪些是好的值!),但您可以使用相机运行opencv示例软件并查看它计算了什么。 - Martin Beckett
顺便说一句,将图像中心设置为365.14,206.98对于640x480的图像来说完全不准确。很可能更接近320,240。我有一个中心点为317.66,240.11的640x480相机。 - cape1232

1

校准是通过数值优化完成的,该优化在解决方案附近具有相当平缓的斜率。此外,被最小化的函数非常非线性。因此,我猜测你的10张图像不足以完成校准。我使用非常广角的镜头(即非常畸变的图像)来校准相机,并尝试获取50或60张图像。

我尝试在图像的每个边缘上获取3或4个位置的棋盘图像,以及一些中间位置的图像,相对于相机具有多个方向,并且在3个不同距离(超近距离、典型距离和尽可能远但仍然能够分辨棋盘的距离)处拍摄。

将棋盘放置在靠近角落的位置非常重要。你的示例图像没有将棋盘放置在图像的角落附近。正是这些点将校准约束到在图像的非常畸变的部分(角落)执行正确的操作。


谢谢您的解释。我会按照您所描述的进行相机校准,并在这里写下所有的结果。 - Banana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接