Ceres-Solver学习笔记(6)

2023-05-16

溯洄从之,道阻且长
建模最小化二乘问题
Ceres有两个组成部分,一个是建模API,它提供了一组丰富的工具,可以在一段时间内构造一个优化问题,另一个是求解程序API,控制最小化算法。这里我们只讨论建模。

class CostFunction

class CostFunction {
 public:
  virtual bool Evaluate(double const* const* parameters,
                        double* residuals,
                        double** jacobians) = 0;
  const vector<int32>& parameter_block_sizes();
  int num_residuals() const;

 protected:
  vector<int32>* mutable_parameter_block_sizes();
  void set_num_residuals(int num_residuals);
};

从这个类继承的用户代码将使用相应的访问器来设置CostFunction::parameter_block_sizes_ 和CostFunction::num_residuals_ 。

bool CostFunction::Evaluate(double const *const *parameters, double *residuals, double **jacobians)

计算残差和Jacobain矩阵
parameters 参数是指向数组的指针数组,其中包含各种参数块。参数的元素数量与CostFunction::parameter_block_sizes_相同,参数块的顺序与s CostFunction::parameter_block_sizes_相同。
residuals 是num_residuals_大小的数组。
residuals 是ostFunction::parameter_block_sizes_大小的数组,包含指向每个参数块对应的Jacobain矩阵的指针。Jacobain矩阵与CostFunction::parameter_block_sizes_的顺序是相同的。jacobians[i]是一个数组包含CostFunction::num_residuals_ x CostFunction::parameter_block_sizes_ [i]元素,每个jacobians矩阵都以行优先顺序存储,例如:
jacobians[i][r * parameter_block_size_[i] + c]= residual[r]parameters[i][c]
如果jacobians 是 NULL,就不返回导数,当只计算cost时才会这样。如果jacobians[i]是空的,那么jacobians矩阵对应的第i个参数块就不能返回,这就是当一个参数块被标记为常量时的情况。
注意,返回值表示残差 和/或 jacobians 的计算是否成功。
例如 这可以用来传递 雅可比矩阵中数值计算失败。

SizedCostFunction
如果参数块的大小和惨差响亮的大小在编译时是已知的,在这些值可以指定为模板参数时可以使用SizeCostFunction,用户只需要实现 CostFunction::Evaluate().

template<int kNumResiduals,
         int N0 = 0, int N1 = 0, int N2 = 0, int N3 = 0, int N4 = 0,
         int N5 = 0, int N6 = 0, int N7 = 0, int N8 = 0, int N9 = 0>
class SizedCostFunction : public CostFunction {
 public:
  virtual bool Evaluate(double const* const* parameters,
                        double* residuals,
                        double** jacobians) const = 0;
};

AutoDiffCostFunction

定义一个CostFunction 或 SizedCostFunction可能是一个繁琐且容易出错的过程,尤其是在计算导数的时候。为此,Ceres提供了 automatic differentiation.。

template <typename CostFunctor,
       int kNumResiduals,  // Number of residuals, or ceres::DYNAMIC.
       int N0,       // Number of parameters in block 0.
       int N1 = 0,   // Number of parameters in block 1.
       int N2 = 0,   // Number of parameters in block 2.
       int N3 = 0,   // Number of parameters in block 3.
       int N4 = 0,   // Number of parameters in block 4.
       int N5 = 0,   // Number of parameters in block 5.
       int N6 = 0,   // Number of parameters in block 6.
       int N7 = 0,   // Number of parameters in block 7.
       int N8 = 0,   // Number of parameters in block 8.
       int N9 = 0>   // Number of parameters in block 9.
class AutoDiffCostFunction : public
SizedCostFunction<kNumResiduals, N0, N1, N2, N3, N4, N5, N6, N7, N8, N9> {
 public:
  explicit AutoDiffCostFunction(CostFunctor* functor);
  // Ignore the template parameter kNumResiduals and use
  // num_residuals instead.
  AutoDiffCostFunction(CostFunctor* functor, int num_residuals);
};

要获得一个自动微分的成本函数,您必须定义一个带有模板操作符()(函数)的类,该函数以模板参数T的形式计算成本函数。

注意,在声明 operator() 输入参数x和y是第一位的,并作为T类型常量数组指针传递 .如果有三个输入参数,然后第三个输入跟在y后面。输出总是最后一个参数,和也是一个指向数组的指针。
AutoDiffCostFunction支持在运行时决定残差数量。

DynamicAutoDiffCostFunction

AutoDiffCostFunction 需要在编译时知道参数块的数量和它们的大小。它也有10个参数块的上限。在许多应用程序中,这是不够的。如贝塞尔曲线拟合,神经网络训练等。

template <typename CostFunctor, int Stride = 4>
class DynamicAutoDiffCostFunction : public CostFunction {
};

在这种情况下可以使用DynamicAutoDiffCostFunction ,像 AutoDiffCostFunction一样,用户必须定义模板函数,但是函数的特点稍微不同,我们希望 cost functors 的接口是:

struct MyCostFunctor {
  template<typename T>
  bool operator()(T const* const* parameters, T* residuals) const {
  }
}

由于参数的大小是在运行时确定的,所以在创建dynamic autodiff cost function之后,您还必须指定大小。例如:

DynamicAutoDiffCostFunction<MyCostFunctor, 4>* cost_function =
  new DynamicAutoDiffCostFunction<MyCostFunctor, 4>(
    new MyCostFunctor());
cost_function->AddParameterBlock(5);
cost_function->AddParameterBlock(10);
cost_function->SetNumResiduals(21);

在底层,实现对成本函数进行多次评估,每次计算一小组的导数(默认情况下4个,由 Stride 模板参数控制)。传递的大小有一个性能权衡;较小的尺寸更高效,但会导致更多的传递,而更大的跨步长度可以破坏缓存的位置,同时减少成本函数的传递次数。最优值取决于各种参数块的数量和大小。
作为一个经验法则,试着用AutoDiffCostFunction 在使用DynamicAutoDiffCostFunction之前。

DynamicAutoDiffCostFunction
在不能使用模板定义的时候使用。

template <typename CostFunctor,
          NumericDiffMethodType method = CENTRAL,
          int kNumResiduals,  // Number of residuals, or ceres::DYNAMIC.
          int N0,       // Number of parameters in block 0.
          int N1 = 0,   // Number of parameters in block 1.
          int N2 = 0,   // Number of parameters in block 2.
          int N3 = 0,   // Number of parameters in block 3.
          int N4 = 0,   // Number of parameters in block 4.
          int N5 = 0,   // Number of parameters in block 5.
          int N6 = 0,   // Number of parameters in block 6.
          int N7 = 0,   // Number of parameters in block 7.
          int N8 = 0,   // Number of parameters in block 8.
          int N9 = 0>   // Number of parameters in block 9.
