花一点时间深入C++吧(2)
上次说了计算机视觉的工程实践(主要是在opencv里面)中,为什么需要“类”这样东西,以及接口的作用。现在可以了解一般情况下,一个“类”是怎样写出来的。
这次就不举kalman的例子,来换一个常见的例子,PnP问题(perspective n point problem)
一般而言,你可以发现两个文件,一个是PnPProblem.h,而另一个是PnPProblem.cpp
如果你在完成老板布置给你的任务时,需要编写一个类去解决一类问题(这个说法本身也很巧秒),就要向你的工程添加一个h文件以及一个cpp文件
先看一下PnPProblem.h,可以发现,
#ifndef PNPPROBLEM_H_ #define PNPPROBLEM_H_ #include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include "Mesh.h" #include "ModelRegistration.h" class PnPProblem { //............ } //......... #endif /* PNPPROBLEM_H_ */
这样一个框架结构,开头是#ifndef,然后#define,末尾是#endif(似乎一切都很简单)
再来看class PnPProblem,
class PnPProblem { public: explicit PnPProblem(const double param[]); // custom constructor virtual ~PnPProblem(); bool backproject2DPoint(const Mesh *mesh, const cv::Point2f &point2d, cv::Point3f &point3d); bool intersect_MollerTrumbore(Ray &R, Triangle &T, double *out); std::vector<cv::Point2f> verify_points(Mesh *mesh); cv::Point2f backproject3DPoint(const cv::Point3f &point3d); bool estimatePose(const std::vector<cv::Point3f> &list_points3d, const std::vector<cv::Point2f> &list_points2d, int flags); void estimatePoseRANSAC( const std::vector<cv::Point3f> &list_points3d, const std::vector<cv::Point2f> &list_points2d, int flags, cv::Mat &inliers, int iterationsCount, float reprojectionError, double confidence ); cv::Mat get_A_matrix() const { return _A_matrix; } cv::Mat get_R_matrix() const { return _R_matrix; } cv::Mat get_t_matrix() const { return _t_matrix; } cv::Mat get_P_matrix() const { return _P_matrix; } void set_P_matrix( const cv::Mat &R_matrix, const cv::Mat &t_matrix); private: /** The calibration matrix */ cv::Mat _A_matrix; /** The computed rotation matrix */ cv::Mat _R_matrix; /** The computed translation matrix */ cv::Mat _t_matrix; /** The computed projection matrix */ cv::Mat _P_matrix; };
从这段代码中,可以学习到一些知识:
(1)public存放接口函数,private存放需要维护的变量
(2)但凡创建一个对象,就需要一个构造函数。但凡销毁一个对象,就要有构析函数
(3)h文件只是声明这个类,以及该类的接口函数,然而具体实现存放与cpp文件中
(4)大量的注释,解释所要维护变量的意义,以及接口函数的意义,注释方便设计者维护自己的类,也方便使用者使用这些函数
除此之外,还有“类外函数”的概念(C++primer称为相关的非成员函数),比如
// Functions for Möller–Trumbore intersection algorithm cv::Point3f CROSS(cv::Point3f v1, cv::Point3f v2); double DOT(cv::Point3f v1, cv::Point3f v2); cv::Point3f SUB(cv::Point3f v1, cv::Point3f v2);
定义了矢量的点积和叉积。为什么有类外函数呢?其实也好理解,类外,就是不太重要的意思。像点积叉积这些操作都很基础,就不必放到类内。并且,类外的函数不能引用private变量。总之,写一个h文件似乎不太难,格式到位就行,而cpp文件的编写是重头戏
先看cpp文件构造以及构析类PnPProblem的代码,
PnPProblem::PnPProblem(const double params[]) { _A_matrix = cv::Mat::zeros(3, 3, CV_64FC1); // intrinsic camera parameters _A_matrix.at<double>(0, 0) = params[0]; // [ fx 0 cx ] _A_matrix.at<double>(1, 1) = params[1]; // [ 0 fy cy ] _A_matrix.at<double>(0, 2) = params[2]; // [ 0 0 1 ] _A_matrix.at<double>(1, 2) = params[3]; _A_matrix.at<double>(2, 2) = 1; _R_matrix = cv::Mat::zeros(3, 3, CV_64FC1); // rotation matrix _t_matrix = cv::Mat::zeros(3, 1, CV_64FC1); // translation matrix _P_matrix = cv::Mat::zeros(3, 4, CV_64FC1); // rotation-translation matrix } PnPProblem::~PnPProblem() { // TODO Auto-generated destructor stub }
这段代码可以学到几样东西:
(1)注意 :: 这个定义域符号
(2)在h文件中,注意explicit(显式构造)的意义,说明类PnPProblem不能默认构造,必须经过构造函数才能初始化
然后我们随便看一个接口函数,
// Estimate the pose given a list of 2D/3D correspondences and the method to use bool PnPProblem::estimatePose( const std::vector<cv::Point3f> &list_points3d, const std::vector<cv::Point2f> &list_points2d, int flags) { cv::Mat distCoeffs = cv::Mat::zeros(4, 1, CV_64FC1); cv::Mat rvec = cv::Mat::zeros(3, 1, CV_64FC1); cv::Mat tvec = cv::Mat::zeros(3, 1, CV_64FC1); bool useExtrinsicGuess = false; // Pose estimation bool correspondence = cv::solvePnP( list_points3d, list_points2d, _A_matrix, distCoeffs, rvec, tvec, useExtrinsicGuess, flags); // Transforms Rotation Vector to Matrix Rodrigues(rvec,_R_matrix); _t_matrix = tvec; // Set projection matrix this->set_P_matrix(_R_matrix, _t_matrix); return correspondence; }最后,简单分析了PnPProblem,可以知道一般的类的编写方法(没有用什么高级技巧,很简单很实用)
还有一个东西比较常用,就是this指针,这个概念很好理解,就不多言了。