花一点时间深入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指针,这个概念很好理解,就不多言了。