class NumericDiffCostFunction : public
SizedCostFunction<kNumResiduals, N0, N1, N2, N3, N4, N5, N6, N7, N8, N9> {
};

NumericDiffCostFunction也支持运行时决定残差数量。
Numeric Differentiation & LocalParameterization
如果您的cost function 依赖于一个参数块,它必须位于一个manifold上,并且不能对函数的值进行求值,因为参数块的值不是在manifold上,那么您可能会有问题数值微分这些函数。
这是因为Ceres的数值微分是通过扰动一个cost functor所依赖的参数块的个别坐标来实现的。这个时候,我们假设参数块位于欧氏空间中,忽略它们所处的manifold 的结构,因此有些扰动可能不会位于与参数块对应的manifold中。
例如,考虑一个4维的参数块,它被解释为一个单元四元数。扰乱这个参数块的坐标将会违反参数块的单位范数属性。
要解决这个问题,NumericDiffCostFunction 需要计算出与每个参数块相关联的LocalParameterization ,并且只在每个参数块的局部切线空间中产生扰动。
就目前而言,这并不是一个足够严重的问题,必须改变数字扩散函数的API。而且,在大多数情况下,在函数中使用它之前,将一个点投射到多个manifold上是相对简单的。例如,在四元数的情况下,在使用它之前对4个向量进行规范化会奏效。

由于各种原因,包括与遗留代码的兼容性, NumericDiffCostFunction 也可以将 CostFunction对象作为输入。

DynamicNumericDiffCostFunction

就像 AutoDiffCostFunction 一样,NumericDiffCostFunction需要在编译时知道参数块的数量和它们的大小。它也有10个参数块的上限。在许多应用程序中,这是不够的。

template <typename CostFunctor, NumericDiffMethodType method = CENTRAL>
class DynamicNumericDiffCostFunction : public CostFunction {
};

就像NumericDiffCostFunction一样,用户必须定义一个functor。 cost functors的预期接口是:

struct MyCostFunctor {
  bool operator()(double const* const* parameters, double* residuals) const {
  }
}

由于参数的大小是在运行时完成的,所以您还必须在创建dynamic numeric diff 成本函数之后指定大小。例如:

DynamicNumericDiffCostFunction<MyCostFunctor>* cost_function =
  new DynamicNumericDiffCostFunction<MyCostFunctor>(new MyCostFunctor);
cost_function->AddParameterBlock(5);
cost_function->AddParameterBlock(10);
cost_function->SetNumResiduals(21);

CostFunctionToFunctor

CostFunctionToFunctor是一个适配器类,它允许用户在模板函数中使用CostFunction对象,用于自动微分。这允许用户无缝地混合analytic, numeric and automatic differentiation。

class IntrinsicProjection : public SizedCostFunction<2, 5, 3> {
  public:
    IntrinsicProjection(const double* observation);
    virtual bool Evaluate(double const* const* parameters,
                          double* residuals,
                          double** jacobians) const;
};

这是一个CostFunction,它实现了局部坐标系中的点的投影到它的图像平面上,并将观察到的点减去投影。它可以计算它的残值,通过 analytic 或numerical differentiation可以计算出它的雅克比。

现在,我们想用相机外参例如旋转和平移来构成这个 CostFunction的作用。假设我们有一个模板函数

template<typename T>
void RotateAndTranslatePoint(const T* rotation,
                             const T* translation,
                             const T* point,
                             T* result);

然后如下:

struct CameraProjection {
  CameraProjection(double* observation)
  : intrinsic_projection_(new IntrinsicProjection(observation)) {
  }

  template <typename T>
  bool operator()(const T* rotation,
                  const T* translation,
                  const T* intrinsics,
                  const T* point,
                  T* residual) const {
    T transformed_point[3];
    RotateAndTranslatePoint(rotation, translation, point, transformed_point);

    // Note that we call intrinsic_projection_, just like it was
    // any other templated functor.
    return intrinsic_projection_(intrinsics, transformed_point, residual);
  }

 private:
  CostFunctionToFunctor<2,5,3> intrinsic_projection_;
};

注意, CostFunctionToFunctor接受传递给构造函数的 CostFunction的所有权。

在上面的例子中,我们假设 IntrinsicProjection是一个能够评估值和导数的CostFunction 。假设,如果不是这样,而IntrinsicProjection的定义是这样的:

struct IntrinsicProjection
  IntrinsicProjection(const double* observation) {
    observation_[0] = observation[0];
    observation_[1] = observation[1];
  }

  bool operator()(const double* calibration,
                  const double* point,
                  double* residuals) {
    double projection[2];
    ThirdPartyProjectionFunction(calibration, point, projection);
    residuals[0] = observation_[0] - projection[0];
    residuals[1] = observation_[1] - projection[1];
    return true;
  }
 double observation_[2];
};

这里ThirdPartyProjectionFunction是一些第三方库函数,我们无法控制。这个函数可以计算它的值,我们想用数值微分来计算它的导数。在这种情况下,我们可以NumericDiffCostFunction 和 CostFunctionToFunctor的组合来完成任务。

struct CameraProjection {
  CameraProjection(double* observation)
    intrinsic_projection_(
      new NumericDiffCostFunction<IntrinsicProjection, CENTRAL, 2, 5, 3>(
        new IntrinsicProjection(observation)) {
  }

  template <typename T>
  bool operator()(const T* rotation,
                  const T* translation,
                  const T* intrinsics,
                  const T* point,
                  T* residuals) const {
    T transformed_point[3];
    RotateAndTranslatePoint(rotation, translation, point, transformed_point);
    return intrinsic_projection_(intrinsics, transformed_point, residual);
  }

 private:
  CostFunctionToFunctor<2,5,3> intrinsic_projection_;
};

DynamicCostFunctionToFunctor

DynamicCostFunctionToFunctor提供相同的功能作为CostFunctionToFunctor情况参数向量的数量和大小和残差是在编译时不知道。DynamicCostFunctionToFunctor匹配提供的API与DynamicAutoDiffCostFunction相同,即它提供了一个模板化functor有如下的形式:

template<typename T>
bool operator()(T const* const* parameters, T* residuals) const;

与 CostFunctionToFunctor的例子类似,让我们假设

class IntrinsicProjection : public CostFunction {
  public:
    IntrinsicProjection(const double* observation);
    virtual bool Evaluate(double const* const* parameters,
                          double* residuals,
                          double** jacobians) const;
};

是一个CostFunction ,它将其局部坐标系中的一个点投射到它的图像平面上,并将它从观察到的点减去投影。
用模板化函数使用这个 CostFunction将会是这样的:

struct CameraProjection {
  CameraProjection(double* observation)
      : intrinsic_projection_(new IntrinsicProjection(observation)) {
  }

