PIMPL
全称为Pointer to Implementation
,其在cppreference上的解释为:
“Pointer to implementation” or “pImpl” is a C++ programming technique that removes implementation details of a class from its object representation by placing them in a separate class, accessed through an opaque pointer
如下是本人最近正在实现的一个KalmanFilter封装项目部分tree:
│ └── Interface
│ ├── Kalmer.h
│ ├── KalmImplGaGa
│ │ ├── KalmanImplKf.h
│ │ └── KalmanImplUkf.h
│ └── KalmImpl.h
├── LICENSE
├── main.cpp
├── readme.md
└── src
└── Kalm.cpp
#ifndef __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMER_H_
#define __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMER_H_
#include "Eigen/Core"
#include "Define/basic_type.h"
#include "Define/config.h"
#include <memory>
class KalmImpl;
class KalmImplKf;
class KalmImplUkf;
class Kalmer
{
private:
/* data */
public:
Kalmer(/* args */);
~Kalmer();
RC init(cf_ic &&);
RC predict();
RC update();
private:
std::shared_ptr<KalmImpl> sp_kalman_impl_;
};
#endif // __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMER_H_
Kalmer类在此项目中作为最外层接口,当然,也可以将类名前加大写字母I,表示接口(Interface),在此不做赘述。
通过上述代码,可以发现Kalmer
类包含一个智能指针指向KalmImpl
类,其为Kalmer的实现类。
你以为Kalmer
的成员函数init
predict
update
三个函数中包含具体实现吗?你以为这三个函数包含了矩阵的眼花缭乱的操作吗?
其实不是这样的。请看下面代码:
#include "Interface/Kalmer.h"
#include "Interface/KalmImpl.h"
#include "Interface/KalmImplGaGa/KalmanImplKf.h"
#include "Interface/KalmImplGaGa/KalmanImplUkf.h"
Kalmer::Kalmer(/* args */)
{
}
Kalmer::~Kalmer()
{
}
RC Kalmer::init(cf_ic && _init_config)
{
if (_init_config.kal_type_ == kalman_types::NORMAL_KF_)
{
sp_kalman_impl_ = std::make_shared<KalmImplKf>();
return RC::RETURN_OK;
}
if (_init_config.kal_type_ == kalman_types::UNSCENTED_KF_)
{
sp_kalman_impl_ = std::make_shared<KalmImplUkf>();
return RC::RETURN_OK;
}
printf("%s unknown error\n", __FUNCTION__);
return RC::RETURN_ERROR;
}
RC Kalmer::predict()
{
return sp_kalman_impl_->predict();
}
RC Kalmer::update()
{
return sp_kalman_impl_->update();
}
可以发现Kalmer
类的实现cpp中,其三个public函数并未有实质性算法代码
- init用于通过config配置文件初始化其算法指针,至于为什么
sp_kalman_impl
明明是KalmImpl
类,为何可以被KalmImplKf和KalmImplUkf实例化,这涉及到继承的问题,后面贴上两个子类的实现cpp文件,大家就一目了然了。 - predict调用成员指针的predict方法
- update调用成员指针的update方法
同样可以发现:真正的核心算法并没有在这个cpp文件中实现,当然如果你想的话,也可以在这个cpp文件中实现。而后我们通过CMakelist编译出library,再配上光秃秃的Kalmer.h就可以提供给算法的使用者了,这种情况下可以最小化暴露我们算法的实现。
当前这个工程不能很完美的掩盖算法的实现细节,因为它暴露了3个public函数给使用者,可是卡尔曼滤波至少得有2个方法。
通常一个算法接口应当只包含一个public方法,其名称通常为run
或 process
,当然如果你想,也可以命名为gagaga
(只要你的下游使用者能明白就行)。
再看看KalmImpl
和他的子类KalmanImplKf
的实现吧
#ifndef __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPL_H_
#define __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPL_H_
#include "Interface/Kalmer.h"
class KalmImpl
{
private:
/* data */
public:
KalmImpl(cf_ic &&_init_config) : sp_state_(std::make_shared<kalman_matrix_state_t>(_init_config)),
sp_confg_(std::make_shared<matrix_size_config__t>(_init_config)){};
virtual ~KalmImpl() = default;
virtual RC predict() = 0;
virtual RC update() = 0;
RC set_measu(Eigen::VectorXf &&_measu) {
// TODO: check size
if(_measu.size() != sp_confg_->msu_size_)
{
printf("KalmImpl::set_measu check error\n");
return RC::RETURN_ERROR;
}
sp_state_->v_measu_ = _measu;
return RC::RETURN_OK;
};
RC set_P(Eigen::MatrixXf &&_P){
sp_state_->m_P_ = _P;
}
RC set_Q(Eigen::MatrixXf &&_Q)
{
sp_state_->m_Q_ = _Q;
}
RC set_R(Eigen::MatrixXf &&_R)
{
sp_state_->m_R_ = _R;
}
RC set_F(Eigen::MatrixXf &&_F)
{
sp_state_->m_F_ = _F;
}
protected:
using kalman_matrix_state_t = km_s;
using matrix_size_config__t = cf_ic;
std::shared_ptr<kalman_matrix_state_t> sp_state_;
std::shared_ptr<matrix_size_config__t> sp_confg_;
};
#endif // __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPL_H_
可以看到父类KalmImpl.h中将predict和update两个函数声明为纯虚函数,其目的时让子类KalmImplKf和KalmImplUkf去继承和实现。
其被直接实现的几个函数可以被子类所用,因为卡尔曼滤波和无迹卡尔曼滤波本质时一样的,都需要去设置协方差矩阵、噪音矩阵、转换矩阵等。关于卡尔曼滤波的原理将会在后续的博文中详细阐述。
#ifndef __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPLGAGA_KALMANIMPLKF_H_
#define __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPLGAGA_KALMANIMPLKF_H_
#include "../KalmImpl.h"
class KalmImplKf : public KalmImpl
{
public:
KalmImplKf(cf_ic &&_init_config) : KalmImpl(std::move(_init_config)) {}
virtual RC predict()
{
auto s = sp_state_;
s->v_state_ = s->m_F_ * s->v_state_;
s->m_P_ = s->m_F_ * s->v_state_ * s->m_F_.transpose() + s->m_Q_;
}
virtual RC update()
{
auto s = sp_state_;
s->m_K_ = s->m_P_ * s->m_H_.transpose() * ((s->m_H_ * s->m_P_ * s->m_H_.transpose() + s->m_R_).inverse());
s->v_state_ = s->v_state_ + s->m_K_ * (s->v_measu_ - s->m_H_ * s->v_state_);
s->m_P_ = s->m_P_ - s->m_K_ * s->m_H_ * s->m_P_;
}
virtual ~KalmImplKf() override{}
};
#endif // __WORKSPACE_KALMANFILTER_INC_INTERFACE_KALMIMPLGAGA_KALMANIMPLKF_H_
上述代码为卡尔曼滤波的实现,可以看到直到这里,算法才真正的被实现。加粗的五个公式为卡尔曼滤波的矩阵运算公式,其矩阵形式引用了Eigen。
至于UKF的实现我还没有写完,具体项目地址。
画外音:本人硕士写的YOLOV3的论文是怎么跟知乎的身体健康类问题挂上边的,具体地址。
发表回复