单目标定分析+外参数求解+重投影误差验证

可以验证 RANSAC + PNP + BA 对单目标定中重投影误差的计算和  opencv 中 calibrateCamera输出的误差一模一样!!!,当然你也可以用Projection直接计算重投影误差。

看代码,此代码改编自opencv安装目录下 “calibration.cpp”,这里我以三种方式打印每张棋盘的重投影误差:

1、calibrateCamera 输出的每张图的重投影误差(cv::Mat perViewErrors 中每一行代表一张棋盘的重投影误差)

2、借助 calibrateCamera 输出的棋盘外参数 rvecs, tvecs 和 projectPoints()计算重投影点,接着计算每张棋盘重投影误差

3、借助 solvePnPRansac + solvePnPRefineLM 求得 rvecs, tvecs,接着..同2

      你看看输出,我测了三组棋盘(SB VS2019让第二组图挂了,我这里暂时上传两组图,请放大看,或者下载看)

 

 

 

      其实在实验过程中,  solvePnPRansac的作用在于梯度下降过程中取一个好的起点。如果我把solvePnPRansac 参数误差阈值 float reprojectionError 设置得非常小,我们从 inliers 可知内点比例不是100%,甚至不到50%。这时候,起点选的不够好,容易陷入极小值。ransanc的作用在于选择最优起点,所以我们应该把迭代次数设置得越高越好,把门槛误差设置得越松(大)越好。而在梯度下降过程中,迭代次数也要要高,至于门槛误差,,那当然是要无限小最好。

  1 #define _CRT_SECURE_NO_WARNINGS
  2 #include <iostream>     // cout
  3 #include<fstream>
  4 #include <algorithm>    // std::sort
  5 using namespace std;
  6 using std::string;
  7 using std::cout;
  8 using std::cerr;
  9 using std::endl;
 10 
 11 #include <opencv2/opencv.hpp>
 12 using namespace cv;
 13 
 14 void readFileNames(const string& fileList, vector<string>& fileNames)
 15 {
 16     fstream txt;
 17     txt.open(fileList, ios::in);
 18     while (!txt.eof())
 19     {
 20         string s;
 21         getline(txt, s);
 22         if (!s.empty())
 23         {
 24             fileNames.push_back(s);
 25         }
 26     }
 27     txt.close();
 28     return;
 29 }
 30 
 31 // camera -> pixel
 32 // (X Y Z)-> (u v) 
 33 void CameraToPixel(const Point3d& pt3d, const Mat& M, Point2d& pt2d)
 34 {
 35     double fx = M.at<double>(0, 0);
 36     double fy = M.at<double>(1, 1);
 37     double cx = M.at<double>(0, 2);
 38     double cy = M.at<double>(1, 2);
 39     pt2d.x = fx * pt3d.x / pt3d.z + cx;
 40     pt2d.y = fy * pt3d.y / pt3d.z + cy;
 41 }
 42 
 43 int main(int argc, const char* argv[])
 44 {
 45     // 第一组棋盘
 46     //string folder = "calibration1\\";//
 47     //string fileList = "filelist_1.txt";
 48 
 49     //float image_sf = 1.f;    // 后面要resize
 50     //int delay = 250;          // 延时,方便观察
 51     //int board_w = 12;
 52     //int board_h = 12;
 53 
 54     // 第二组棋盘
 55     string folder = "calibration2\\";//
 56     string fileList = "filelist_2.txt";
 57 
 58     float image_sf = 1.f;    // 后面要resize
 59     int delay = 250;          // 延时,方便观察
 60     int board_w = 7;
 61     int board_h = 5;
 62 
 63     // 第三组棋盘
 64     //string folder = "calibration3\\";//
 65     //string fileList = "filelist_3.txt";
 66 
 67     //float image_sf = 1.0;    // 后面要resize
 68     //int delay = 10;          // 延时,方便观察
 69     //int board_w = 9;
 70     //int board_h = 6;
 71 
 72     vector<string> fileNames;
 73     readFileNames(fileList, fileNames);
 74     int num_files = (int)fileNames.size();
 75     cout << "   ... Done. Number of files = " << num_files << "\n" << endl;
 76     int n_boards = num_files;
 77 
 78     int board_n = board_w * board_h;  // 一张图棋盘点数量
 79     cv::Size board_sz = cv::Size(board_w, board_h);  //棋盘的是12X12
 80 
 81     // 提取样本点
 82     vector<vector<cv::Point2f> > image_points;
 83     vector<vector<cv::Point3f> > object_points;
 84 
 85     // <1>、检测2D角点、指定3D棋盘点
 86     cv::Size image_size;
 87     int board_count = 0;
 88     for (size_t i = 0; (i < fileNames.size()) && (board_count < n_boards); ++i) {
 89         cv::Mat image, image0 = cv::imread(folder + fileNames[i]);
 90         board_count += 1;
 91         if (!image0.data)
 92         {
 93             cerr << folder + fileNames[i] << ", file #" << i << ", is not an image" << endl;
 94             continue;
 95         }
 96         image_size = image0.size();
 97         cv::resize(image0, image, cv::Size(), image_sf, image_sf, cv::INTER_LINEAR);//这里是在干鸡毛???
 98 
 99         // 在图上找到棋盘
100 
101         vector<cv::Point2f> corners;
102         bool found = cv::findChessboardCorners(image, board_sz, corners);
103 
104         // 画出来
105         drawChessboardCorners(image, board_sz, corners, found);  // 当然是找到了才能画出来
106 
107         //  如果找到了,就可以提取2D焦点了
108         if (found) {
109             image ^= cv::Scalar::all(255); // 异或运算
110             cv::Mat mcorners(corners);
111 
112             // do not copy the data
113             mcorners *= (1.0 / image_sf); // 角点坐标也要缩放
114 
115             // 缩放角点坐标
116             image_points.push_back(corners);
117             object_points.push_back(vector<cv::Point3f>());// malloc?
118             vector<cv::Point3f>& opts = object_points.back();
119 
120             opts.resize(board_n);
121             for (int j = 0; j < board_n; j++) {
122                 opts[j] = cv::Point3f(static_cast<float>(j / board_w),static_cast<float>(j % board_w), 0.0f);
123             }
124             cout << "Collected " << static_cast<int>(image_points.size())
125                 << "total boards. This one from chessboard image #"
126                 << i << ", " << folder + fileNames[i] << endl;
127         }
128         cv::namedWindow("Calibration", cv::WINDOW_NORMAL);
129         cv::imshow("Calibration", image);
130 
131         // show in color if we did collect the image
132         if ((cv::waitKey(delay) & 255) == 27) {
133             return -1;
134         }
135     }
136 
137     cv::destroyWindow("Calibration");
138     cout << "\n\n*** CALIBRATING THE CAMERA...\n" << endl;
139 
140     // <2>、调用算法开始标定
141     cv::Mat intrinsic_matrix, distortion_coeffs;
142     std::vector<cv::Mat> rvecs;
143     std::vector<cv::Mat> tvecs;
144     cv::Mat perViewErrors;// 每一张棋盘的重投影误差
145     double err = cv::calibrateCamera(   object_points, image_points, image_size, intrinsic_matrix, distortion_coeffs,
146                                         rvecs, tvecs, cv::noArray(), cv::noArray(), perViewErrors/*,  // 我劝你不会用这些参数,就不要设置了
147                                         cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_PRINCIPAL_POINT*/);
148     
149     double fx = intrinsic_matrix.at<double>(0, 0);
150     double fy = intrinsic_matrix.at<double>(1, 1);
151     double cx = intrinsic_matrix.at<double>(0, 2);
152     double cy = intrinsic_matrix.at<double>(1, 2);
153 
154     // <2.1> 验证 perViewErrors
155     cout << "err = " << endl;
156     cout << err << endl;
157     cout << "sum(perViewErrors) / rows =" << endl;
158     double s1 = 0.;
159     for (size_t i = 0; i < perViewErrors.rows; i++)
160     {
161         s1 += std::pow(perViewErrors.at<double>((int)i, 0), 2);
162     }
163     cout << std::sqrt(s1 / perViewErrors.rows) << endl;
164     cout << "---------------" << endl;
165 
166     // <2.2> 验证重投影误差
167     cout << "每张棋盘重投影误差【calibrateCamera输出】:" << endl;
168     cout << perViewErrors.t() << endl;
169 
170     // 遍历所有棋盘(利用张正友棋盘法估算的外参)
171     cout << "每张棋盘重投影误差【利用外参计算】:" << endl;
172     for (size_t k = 0; k < object_points.size(); k++)
173     {
174         vector<Point2f> pts2d1; // [u' v'] 重投影后的点
175         double perViewError1 = 0.;
176         projectPoints(Mat(object_points[k]), rvecs[k], tvecs[k], intrinsic_matrix, distortion_coeffs, pts2d1);
177         perViewError1 = norm(Mat(image_points[k]), Mat(pts2d1), NORM_L2);
178         cout << std::sqrt(perViewError1 * perViewError1/ (int)pts2d1.size()) << " ";
179        // // 外参
180        // Mat rvec = rvecs[k];
181        // Mat t = tvecs[k];
182        // Mat R;
183        // Rodrigues(rvec, R);
184        // // T = (R|t)
185        // cv::Affine3d T(
186        //     cv::Affine3d::Mat3( R.at<double>(0, 0), R.at<double>(0, 1), R.at<double>(0, 2),
187        //                         R.at<double>(1, 0), R.at<double>(1, 1), R.at<double>(1, 2),
188        //                         R.at<double>(2, 0), R.at<double>(2, 1), R.at<double>(2, 2)),
189        //     cv::Affine3d::Vec3(t.at<double>(0, 0), t.at<double>(1, 0), t.at<double>(2, 0)));
190 
191        ///* cout << T.rotation() << endl;
192        // cout << T.translation() << endl;*/
193 
194        // // 遍历一张棋盘中所有点
195        // for (size_t i = 0; i < object_points[k].size(); i++)
196        // {
197        //     Point3d ptR3d = (Point3d)object_points[k][i]; 
198        //     Point2d ptL2d = (Point2d) image_points[k][i];
199 
200        //     // <1> 仿射
201        //     Point3d ptL3d = T * ptR3d;
202 
203        //     // <2> 投影 + 畸变矫正
204        //     Point2d ptL2d_;
205        //     CameraToPixel(ptL3d, intrinsic_matrix, ptL2d_);
206        //     undistortPoints();
207 
208        //     
209        // }
210        //cv::projectPoints
211     }
212 
213     // <2.3> 外参数验证
214     double sum_errs_t = 0.;
215     Mat rvec1, t1, inliers;
216     // 遍历所有棋盘
217     cout << endl;
218     cout << "每张棋盘重投影误差【非线性优化】:" << endl;
219     for (size_t k = 0; k < object_points.size(); k++)
220     {
221         // 标定的外参数t2
222         Mat t2 = tvecs[k];
223         //cout << "t2 = " << t2.t() << " ";
224 
225         // pnp 
226         // 注意调参数,如果误差阈值卡得太严格,尽量大,反正迭代会让误差收敛,可能需要在GUI做一个手动设置
227         // 当你误差设得很大的时候,此时,你需要将迭代次数射得大一些,这样才能确保得到一个好的姿态
228         // 置信度?
229         solvePnPRansac(object_points[k], image_points[k], intrinsic_matrix, distortion_coeffs, rvec1, t1, false, 300, 30.0, 0.99, inliers); // 为什么内点为空? 因为阈值太低
230        /* if (inliers.rows < ((int)object_points[k].size() / 3))
231         {
232             cout << " 内点比例:"<< (float)inliers.rows / (int)object_points[k].size() << endl;
233             return -1;
234         }*/
235         // TermCriteria::EPS = FLT_EPSILON,这个精度不可能达到,所以还是迭代300次
236         // TermCriteria::COUNT = 300
237         // + 表示或的关系不是与
238         solvePnPRefineLM(object_points[k], image_points[k], intrinsic_matrix, distortion_coeffs, rvec1, t1, TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 300, FLT_EPSILON));
239         //cout << "t1 = " << t1.t() << " ";
240 
241         sum_errs_t += norm(t1, t2, NORM_L2);
242         //cout << norm(t1, t2, NORM_L2) << endl;// 梯度下降后的外参数t1 与 张正友外参数差距
243         
244         // 2D点重投影误差(利用solvePnPRefineLM 求解出的外参数)
245         vector<Point2f> pts2d2; // [u' v'] 重投影后的点
246         double perViewError2 = 0.;
247         projectPoints(Mat(object_points[k]), rvec1, t1, intrinsic_matrix, distortion_coeffs, pts2d2);
248         perViewError2 = norm(Mat(image_points[k]), Mat(pts2d2), NORM_L2);
249         cout << std::sqrt(perViewError2 * perViewError2 / (int)pts2d2.size()) << " ";
250     }
251     cout << endl;
252     cout << "内点数量 =" << inliers.rows << endl;
253     cout << sum_errs_t / (int)object_points.size() << endl;
254 
255 
256     // undistortPoints
257     /*cvProjectPoints2*/
258     return 0;
259 }

 

posted @ 2020-04-26 20:03  佚名12  阅读(91)  评论(0编辑  收藏  举报