  template <typename T>
  bool operator()(T const* const* parameters,
                  T* residual) const {
    const T* rotation = parameters[0];
    const T* translation = parameters[1];
    const T* intrinsics = parameters[2];
    const T* point = parameters[3];

    T transformed_point[3];
    RotateAndTranslatePoint(rotation, translation, point, transformed_point);

    const T* projection_parameters[2];
    projection_parameters[0] = intrinsics;
    projection_parameters[1] = transformed_point;
    return intrinsic_projection_(projection_parameters, residual);
  }

 private:
  DynamicCostFunctionToFunctor intrinsic_projection_;
};

ConditionedCostFunction

这个类允许您对打包的cost function的残差值应用不同的条件。一个例子,您有一个现有的成本函数产生N个值,但你想要的总成本不是这些值的平方和,也许你想要应用不同的缩放值,改变他们对成本的贡献。

//  my_cost_function produces N residuals
CostFunction* my_cost_function = ...
CHECK_EQ(N, my_cost_function->num_residuals());
vector<CostFunction*> conditioners;

//  Make N 1x1 cost functions (1 parameter, 1 residual)
CostFunction* f_1 = ...
conditioners.push_back(f_1);

CostFunction* f_N = ...
conditioners.push_back(f_N);
ConditionedCostFunction* ccf =
  new ConditionedCostFunction(my_cost_function, conditioners);

现在 ccf 的 residual[i] (i=0..N-1) 将通过第i个条件.

ccf_residual[i] = f_i(my_cost_function_residual[i])

Jacobain也会被适当的改变。

GradientChecker

这个类将 cost function与使用有限微分估计的导数进行比较。它是用于单元测试的工具,它比solver选项中的check_gradients选项提供了更细粒度的控制。

强制执行的条件是:

i,j:JijJijmaxij(JijJij)<r

Jij 是由提供的成本函数(由用户)计算的雅可比矩阵乘以局部参数化Jacobian矩阵, Jij 是由有限差分计算的jacobian矩阵乘以局部参数化雅可比矩阵,r是相对精度。

用法:

//  my_cost_function takes two parameter blocks. The first has a local
//  parameterization associated with it.
CostFunction* my_cost_function = ...
LocalParameterization* my_parameterization = ...
NumericDiffOptions numeric_diff_options;

std::vector<LocalParameterization*> local_parameterizations;
local_parameterizations.push_back(my_parameterization);
local_parameterizations.push_back(NULL);

std::vector parameter1;
std::vector parameter2;
// Fill parameter 1 & 2 with test data...

std::vector<double*> parameter_blocks;
parameter_blocks.push_back(parameter1.data());
parameter_blocks.push_back(parameter2.data());

GradientChecker gradient_checker(my_cost_function,
    local_parameterizations, numeric_diff_options);
GradientCheckResults results;
if (!gradient_checker.Probe(parameter_blocks.data(), 1e-9, &results) {
  LOG(ERROR) << "An error has occurred:\n" << results.error_log;
}

GradientChecker

class NormalPrior: public CostFunction {
 public:
  // Check that the number of rows in the vector b are the same as the
  // number of columns in the matrix A, crash otherwise.
  NormalPrior(const Matrix& A, const Vector& b);

  virtual bool Evaluate(double const* const* parameters,
                        double* residuals,
                        double** jacobians) const;
 };

实现 cost function形如:

cost(x)=||A(xb)||2

矩阵A和向量b是固定的,x是变量。如果用户对实现如下形式的成本函数感兴趣
cost(x)=(xμ)TS1(xμ)

μ是一个向量,S是协方差矩阵,然后, A=S1/2 ,A是协方差矩阵的平方根,也就是所谓的stiffness矩阵。然而对A的形状没有任何限制。如果协方差矩阵是秩不足的情况就会是矩形。

LossFunction

对于最小的平方问题,最小化可能会遇到包含异常值的输入项,也就是,完全虚假的测量,使用损失函数降低它们的影响是很重要的。

class LossFunction {
 public:
  virtual void Evaluate(double s, double out[3]) const = 0;
};

他的关键方法是 LossFunction::Evaluate(),它给出一个非负的标量,计算

out=[ρ(s),ρ(s),ρ(s)]

这里的约定是,一项对成本函数的贡献是 12ρ(s) 给出的, s=|fi|2 。s是负值是一个错误,并且不需要实现这种情况。

最明智的选择:

ρ(0)ρ(0)ρ(s)ρ(s)=0=1<1 in the outlier region<0 in the outlier region

实例
Ceres包含许多预定义的损失函数。为了简单起见,我们描述了它们的未缩放版本。下图演示了图形的形状。更多的细节可以在include/ceres/loss_function.h中找到。
这里写图片描述

class TrivialLoss

ρ(s)=s

class HuberLoss

ρ(s)={s2s1s1s>1

class SoftLOneLoss

ρ(s)=2(1+s1)

class ArctanLoss

ρ(s)=arctan(s)

class TolerantLoss

ρ(s,a,b)=blog(1+e(sa)/b)blog(1+ea/b)

class ComposedLoss
给定两个损失函数f和g,实现损失函数h(s)=f(g(s))。

class ComposedLoss : public LossFunction {
 public:
  explicit ComposedLoss(const LossFunction* f,
                        Ownership ownership_f,
                        const LossFunction* g,
                        Ownership ownership_g);
};

class LossFunctionWrapper
有时,在优化问题出现后,我们希望改变损失函数的scale。这个模板化类允许用户实现一个损失函数,在构造优化问题之后,它的scale可以发生变化,

Problem problem;

// Add parameter blocks

CostFunction* cost_function =
    new AutoDiffCostFunction < UW_Camera_Mapper, 2, 9, 3>(
        new UW_Camera_Mapper(feature_x, feature_y));

LossFunctionWrapper* loss_function(new HuberLoss(1.0), TAKE_OWNERSHIP);
problem.AddResidualBlock(cost_function, loss_function, parameters);

Solver::Options options;
Solver::Summary summary;
Solve(options, &problem, &summary);

loss_function->Reset(new HuberLoss(1.0), TAKE_OWNERSHIP);
Solve(options, &problem, &summary);

理论
让我们考虑单个问题和单个参数块的问题。

minx12ρ(f2(x))

然后,梯度和高斯-牛顿的Hessian是
g(x)H(x)=ρJ(x)f(x)=J(x)(ρ+2ρf(x)f(x))J(x)

其中涉及f(x)的二阶导数的项被忽略了。注意,H(x)是不确定的,如果 ρf(x)f(x)+12ρ<0 。如果不是这样的话,那么它就有可能重新加权残差和Jacobian矩阵,这样就可以将相应的线性最小二乘问题解决到Gauss-Newton的步骤中。
让 α 是下式的根

12α2αρρf(x)2=0.

定义残差和Jacobian
f̃ (x)J̃ (x)=ρ1αf(x)=ρ(1αf(x)f(x)f(x)2)J(x)

在这个例子中, 2ρf(x)2+ρ0 ,我们限制了 α1ϵ 对一个很小的ϵ。
利用这种简单的重新计算,可以利用雅可比矩阵的非线性最小二乘算法对非线性最小二乘问题进行求解。

LocalParameterization

class LocalParameterization {
 public:
  virtual ~LocalParameterization() {}
  virtual bool Plus(const double* x,
                    const double* delta,
                    double* x_plus_delta) const = 0;
  virtual bool ComputeJacobian(const double* x, double* jacobian) const = 0;
  virtual bool MultiplyByJacobian(const double* x,
                                  const int num_rows,
                                  const double* global_matrix,
                                  double* local_matrix) const;
  virtual int GlobalSize() const = 0;
  virtual int LocalSize() const = 0;
};

有时,参数x可能对问题进行过参数化。在这种情况下,最好选择参数化来移除cost的零方向。更一般地说,如果x位于一个比它所嵌入的环境空间小的多的维度上,可以用每个点的切空间参数化来在数字上和计算上得到更有效的优化。
例如,三维空间中的球体是一个二维的manifold,嵌入在三维空间中。在球面上的每一点上,与它相切的平面定义了一个二维的切线空间。对于这个球面上定义的成本函数,给定一个点x,在这个点向法线方向移动是没有用的。因此,在球面上对点进行参数化的一个更好的方法是在球面点上的切线空间中对二维向量x进行优化,,然后“移动”到点x+Δx,其中移动操作涉及到投影到球面上。这样做可以消除优化中的冗余维度,使其在数字上更加鲁棒和高效。

一般来说,我们可以定义一个函数

x=(x,Δx),

x′和x具有相同的大小,Δx小于或等于x的大小,函数 ⊞概括了向量加法的定义。因此它满足了这个等式
(x,0)=x,x.

LocalParameterization的实例实现了 ⊞操作和它相对于Δx的微分( Δx=0 ).

LocalParameterization::GlobalSize()

参数块x存在的环境空间的维数。

LocalParameterization::LocalSize()

Δx 存在的切空间的维数。

LocalParameterization::Plus(const double *x, const double *delta, double *x_plus_delta) const

LocalParameterization::Plus() 实现了⊞(x,Δx).

LocalParameterization::ComputeJacobian(const double *x, double *jacobian) const

计算Jacobian矩阵

J=Δx(x,Δx)Δx=0

以行优先的形式。

MultiplyByJacobian(const double *x, const int num_rows, const double *global_matrix, double *local_matrix) const

local_matrix = global_matrix * jacobian

实例

IdentityParameterizationconst

一个不重要的⊞版本是Δx和x有相同的大小

(x,Δx)=x+Δx

SubsetParameterization

一个有去的情况是x是一个两维向量,用户想要保持地一个坐标不变,

(x,Δx)=x+[01]Δx

SubsetParameterization 通常构造来保持任意部分参数块不变。

QuaternionParameterization

另一个例子发生在SfM问题,当相机旋转用一个四元数参数化,

(x,Δx)=x+[01]Δx

SubsetParameterization 通常构造来保持任意部分参数块不变。只有使更新量正交于定义四元数的向量才会有用。一个方式是让 Δx是一个3维向量,定义⊞
(x,Δx)=[cos(|Δx|),sin(|Δx|)|Δx|Δx]x

右边这两个4向量的乘法是标准的四元数乘积

EigenQuaternionParameterization

Eigen使用了一种相对于常用四元数不同的内部内存布局,具体地说Eigen存储的形式是 [x, y, z, w] ,实部存在最后,一般来说存在最前。注意,当通过构造函数创建一个Eigen的四元数时,元素接收顺序为w、x、y、z。因为Ceres是在参数块上运行的,它是原始的double指针,这个差别很重要,需要一个不同的参数化,EigenQuaternionParameterization 和QuaternionParameterization 使用相同的更新,但是使用Eigen内部存储元素顺序。

HomogeneousVectorParameterization

在计算机视觉中,homogeneous向量通常被用来表示投影几何中的实体,例如投影空间中的点。其中一个例子是,使用这种过参数化是指那些三角测量不符合条件的点。在这里,使用齐次向量是有利的,而不是一个欧氏向量,因为它可以表示无穷远处的点。

当使用homogeneous向量时,使更新正交于定义homogeneous的n向量是有利的——定义齐次向量哈列兹瑟曼。一种方法是让x等于n -1维向量,然后定义⊞为

(x,Δx)=[sin(0.5|Δx|)|Δx|Δx,cos(0.5|Δx|)]x

右边的两个向量的乘法定义了一个操作,将更新正交于x来保持在球面上。注意,假设x的最后一个元素是homogeneous向量的标量分量。

ProductParameterization

考虑一个关于刚性转换SE(3)的优化问题,它是SO(3)和R3的笛卡尔积。假设您使用四元数来表示旋转,Ceres对它带有一个局部的参数化,而R3则不需要,或IdentityParameterization 参数化。那么我们如何对一个刚体变换构造一个局部参数化?

在一些情况下,一个参数块是很多manifolds的笛卡尔积,然后你有分离的manifolds的本地参数化,ProductParameterizatio可以用来对笛卡尔积构造一个本地参数化。对于刚性变换的情况,比如你有一个大小为7的参数块,当前四个项表示旋转的四元数时,可以构造局部的参数化

ProductParameterization se3_param(new QuaternionParameterization(),
                                  new IdentityTransformation(3));

AutoDiffLocalParameterization

AutoDiffLocalParameterization对于LocalParameterization就像t AutoDiffCostFunction 对于 CostFunction一样,他允许用户定义一个模板functor,实现LocalParameterization::Plus() 操作并用自动微分去实现计算Jacobian。

要获得自动微分本地参数化,必须定义一个类,该类具有一个模板化操作符()用来计算

x=(x,Δx),

例如,Quaternions有一个三维的局部参数化。它的+操作可以被实现为(来自internal/ceres/autodiff_local_parameterization_test.cc)

struct QuaternionPlus {
  template<typename T>
  bool operator()(const T* x, const T* delta, T* x_plus_delta) const {
    const T squared_norm_delta =
        delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];

    T q_delta[4];
    if (squared_norm_delta > 0.0) {
      T norm_delta = sqrt(squared_norm_delta);
      const T sin_delta_by_delta = sin(norm_delta) / norm_delta;
      q_delta[0] = cos(norm_delta);
      q_delta[1] = sin_delta_by_delta * delta[0];
      q_delta[2] = sin_delta_by_delta * delta[1];
      q_delta[3] = sin_delta_by_delta * delta[2];
    } else {
      // We do not just use q_delta = [1,0,0,0] here because that is a
      // constant and when used for automatic differentiation will
      // lead to a zero derivative. Instead we take a first order
      // approximation and evaluate it at zero.
      q_delta[0] = T(1.0);
      q_delta[1] = delta[0];
      q_delta[2] = delta[1];
      q_delta[3] = delta[2];
    }

    Quaternionproduct(q_delta, x, x_plus_delta);
    return true;
  }
};

给定这个结构,现在可以构造自动微分局部参数化
这里写图片描述

Problem

Problem持有robustified边界约束的非线性最小二乘问题。使用Problem::AddResidualBlock()和Problem::AddParameterBlock() 方法来创建一个最小二乘问题。

例如,一个问题包含3个参数块,分别为3、4和5,以及2和6的两个残差块:

double x1[] = { 1.0, 2.0, 3.0 };
double x2[] = { 1.0, 2.0, 3.0, 5.0 };
double x3[] = { 1.0, 2.0, 3.0, 6.0, 7.0 };

Problem problem;
problem.AddResidualBlock(new MyUnaryCostFunction(…), x1);
problem.AddResidualBlock(new MyBinaryCostFunction(…), x2, x3);

Problem::AddResidualBlock() 就像他的名字一样,向problem添加残差块。它添加了3个CostFunction,1个可选的LossFunction ,并把CostFunction连接到一组参数块。

cost function 包含它所期望的参数块大小的信息。函数检查这些参数块与parameter_blocks中列出的参数块的大小匹配。如果检测到不匹配,程序将中止。损失函数可以是空的,在这种情况下,这一项就是残差的平方和。

用户能够显式地添加参数块使用Problem::AddParameterBlock()。这就导致了额外的正确性检查;Problem::AddResidualBlock() 隐式添加参数块,所以调用 Problem::AddParameterBlock()显式地不是必需的。

roblem::AddParameterBlock() 还允许用户将LocalParameterization对象与参数块关联起来。
重复调用相同的参数会被忽略。重复调用相同的double 指针,但是不同的大小会导致未定义的行为。

你可以用 Problem::SetParameterBlockConstant()设置任何参数块变成一个常量,不要用SetParameterBlockVariable()来做这个。

实际上,您可以将任意数量的参数块设置为常量,而Ceres非常聪明,可以解决您所构建的依赖于自由更改的参数块的问题,并且只花费时间来解决它。例如,如果你构造了一个有100万个参数块和200万个残差块的问题,然后只留下一个参数块不是常量,只有10个剩余块依赖于这个非常量参数块。那么在解决这个问题时,Ceres花费的计算精力将是和1个参数块,10个残差块的问题相同的。

所有权

Problem 默认持有 cost_function, loss_function 和 local_parameterization 指针的所有权.这些对象在Problem的生存期存活,如果用户想要保持摧毁权,可以设置相应的枚举Problem::Options结构。

注意,即使Problem拥有cost_function和loss_function的所有权,它并不排除用户在另一个残差块重用他们。析构函数会在每一个代价函数或损失函数指针上调用一次删除操作,不管有多少剩余块引用它们。

Problem::AddResidualBlock

向总成本函数添加一个残差块。成本函数包含它所期望的参数块大小的信息。函数检查这些参数块与参数块中列出的参数块的大小匹配。如果检测到不匹配,程序将中止。损失函数可以是空的,在这种情况下,这一项的代价就是残差的平方和。

Problem::AddParameterBlock

有两个重载,有一个会进行参数化。
添加一个适当的大小参数块,并对问题进行参数化。重复调用相同的参数会被忽略。重复调用相同的double指针,但是不同的大小会导致未定义的行为。

Problem::RemoveResidualBlock

从问题中删除一个残差块。残差块所依赖的任何参数都不会被删除。残差块的成本和损失函数不会立即被删除,在问题本身被删除之前不会发生。如果Problem::Options::enable_fast_removal设为true,删除会很快,基本是常量时间,否则,删除一个残差块将会对整个问题对象进行扫描,以验证残差块是否代表问题中的有效残差。

警告:移除残差或参数块将破坏隐式排序,导致解析器无法解释的雅可比矩阵或剩余值返回。如果您依赖于求值的雅可比矩阵,请不要使用remove!这可能会在未来的版本中发生变化。在优化期间保持指定的参数块不变。

Problem::RemoveParameterBlock

从问题中删除一个参数块。参数块的参数化,如果存在,将一直存在直到删除problem。任何依赖于参数的残差块也会被删除,如RemoveResidualBlock()中所述。如果Problem::Options::enable_fast_remova是true,那么删除是快速的(几乎是不变的时间)。否则,删除一个参数块将导致对整个问题对象的扫描。

Problem::SetParameterBlockConstant

在优化期间保持指定的参数块不变。

Problem::SetParameterBlockVariable

允许指定参数在优化期间变化。

Problem::SetParameterization

为其中一个参数块设置局部参数化。local_parameterization在默认情况下是由问题所拥有的。为多个参数设置相同的参数化是可以接受的;析构函数只会小心地删除 local parameterizations 1次。局部参数化只能在每个参数上设置一次,并且不能在设置后改变。

Problem::GetParameterization

获取与此参数块关联的局部参数化对象。如果没有参数化对象,则返回NULL

Problem::SetParameterLowerBound

设置values相对应的参数块中index位置参数的下界值。在默认情况下,下界是−∞。

Problem::SetParameterUpperBound

设置values相对应的参数块中index位置参数的上界值。在默认情况下,下界是∞。

Problem::NumParameterBlocks

问题中的参数块数量。总是等于parameter_blocks().size() 和parameter_block_sizes().size()。

Problem::NumParameters

通过对所有参数块的大小求和得到参数向量的大小。

Problem::NumResidualBlocks

问题中的残差块数量。总是等于residual_blocks().size()。

Problem::NumResiduals

通过对所有残差块的大小求和得到的残差向量的大小。

Problem::ParameterBlockSize

参数块大小。

Problem::ParameterBlockLocalSize

参数块的局部参数化的大小。如果没有与此参数块相关联的局部参数化,那么ParameterBlockLocalSize=ParameterBlockSize。

Problem::HasParameterBlock

problem中是否存在给定的参数快?

Problem::GetParameterBlocks

用指向当前problem中的参数块的指针填充传递的参数块向量,在这个调用之后,parameter_block.size() == NumParameterBlocks.

Problem::GetResidualBlocks

用指向当前problem中的残差块的指针填充传递的残差块向量,在这个调用之后,residual_blocks.size() == NumResidualBlocks.

Problem::GetParameterBlocksForResidualBlock

获得所有的依赖于给定残差块的参数块。

Problem::GetResidualBlocksForParameterBlock

如果Problem::Options::enable_fast_removal是true,那么获取残差块的速度很快,只依赖于残差块的数量。否则,获取参数块的剩余块将会对整个 Problem 对象进行扫描。

Problem::GetCostFunctionForResidualBlock

获得给定残差块的CostFunction。

Problem::GetLossFunctionForResidualBlock

获得给定残差块的 LossFunction 。

Problem::Evaluate

求解一个 Problem,输出指针都可以为NULL,使用哪一个残差块和参数块由 Problem::EvaluateOptions选项控制。

求值将使用在问题构造时使用的参数块指针指向的内存位置中的值,例如在以下代码中:

Problem problem;
double x = 1;
problem.Add(new MyCostFunction, NULL, &x);
double cost = 0.0;
problem.Evaluate(Problem::EvaluateOptions(), &cost, NULL, NULL, NULL);

请注意
如果不使用局部参数化,那么梯度向量的大小就是所有参数块的大小之和。如果一个参数块有一个局部的参数化,那么它就会将“LocalSize”贡献给梯度向量。

在解决问题时,这个函数不能被调用,例如,不能从IterationCallback 调用他,在solve的迭代结束时。

Problem::EvaluateOptions

用于控制 Problem::Evaluate()。

Problem::EvaluateOptions::parameter_blocks

一组参数块,用于进行求值。这个向量决定了在梯度向量和雅可比矩阵列中的参数块的顺序。如果parameter_blocks是空的,则假定它等于一个包含所有参数块的向量。一般来说,在这种情况下,参数块的顺序取决于将它们添加到问题的顺序以及用户是否删除了任何参数块。

注意这个向量应该包含与用于向问题添加参数块的指针相同的指针。这些参数块不应该指向新的内存位置。如果你做了,坏事就会发生。

Problem::EvaluateOptions::residual_blocks

用于执行计算的残差块的集合。这个向量决定了残差的顺序,以及如何排序雅可比矩阵的行。如果residual_blocks是空的,则假设它等于包含所有参数块的向量。

Problem::EvaluateOptions::apply_loss_function

尽管问题中的残差块可能包含损失函数,将apply_loss_function设置为false将会将损失函数的应用程序关闭。例如,如果用户希望分析解决方案的效果,通过研究前后的残差分布,就可以使用这种方法。

Problem::EvaluateOptions::num_threads

使用的线程数量。

rotation.h

Ceres的许多应用程序都涉及到一些优化问题,其中一些变量对应于旋转。为了减轻工作的痛苦,使用各种不同的旋转表示(角轴、四元数和矩阵),我们提供了一组简便的模板函数。这些函数是模板化的,这样用户就可以在Ceres求解器的自动微分框架中使用它们。

void AngleAxisToQuaternion(T const *angle_axis, T *quaternion)

将轴角表示的值转换为四元数。

angle_axis是三个一组的,它的角度用弧度表示,它的方向与旋转轴是一致的,quaternion是四个一组,它将包含生成的四元数。

void QuaternionToAngleAxis(T const *quaternion, T *angle_axis)

将四元数转换成等效的轴角表示。

quaternion必须是单位四元数,angle_axis将被填充 用弧度表示的旋转 ,方向是旋转轴方向。

RotationMatrixToAngleAxis
AngleAxisToRotationMatrix
RotationMatrixToAngleAxis
AngleAxisToRotationMatrix

在3x3旋转矩阵与轴角表示之间的转换。

EulerAnglesToRotationMatrix
EulerAnglesToRotationMatrix

在3x3旋转矩阵与欧拉角表示之间的转换。
{pitch,roll,yaw} 欧拉角是分别沿着 {x,y,z} 轴.

QuaternionToScaledRotation
QuaternionToScaledRotation

将4元向量转化为3×3缩放的旋转矩阵。

他选择的旋转是这样的,对于四元数 [1 0 0 0],得到一个单位矩阵,对于小的 a,b,c ,四元数[1 a b c]得到矩阵

I+20cbc0aba0+O(q2)

对应着Rodrigues近似,最后一个矩阵表示[a b c ]的叉乘矩阵。结合 R(q1q2)=R(q1)R(q2) 的特性,这一特性定义了从q到R的映射。

QuaternionToRotation

旋转矩阵用 Frobenius 归一化。 RR=I (det(R)=1)

UnitQuaternionRotatePoint

通过四元数旋转一个点,会对四元数进行归一化。

QuaternionRotatePoint

要求四元数模长不为0。

QuaternionProduct

四元数乘法

CrossProduct

四元数叉乘

AngleAxisRotatePoint

轴角旋转点

Cubic Interpolation

优化问题通常涉及以一个值表形式给出的函数,例如一个图像。计算这些函数及其导数需要对这些值进行插值,插值表函数是一个很大的研究领域,有很多库实现了各种插值方案。然而,在Ceres的自动微分框架中使用它们是相当痛苦的。为此,Ceres提供了对一维和二维表格函数进行插值的能力。

一维插值是基于 Cubic Hermite Spline,也被称为Catmull-Rom Spline。这就产生了一阶可微的内插函数。二维插值方案是一维方案的推广,插值函数被认为在两个维度是可分离的。

class CubicInterpolator

作为输入,一个无限的一维网格,它提供了下面的接口。

struct Grid1D {
enum { DATA_DIMENSION = 2; };
void GetValue(int n, double* f) const;
};

GetValue给出了一个函数f(可能是向量值)对于任何整数n的值,而枚举DATA_DIMENSION则表示插值函数的维数。例如,如果你是用轴角的方式插值相对时间的旋转,那么DATA_DIMENSION = 3.

CubicInterpolator 使用Cubic Hermite splines 来生成一个平滑的近似,可以用来计算在实数线上的任意点f(x)和f’(x)。例如,下面的代码插入一个4个数字的数组。

const double data[] = {1.0, 2.0, 5.0, 6.0};
Grid1D<double, 1> array(x, 0, 4);
CubicInterpolator interpolator(array);
double f, dfdx;
interpolator.Evaluate(1.5, &f, &dfdx);

在上面的代码中,我们使用了Grid1D一个模板化的助手类,它允许在C++数组和 CubicInterpolator之间进行简单的接口。

Grid1D支持向量的值函数,在这些函数中,函数的各种坐标可以交错或叠加。它还允许使用任何数值类型作为输入,只要可以安全地将其转换为double。

class BiCubicInterpolator

作为输入,一个无限的二维网格,它提供了下面的接口

struct Grid2D {
enum { DATA_DIMENSION = 2 };
void GetValue(int row, int col, double* f) const;
};

GetValue给出了一个函数f(可能是向量值)对于任何一对整数row和col的值,而枚举DATA_DIMENSION则表示插值函数的维数。例如,如果你插值一个3通道的图像(Red, Green & Blue),DATA_DIMENSION = 3.

BiCubicInterpolator 使用R. Keys 的三次卷积插值算法,可以产生一个平滑的近似,在任意实数平面计算 f(r,c) , f(r,c)r f(r,c)c

例如下面的代码插值两位数组

const double data[] = {1.0, 3.0, -1.0, 4.0,
                       3.6, 2.1,  4.2, 2.0,
                       2.0, 1.0,  3.1, 5.2};
Grid2D<double, 1>  array(data, 0, 3, 0, 4);
BiCubicInterpolator interpolator(array);
double f, dfdr, dfdc;
interpolator.Evaluate(1.2, 2.5, &f, &dfdr, &dfdc);

上面的代码,模板化的助手类Grid2D用于使一个C++数组对BiCubicInterpolator看起来像一个二维表。

Grid2D支持行优先或列优先的存储。它还支持向量值函数,在这个函数中,函数的各个坐标可能是交叉的或堆叠的。它还允许使用任何数值类型作为输入,只要可以安全地将其转换为double。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Ceres-Solver学习笔记(6) 的相关文章

  • unity:C#控制人在真实环境中行走

    自己在学习unity的课程中遇到了 xff0c 有的地方还没怎么太理解上去 xff0c 先做个笔记 xff0c 顺便看看有没有需要的人 1 搭建一个小场景 xff0c 一个需要控制的 人 xff08 添加CharacterControlle
  • unity 3D:自动寻路

    首先 xff0c 搭建一下场景 xff0c 场景要求 xff1a 有遮挡 xff0c 设置好不可走区域为navigation static 以及 not walkable 在人身上添加Nav Mesh Agent 设置好后勾选显示导航网格
  • 数据结构 ——c++实现(知识点集合)

    数据结构 c 43 43 实现 xff08 知识点集合 xff09 某不知名学狗的复习记录 xff0c 包含数据结构基本概念 xff0c 线性表 xff0c 栈 队列 递归 xff0c 串 数组 广义表和树和森林内容整理 主要整理了知识点
  • Unity3D 使用SceneManager跳转/加载场景

    很久没有更新博客了 xff0c 最近也是还在学习U3D 下面写一下使用SceneManager跳转 加载场景 我们假设要点击一个按钮跳转 xff0c 那么我们只要把跳转的代码写进按钮点击事件里就好了 其实加载场景很简单 xff0c 只需要写
  • [OpenCV] aruco Markers识别 小车巡线

    小车巡线代码 include lt ros ros h gt include lt sensor msgs Image h gt include lt geometry msgs Twist h gt include lt cv bridg
  • 备份ubuntu

    在使用Ubuntu之前 xff0c 相信很多人都有过使用Windows系统的经历 如果你备份过Windows系统 xff0c 那么你一定记忆犹新 xff1a 首先需要找到一个备份工 具 通常都是私有软件 xff0c 然后重启电脑进入备份工具
  • Docker - docker build 命令详解

    docker build 命令原理 docker build 命令从 Dockerfile 和上下文构建镜像构建的上下文 xff1a 位于指定 PATH 或 URL 中的一组文件构建过程可以引用上下文中的任何文件 xff0c 例如 xff0
  • RealSense二次开发

    转载 xff1a librealsense2查看相机设备信息 JavaShuo 文章目录 1 librealsense2设备信息读取 xff12 xff0e realsense 投影函数和反投影函数3 深度相机与彩色相机的坐标变换 1 li
  • 大规模MIP的精确算法和实现

    大规模MIP的精确算法和实现 大规模MIP的精确算法和实现 xff1a 目录第1部分 xff1a CPLEX的Java API详解1 CPLEX简介2 构建简单的模型3 CPLEX的高级应用 第2部分 xff1a Gurobi的Python
  • 两种Dockerfile文件配置

    注意 xff1a xxx是您的项目名称 xff01 Xmx xff1a 堆最大值 xff1b Xms xff1a 堆初始值 COPY指令只能从执行docker build所在的主机上读取资源并复制到镜像中 而ADD指令还支持通过URL从远程
  • 网络编程-----在线词典项目(服务器)

    服务器端 span class token macro property span class token directive keyword include span span class token string lt stdio h
  • 模型的图优化

    图优化 最近在整理之前的一些工作内容 记录下来温故而知新 在各种开源推理框架中 xff0c 我们总能看到有一种graph optimizer技术 xff0c 主要是对用户想要运行的网络模型进行一种网络结构层面的优化 xff0c 剔除不需要的
  • 学C++有多难,你知道吗?

    都2020年了 xff0c 还要学C 43 43 吗 xff1f C 43 43 好多理工科大学里面都有 xff0c 它的学习难度比其他编程语言比如Python Javascript 和Java等等难 那为什么呢 xff1f C 43 43
  • Ubuntu-KCF/DSST算法无人机跟踪仿真/实物保姆级教程

    KCF算法无人机跟踪 介绍 自己搭建的无人机跟踪实验 xff0c 主要讲软件 xff0c 硬件的需要等等 基础知识准备 整个系统需要两部分 xff0c 识别程序和控制无人机运动的程序 xff0c 都是Python脚本 xff0c 但运行需要
  • Linux基础入门:单片机和Linux有什么不同吗

    我发现很多初学者只有单片机基础 xff0c 甚至没有单片机基础 在学习Linux时 xff0c 对很多概念比较陌生 xff0c 导致不知道学什么 xff0c 也不知道学了之后有什么用 所以小编在此分享此文 第1章 单片机和Linux的区别
  • 为什么都说代码改变世界?是因为这五位程序员创造了未来!

    致敬那些为软件开发奠定坚实基础的计算机科学先驱 从 1 和 0 开始 xff0c 编程经历了很长一段路 xff0c 才达到了现在的抽象状态 过去的程序员用伟大的发明 xff0c 为现代程序员轻松地完成工作奠定了坚实的基础 如果我们研究某个软
  • C语言丨关键字enum用法详解,看这篇就够了

    一 关键字enum的定义 enum是C语言中的一个关键字 xff0c enum叫枚举数据类型 xff0c 枚举数据类型描述的是一组整型值的集合 xff08 这句话其实不太妥当 xff09 xff0c 枚举型是预处理指令 define的替代
  • Ubuntu下cmake使用入门

    CMake是一个跨平台的安装 xff08 编译 xff09 工具 xff0c 可以用简单的语句来描述所有平台的安装 编译过程 他能够输出各种各样的makefile或者project文件 其包含自己的语法结构 xff0c 只要按照其语法编写成
  • Windows与Linux双系统设置默认引导项与删除引导项

    当电脑中安装了Windows和Linux双系统 xff0c 但是每次开机默认自动进入Linux系统时 xff0c 有时根本来不及选择 xff0c 平时常用的系统却是Windows xff0c 于是我们需要让电脑默认选择Windows系统启动
  • 统招非全日制研究生就业受歧视的回应文件

    根据教育部办公厅等五部门 关于进一步做好非全日制研究生就业工作的通知 教研厅函 2019 1号 以及教育部办公厅印发 关于统筹全日制和非全日制研究生管理工作的通知 教研厅函 2016 2号 文件 xff0c 明确自2017年起 xff0c

随机推荐

  • IOTDB集群部署

    背景 IOTDB单节点的数据插入性能不是很好 xff0c 所以 xff0c 想看看集群的效果 xff0c 那么就需要搭建集群的环境 文件获取 下载地址 选择集群版本 文件目录 完成IoTDB Cluster安装后 xff0c 默认会在IoT
  • 全球超1850万条POI数据获取方法

    POI是 Point of Interest 的缩写 xff0c 中文可以翻译为 兴趣点 在地理信息系统中 xff0c 一个POI可以是一栋房子 一个商铺 一个邮筒 一个公交站等 数据获取 xff1a 数据分享 全球超1850万条 POI数
  • 百度网盘普通用户如何上传单文件最大4G文件。window split命令如何分割文件上传。

    普通用户使用百度网盘Web端上传文件时 xff0c 单文件最大支持1G大小 xff1b 使用网盘PC客户端上传文件时 xff0c 单文件最大支持4G xff1b 如果您需要上传大于4G文件 xff0c 可充值百度网盘会员 xff0c 其中
  • FreeRTOS 框架

    官网 章节目录规划 Kernel taskmemory managementqueue mutex semaphoretime managesoftware timerinterrupt process FreeRTOS plus Free
  • 飞行器设计之界限线图

    推重比与翼载荷约束分析 主要性能要求可以表示为最小起飞推重比T W和起飞翼载荷W S的函数 xff0c 每一项性能要求可以在起飞推重比T W 起飞翼载荷W S坐标中构成一条约束曲线 采用能量守恒方程 xff0c 推导出飞机性能约束的一般主管
  • FreeRTOS中使用中断的一些注意事项

    1 几个宏定义的解释 configLIBRARY LOWEST INTERRUPT PRIORITY 这个宏是可以定义的中断最低优先级 xff0c 由于STM32中断管理只用了4位来分配抢占优先级和子优先级 xff0c 并且FreeRTOS
  • 软路由cpu性能跑分

    软路由cpu性能跑分 cpu核心功耗单核多核N50304 46W14052909N50004 46W11522608N41204 46W11072477N41004 46W9952238N42004 46W8362027N34504 46W
  • k8s中pod的基本概念以及pod内资源共享的分析与实现

    目录 pod基本概念 xff1a pod的主要用法 pod资源共享实现机制 pod网络共享测试案例 xff1a 存储共享 pod存储共享测试案例 xff1a pod管理命令 常用的pod管理命令 定义pod pod基本概念 xff1a po
  • 迅雷笔试题2014校园招聘 武汉

  • C#中的readonly与const区别

    xfeff xfeff const 的概念就是一个包含不能修改的值的变量 常数表达式是在编译时可被完全计算的表达式 因此不能从一个变量中提取的值来初始化常量 如果 const int a 61 b 43 1 b是一个变量 xff0c 显然不
  • 改变无线连接、有线连接的优先级

    有线和无线连的是同一个网络 xff0c 当笔记本打开时 xff0c 总是优先使用无线连接 xff0c 如何转变优先级为有线连接呢 xff1f 1 打开网络和共享中心 2 更改适配器设置 xff0c 打开网络连接窗口 3 单击此窗口的高级菜单
  • 杂感一

    从2014年7月工作至今已有快2年了 xff0c csdn的博客从毕业后就很少上了 工作中有很多收获 技术上 也在不断积累和成长中 不管做什么事情 xff0c 要坚持下去 xff0c 方得初心 xff0c 把坚持养成习惯 xff0c 学习如
  • MFC隐藏主窗口的方法

    隐藏基于对话框的MFC应用程序窗口的方法 推荐这个方法 xff0c 非常好用 很多人可能会将窗口创建出来 然后用一个 ShowWindow SW HIDE 的方法去隐藏窗口 当然这是可以做到隐藏的功能 但是有一点不足的地方就是窗口在隐藏之前
  • JSP 通过Servlet将excel数据导入SQL

    1 gt 在网上下载jxl jar 这个JAR包用于Java操作excel 下载后 xff0c 将这个包复制到工程Webroot下的WEB INF下的lib中 xff0c 或是在工程中导入jxl jar包 2 gt 准备excel文件 如图
  • 1=5,2=15,3=215,4=2145,那么5=?

    如题 xff0c 1 61 5 xff0c 2 61 15 xff0c 3 61 215 xff0c 4 61 2145 xff0c 那么5 61 xff1f 答案 xff1a 5 61 1 哎 xff0c 这个题出的 xff0c 没反应过
  • 数值分析上机题Matlab--东南大学出版社(牛顿迭代/逐次超松弛迭代/3次样条插值/复合梯形SimpsonRomberg/四阶经典Runge-Kutta/幂法求特征向量)

    第二章上机题 Newton迭代法 function x err 61 Newton f x0 epsilon 用例 xff1a x err 61 Newton 39 x 3 3 x 39 0 7 0 005 Input f 字符串公式 39
  • 村子里有50个人,每人有一条狗,在这50条狗中有病狗(这种病不传染),于是人们要找出病狗。

    xff29 xff22 xff2d 公司向来以高素质人才作为企业持续竞争力的保证 进入 xff29 xff22 xff2d 公司是差不多每个 xff29 xff34 人的梦想 下面这条 xff29 xff22 xff2d 公司的面试题 xf
  • 删除单向链表中的某一个节点

    已知一个单向链表的表头head xff0c 写出一个删除某一个节点的算法 xff0c 要求先找到此节点 xff0c 然后删除 include lt iostream gt using namespace std typedef struct
  • 多段图的最短路径问题-----动态规划法

    对多段图 xff0c 求最短路径 xff0c 如图 xff1a 对其使用动态规划法 xff1a 阶段 xff1a 将图中的顶点划分5个阶段 xff0c k 状态 xff1a 每个阶段有几种供选择的点s 决策 xff1a 当前状态应在前一个状
  • Ceres-Solver学习笔记(6)

    溯洄从之 xff0c 道阻且长 建模最小化二乘问题 Ceres有两个组成部分 xff0c 一个是建模API xff0c 它提供了一组丰富的工具 xff0c 可以在一段时间内构造一个优化问题 xff0c 另一个是求解程序API xff0c 控