Ceres Introdução e Exemplos (11) Sobre Derivativos~Interface com Diferenciação Automática

A diferenciação automática é fácil de usar quando uma expressão explícita para a função de custo está disponível. Normalmente, os programas precisam interagir com programas ou dados externos. Neste capítulo, consideraremos várias abordagens diferentes.

Para tanto, consideraremos o problema de encontrar os parâmetros θ e t para resolver o seguinte problema de otimização na forma de uma transformação afim 2D distorcida:
min ⁡ ∑ i ∥ yi − f ( ∥ qi ∥ 2 ) qi ∥ 2 tal que qi = R ( θ ) xi + t \begin{split}\min & \quad \sum_i \left \|y_i - f\left (\|q_{i}\|^2\right) q_i \right \| ^2\\ \text{tal que} & \quad q_i = R(\theta) x_i + t\end{split}mintal queeu yeuf( ∥q _eu2 )qeu 2qeu=R ( i ) xeu+t
Aqui, R é uma matriz de rotação 2D parametrizada pelo ângulo θ e t é um vetor 2D. f é uma função de distorção externa.

Vamos primeiro considerar o caso em que temos uma função modelo TemplatedComputeDistortion que calcula uma função f. A implementação do funtor residual correspondente é então direta, como segue:

template <typename T> T TemplatedComputeDistortion(const T r2) {
    
    
  const double k1 = 0.0082;
  const double k2 = 0.000023;
  return 1.0 + k1 * r2 + k2 * r2 * r2;
}

struct Affine2DWithDistortion {
    
    
  Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
    
    
    x[0] = x_in[0];
    x[1] = x_in[1];
    y[0] = y_in[0];
    y[1] = y_in[1];
  }

  template <typename T>
  bool operator()(const T* theta,
                  const T* t,
                  T* residuals) const {
    
    
    const T q_0 =  cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
    const T q_1 =  sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
    const T f = TemplatedComputeDistortion(q_0 * q_0 + q_1 * q_1);
    residuals[0] = y[0] - f * q_0;
    residuals[1] = y[1] - f * q_1;
    return true;
  }

  double x[2];
  double y[2];
};

Até aqui tudo bem, mas agora vamos considerar três maneiras de definir f que não podem ser usadas diretamente para diferenciação automática:

  • 1. Uma função sem modelo que calcula seu valor.
  • 2. Encontre a função de seu valor e derivada.
  • 3. Definido em função da tabela de valores a serem interpolados.

Vamos considerá-los por sua vez abaixo.

1、Uma função que retorna seu valor

Suponha que recebemos uma função ComputeDistortionValue declarada da seguinte forma:

double ComputeDistortionValue(double r2);

Calcula o valor de f. A implementação real da função não importa. Existem três etapas para fazer a interface desta função com Affine2DWithDistortion:

  • 1. ComputeDistortionValueEncapsule-o em uma função ComputeDistortionValueFunctor.
  • 2. Diferencie ComputeDistortionValueFunctornumericamente o NumericDiffCostFunction para criar um CostFunction
  • 3. Use CostFunctionToFunctor para encapsular CostFunction . Embrulhado para obter uma operator()função com operadores modelados. Este método de função de operador pode transformar a matriz jacobiana calculada por NumericDiffCostFunctionJet em um objeto.

A implementação das três etapas acima é a seguinte:

struct ComputeDistortionValueFunctor {
    
    
  bool operator()(const double* r2, double* value) const {
    
    
    *value = ComputeDistortionValue(r2[0]);
    return true;
  }
};

struct Affine2DWithDistortion {
    
    
  Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
    
    
    x[0] = x_in[0];
    x[1] = x_in[1];
    y[0] = y_in[0];
    y[1] = y_in[1];

    compute_distortion.reset(new ceres::CostFunctionToFunctor<1, 1>(
         new ceres::NumericDiffCostFunction<ComputeDistortionValueFunctor,
                                            ceres::CENTRAL,
                                            1,
                                            1>(
            new ComputeDistortionValueFunctor)));
  }

  template <typename T>
  bool operator()(const T* theta, const T* t, T* residuals) const {
    
    
    const T q_0 = cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
    const T q_1 = sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
    const T r2 = q_0 * q_0 + q_1 * q_1;
    T f;
    (*compute_distortion)(&r2, &f);
    residuals[0] = y[0] - f * q_0;
    residuals[1] = y[1] - f * q_1;
    return true;
  }

  double x[2];
  double y[2];
  std::unique_ptr<ceres::CostFunctionToFunctor<1, 1> > compute_distortion;
};

2、Uma função que retorna seu valor e derivada

Agora suponha que temos uma função ComputeDistortionValue, podemos obter seu valor e podemos obter sua matriz jacobiana, se quisermos. Sua declaração de função é a seguinte:

void ComputeDistortionValueAndJacobian(double r2,
                                       double* value,
                                       double* jacobian);

