Problem
Problem Introduction
Problem
内保管一个带有边界约束的非线性最小二乘问题,通过使用 Problem::AddResidualBlock
和 Problem::AddParameterBlock
来建立这个问题。例如一个非线性优化问题包含三个参数块,每个参数块的大小分别为 3,4,5。同时拥有两个残差块,残差的维度分别为 2 和 6。定义方式如下:
Problem::AddParameterBlock
正如其函数名一样,是将一个残差块添加到优化问题中去,它需要几个参数,分别是 CostFunction
,一个可选的 LossFunction
,以及 CostFunction
进行计算所需要的参数块。
CostFunction
内部存储了它所期望的参数块大小的信息。函数会检查这些信息是否与 parameter_blocks
中列出的参数块大小一致。loss_function
可以是 nullptr
,在这种情况下,每一项的 cost 只是残差的平方。用户可以选择使用 Problem::AddParameterBlock()
明确添加参数块。不过,如果参数块不存在,Problem::AddResidualBlock()
会隐式添加参数块,因此不需要明确调用 Problem::AddParameterBlock()
。
所以说在很多实际应用中你会发现很多优化问题在定义的时候是没有调用过 Problem::AddParameterBlock()
方法的,即使是在一些需要特殊流形(例如四元数)参与的情况下,用户也可以在添加完残差块后,再对特定的参数块设置流形即可。
Problem::AddParameterBlock()
明确地将参数块添加到问题中。用户也可以选择将 Manifold 对象与参数块关联起来。相同参数重复调用该方法将被忽略,但若是在调用时发现两次的参数块大小设置不一致则会出现警告。
您可以使用 Problem::SetParameterBlockConstant()
将任何参数块设置为常量,也可以使用 SetParameterBlockVariable()
撤销设置。事实上,你可以设置任意数量的参数块为常数,而 Ceres 能找出你所构建问题的哪一部分取决于可自由变化的参数块,并只处理该部分。因此,举例来说,如果你构建了一个有 100 万个参数块和 200 万个残差块的问题,但除了 1 个参数块外,其他所有参数块都设置为常数,并且只有 10 个残差块取决于这一个非常数参数块。那么,Ceres 在解决这个问题上所花费的计算时间,将与你定义一个有 1 个参数块和 10 个残差块的问题所花费的计算时间相同。
默认情况下,Problem
拥有 cost_function
、loss_function
和 manifold
指针的所有权。这些对象在 Problem
的生命周期内保持有效。如果用户希望控制这些对象的销毁,可以通过在 Problem::Options
结构中设置相应的参数来实现。
请注意,尽管 Problem
拥有 cost_function
和 loss_function
对象的所有权,但并不妨碍用户在另一个残差块中重新使用它们。同样,同一个流形对象也可以用于多个参数块。析构函数会对每个拥有的对象做出精确的删除。
Member Variable and Methods
用户控制 Problem
的相关选项
default :
TAKE_OWNERSHIP
此选项控制 Problem
对象是否拥有代价函数。如果设置为 TAKE_OWNERSHIP
,那么 Problem
对象将在销毁时删除代价函数。由于允许共享代价函数,因此析构函数会只删除一次指针。
Default :
TAKE_OWNERSHIP
此选项控制 Problem
对象是否拥有鲁棒核函数。如果设置为 TAKE_OWNERSHIP
,那么 Problem
对象将在销毁时删除鲁棒核函数。由于允许共享鲁棒核函数,因此析构函数会只删除一次指针。
Default :
TAKE_OWNERSHIP
此选项控制 Problem
对象是否拥有 Manifolds。如果设置为 TAKE_OWNERSHIP
,那么 Problem
对象将在销毁时删除Manifolds。由于允许共享Manifolds,因此析构函数会只删除一次指针。
Default :
false
如果为 true,则会用哈希表来存储参数块(残差块)和原始指针的对应关系,以加快 Problem::RemoveResidualBlock()
和 Problem::RemoveParameterBlock()
操作的速度,同样的内存占用也会增加。默认情况下,Problem::RemoveParameterBlock()
和 Problem::RemoveResidualBlock()
所耗费的时间与整个问题的大小成正比。如果用户只是偶尔从问题中删除参数或残差,这点时间耗费可能是可以接受的。但是,如果有空余内存,可以启用此选项,使 Problem::RemoveParameterBlock()
的耗时与依赖它的残差块的数量成正比,而 Problem::RemoveResidualBlock()
的耗时(平均)不变。
内存使用量会增加两倍:每个参数块都会增加一个哈希集,其中包含所有依赖于该参数块的残差;问题中的哈希集包含所有残差。
Default :
false
默认情况下,Ceres 会在构建问题时执行各种安全检查。这些检查会带来微小的性能损失,通常约占构建时间的 5%。如果您确信您的问题构造是正确的,而且您确实希望避免这 5%的问题构造时间,那么您可以将 disable_all_safety_checks
设置为 true
。
Do not set this to true, unless you are absolutely sure of what you are doing.
Default :
nullptr
A Ceres global context to use for solving this problem. This may help to reduce computation time as Ceres can reuse expensive objects to create. The context object can be nullptr, in which case Ceres may create one。Ceres 不拥有指针的所有权。
Default :
nullptr
Using this callback interface, Ceres will notify you when it is about to evaluate the residuals or Jacobians.
If an evaluation_callback
is present, Ceres will update the user’s parameter blocks to the values that will be used when calling CostFunction::Evaluate()
before calling EvaluationCallback::PrepareForEvaluation()
. One can then use this callback to share (or cache) computation between cost functions by doing the shared computation in EvaluationCallback::PrepareForEvaluation()
before Ceres calls CostFunction::Evaluate()
.
Problem does NOT take ownership of the callback.
Evaluation callbacks are incompatible with inner iterations. So calling Solve with Solver::Options::use_inner_iterations
set to true
on a Problem
with a non-null evaluation callback is an error.
在总成本函数中添加一个残差块。cost_function
携带了它所期望的参数块大小的信息。函数会检查这些信息是否与 parameter_blocks
中列出的参数块大小一致。loss_function
可以是 nullptr
,在这种情况下,该项的代价只是残差的平方。
参数块可以以 vector<double*>
或 double*
指针的形式一起传递。用户可以选择使用 AddParameterBlock
明确添加参数块。这将导致额外的合法性检查;不过,如果没有参数块,AddResidualBlock
会隐式添加参数块,因此不需要显式调用 AddParameterBlock
。
Problem 对象默认拥有 cost_function
和 loss_function
指针。这些对象在 Problem 对象的生命周期内保持有效。如果用户希望控制这些对象的销毁,可以通过在 Options 结构中设置相应的参数来实现。
Even though the Problem takes ownership of cost_function
and loss_function
, it does not preclude the user from re-using them in another residual block. The destructor takes care to call delete on each cost_function or loss_function pointer only once, regardless of how many residual blocks refer to them.
Example usage:
在问题中添加指定大小的参数块和 Manifold。manifold 可以是 nullptr
。
相同参数的重复调用将被 Ceres 忽略。但若重复调用时设置了和先前不同的参数块 size 则会导致崩溃(除非 Solver::Options::disable_all_safety_checks
设置为 true)。若重复调用,且大小相同,但 manifold 不同,此时相当于调用 SetManifold()
,即之前关联的 manifold 对象将被替换。
在问题中添加指定大小的参数块。相同参数的重复调用将被 Ceres 忽略。但若重复调用时设置了和先前不同的参数块 size 则会导致崩溃(除非 Solver::Options::disable_all_safety_checks
设置为 true)。
从问题中移除一个 residual block。由于残差块可以共享成本函数和损失函数对象,因此 Ceres Solver 使用引用计数机制。当删除残差块时,相应成本函数和损失函数对象的引用计数就会减少,当该计数达到零时,它们就会被删除。如果 Problem::Options::enable_fast_removal
为 true,则删除速度很快(几乎是常数复杂度)。否则它是线性的,需要遍历所有残差块。删除残差块对问题所依赖的参数块没有影响。
Removing a residual or parameter block will destroy the implicit ordering, rendering the jacobian or residuals returned from the solver uninterpretable. If you depend on the evaluated jacobian, do not use remove! This may change in a future release. Hold the indicated parameter block constant during optimization.
从问题中删除参数块。依赖于该参数的任何 residual block 也会被删除,如上面在RemoveResidualBlock()
中所述。如果该参数块有对应的流形,流形对象将持续存在,直到问题被删除。如果 Problem::Options::enable_fast_removal
为 true,则删除速度很快(几乎是常数复杂度)。否则,删除参数块将遍历整个问题。
Removing a residual or parameter block will destroy the implicit ordering, rendering the jacobian or residuals returned from the solver uninterpretable. If you depend on the evaluated jacobian, do not use remove! This may change in a future release.
在优化期间让指定的参数块为恒定值,该参数块不进行优化。
允许指示的参数在优化期间发生变化。
如果参数块设置为常量,则返回 true
,否则返回 false
。参数块可以通过两种方式设置常量:通过调用 SetParameterBlockConstant
或通过将 Manifold
与零维切空间相关联。
设置参数块的 Manifold
。使用 nullptr
调用 Problem::SetManifold()
将清除之前为参数块设置的任何 Manifold
。重复调用将导致任何先前关联的 Manifold
对象被替换为新的 Manifold
。默认情况下,流形由 Problem
所有(请参阅 Problem::Options
)。可以为多个参数块设置相同的 Manifold
。
获取与此参数块关联的 Manifold
对象。如果没有与参数块关联的 Manifold
对象,则返回 nullptr
。
如果 Manifold 与此参数块关联,则返回 true,否则返回 false。
设置参数块中对应位置处的参数值下限。默认情况下,下限为 -std::numeric_limits::max()
,求解器将其视为与负无穷大。
设置参数块中对应位置处的参数值上限。默认情况下,该值为 std::numeric_limits::max()
,求解器将其视为正无穷大。
获取对应位置的参数值的下限。如果用户没有限制,则其下限为 -std::numeric_limits::max()
。
获取对应位置的参数值的上限。如果用户没有限制,则其上限为 std::numeric_limits::max()
。
问题中的参数块数量。始终等于 parameter_blocks().size()
和parameter_block_sizes().size()
。
通过对所有参数块的大小求和获得的参数向量的大小。
问题中的残差块数量。始终等于 residual_blocks().size()
。
通过对所有残差块的大小求和而获得的残差向量的大小。
参数块的流形切线空间的尺寸。如果没有与此参数块关联的 Manifold
,则 ParameterBlockTangentSize = ParameterBlockSize
。
判断问题中是否存在给定的参数块。
获取当前问题中的参数块,用指向当前问题中的参数块的指针填充 parameter_blocks
向量。调用后,parameter_block.size() == NumParameterBlocks
。
获取当前问题中的残差块,用指向当前问题中的残差块的指针填充残差块向量 residual_blocks
。调用后,residual_blocks.size() == NumResidualBlocks
。
获取依赖于给定残差块的所有参数块。
获取依赖于给定参数块的所有残差块。如果 Problem::Options::enable_fast_removal
为 true
,则获取残差块的速度很快,并且仅取决于残差块的数量。否则,获取参数块的残差块将扫描整个问题。
获取给定残差块的 CostFunction
。
获取给定残差块的 LossFunction
。
评估残差块,将标量残差存储在 cost
中,将残差分量存储在 residuals
中,并按行优先顺序将参数和残差之间的 jacobian
矩阵存储在 jacobians[i]
中。如果 residuals
是 nullptr
则 residuals
将不会被计算。如果 jacobians
为 nullptr
则 jacobains
不会被计算,如果 jacobians[i]
是空的,则对于该参数块的 jacobian
也不会被计算,例如当参数块为常数的时候。返回值代表计算是否成功,即使返回的是 false
,用户也应当意识到相应内存中的值也可能已经发生了变化。返回的残差和 jacobians
已经利用了鲁棒核函数和流形,例如,使用 QuaternionManifold
的四元数参数的雅可比矩阵是 num_residuals x 3
,而不是 num_residuals x 4
。apply_loss_function
顾名思义,允许用户打开和关闭损失函数。
If an EvaluationCallback
is associated with the problem, then its EvaluationCallback::PrepareForEvaluation()
method will be called every time this method is called with new_point = true. This conservatively assumes that the user may have changed the parameter values since the previous call to evaluate / solve. For improved efficiency, and only if you know that the parameter values have not changed between calls, see Problem::EvaluateResidualBlockAssumingParametersUnchanged()
.
Same as Problem::EvaluateResidualBlock()
except that if an EvaluationCallback
is associated with the problem, then its EvaluationCallback::PrepareForEvaluation()
method will be called every time this method is called with new_point = false.
This means, if an EvaluationCallback
is associated with the problem then it is the user’s responsibility to call EvaluationCallback::PrepareForEvaluation()
before calling this method if necessary, i.e. iff the parameter values have been changed since the last call to evaluate / solve.’
This is because, as the name implies, we assume that the parameter blocks did not change since the last time EvaluationCallback::PrepareForEvaluation()
was called (via Solve()
, Problem::Evaluate()
or Problem::EvaluateResidualBlock()
).
评估 Problem。任何输出指针都可以为 nullptr
。使用哪些残差块和参数块由下面的 Problem::EvaluateOptions
控制。Evaluate 将使用在构建问题时参数块指针所指向的内存位置中的值,例如在以下代码中:
If no Manifold
are used, then the size of the gradient vector is the sum of the sizes of all the parameter blocks. If a parameter block has a manifold then it contributes “TangentSize” entries to the gradient vector.
This function cannot be called while the problem is being solved, for example it cannot be called from an IterationCallback
at the end of an iteration during a solve.
If an EvaluationCallback
is associated with the problem, then its PrepareForEvaluation method will be called everytime this method is called with new_point = true
.
用于控制 Problem::Evaluate() 的配置参数。
应进行评估的参数块集合。parameter_blocks
决定了参数块在梯度向量和 Jacobian 矩阵中出现的顺序。如果 parameter_blocks
为空,则假定它为包含了 Problem 所有参数块的向量。一般来说,这种情况下参数块的排序取决于它们被添加到问题中的顺序,以及用户是否删除了一些参数块。注意:该向量所包含的指针应与向问题中添加参数块的指针相同。这些参数块不应指向新的内存位置。
应进行评估的残差块集合。residual_blocks
向量决定了残差出现的顺序,以及 jacobian 行的排序方式。如果 residual_blocks
为空,则假定它为包含所有残差块的向量。
即使问题中的残差块可能包含损失函数,将 apply_loss_function 设置为 false 也会关闭损失函数。例如,如果用户希望通过研究求解前后的残差来分析求解质量,就可以使用这种方法。
使用的线程数。
Last updated