Novamente, a implementação real da função não importa. A interface desta função com Affine2DWithDistortiono encaixe requer duas etapas:

  • 1. ComputeDistortionValueAndJacobianEncapsule-o em um objeto CostFunction. Chamamos esse objeto CostFunction ComputeDistortionFunction.
  • 2. Encapsule o objeto ComputeDistortionFunction obtido com CostFunctionToFunctor. Obtém um Functor com um operator()método de operador de modelo que transforma o Jacobiano calculado pelo NumericDiffCostFunction em um Jetobjeto de ajuste.

código mostra como abaixo:

class ComputeDistortionFunction : public ceres::SizedCostFunction<1, 1> {
    
    
 public:
  virtual bool Evaluate(double const* const* parameters,
                        double* residuals,
                        double** jacobians) const {
    
    
    if (!jacobians) {
    
    
      ComputeDistortionValueAndJacobian(parameters[0][0], residuals, nullptr);
    } else {
    
    
      ComputeDistortionValueAndJacobian(parameters[0][0], residuals, jacobians[0]);
    }
    return true;
  }
};

struct Affine2DWithDistortion {
    
    
  Affine2DWithDistortion(const double x_in[2], const double y_in[2]) {
    
    
    x[0] = x_in[0];
    x[1] = x_in[1];
    y[0] = y_in[0];
    y[1] = y_in[1];
    compute_distortion.reset(
        new ceres::CostFunctionToFunctor<1, 1>(new ComputeDistortionFunction));
  }

  template <typename T>
  bool operator()(const T* theta,
                  const T* t,
                  T* residuals) const {
    
    
    const T q_0 =  cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
    const T q_1 =  sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
    const T r2 = q_0 * q_0 + q_1 * q_1;
    T f;
    (*compute_distortion)(&r2, &f);
    residuals[0] = y[0] - f * q_0;
    residuals[1] = y[1] - f * q_1;
    return true;
  }

  double x[2];
  double y[2];
  std::unique_ptr<ceres::CostFunctionToFunctor<1, 1> > compute_distortion;
};

3、Uma função que é definida como uma tabela de valores

O terceiro e último caso que iremos considerar é quando a função f é definida como uma tabela de valores no intervalo [0,100], com um valor para cada inteiro.

vector<double> distortion_values;

Há muitas maneiras de inserir uma tabela de valores. Talvez o método mais simples e comum seja a interpolação linear. Mas usar a interpolação linear não é uma boa ideia, porque a função de interpolação não é diferenciável nos pontos de amostragem.

Outro método de interpolação diferenciável simples, mas excelente, é o Cubic Hermite Spline (interpolação Hermite chinesa). O Ceres fornece todo o processo de interpolação cúbica e bi-cúbica e pode facilmente aplicar algoritmos de diferenciação automática.

Para usar a interpolação cúbica, você precisa primeiro construir um objeto Grid1D para encapsular a tabela de valores e, em seguida, usá-lo para construir um objeto CubicInterpolator.

O código resultante se parece com isso:

struct Affine2DWithDistortion {
    
    
  Affine2DWithDistortion(const double x_in[2],
                         const double y_in[2],
                         const std::vector<double>& distortion_values) {
    
    
    x[0] = x_in[0];
    x[1] = x_in[1];
    y[0] = y_in[0];
    y[1] = y_in[1];

    grid.reset(new ceres::Grid1D<double, 1>(
        &distortion_values[0], 0, distortion_values.size()));
    compute_distortion.reset(
        new ceres::CubicInterpolator<ceres::Grid1D<double, 1> >(*grid));
  }

  template <typename T>
  bool operator()(const T* theta,
                  const T* t,
                  T* residuals) const {
    
    
    const T q_0 =  cos(theta[0]) * x[0] - sin(theta[0]) * x[1] + t[0];
    const T q_1 =  sin(theta[0]) * x[0] + cos(theta[0]) * x[1] + t[1];
    const T r2 = q_0 * q_0 + q_1 * q_1;
    T f;
    compute_distortion->Evaluate(r2, &f);
    residuals[0] = y[0] - f * q_0;
    residuals[1] = y[1] - f * q_1;
    return true;
  }

  double x[2];
  double y[2];
  std::unique_ptr<ceres::Grid1D<double, 1> > grid;
  std::unique_ptr<ceres::CubicInterpolator<ceres::Grid1D<double, 1> > > compute_distortion;
};

No exemplo acima, usamos Grid1D e CubicInterpolator para interpolar uma tabela de valores unidimensionais. Grid2D é usado em conjunto com CubicInterpolator para permitir que os usuários insiram tabelas bidimensionais de valores. Observe que nem Grid1D nem Grid2D estão limitados a funções com valor escalar, eles também podem usar funções com valor vetorial.

Acho que você gosta

Origin blog.csdn.net/wanggao_1990/article/details/129729135
Recomendado
Clasificación