Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'extern/libmv/libmv/tracking/esm_region_tracker.cc')
-rw-r--r--extern/libmv/libmv/tracking/esm_region_tracker.cc288
1 files changed, 288 insertions, 0 deletions
diff --git a/extern/libmv/libmv/tracking/esm_region_tracker.cc b/extern/libmv/libmv/tracking/esm_region_tracker.cc
new file mode 100644
index 00000000000..2e5c255e153
--- /dev/null
+++ b/extern/libmv/libmv/tracking/esm_region_tracker.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 2007, 2008, 2009, 2011 libmv authors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "libmv/tracking/esm_region_tracker.h"
+
+#include "libmv/logging/logging.h"
+#include "libmv/image/image.h"
+#include "libmv/image/convolve.h"
+#include "libmv/image/sample.h"
+#include "libmv/numeric/numeric.h"
+
+namespace libmv {
+
+// TODO(keir): Reduce duplication between here and the other region trackers.
+bool RegionIsInBounds(const FloatImage &image1,
+ double x, double y,
+ int half_window_size) {
+ // Check the minimum coordinates.
+ int min_x = floor(x) - half_window_size - 1;
+ int min_y = floor(y) - half_window_size - 1;
+ if (min_x < 0.0 ||
+ min_y < 0.0) {
+ return false;
+ }
+
+ // Check the maximum coordinates.
+ int max_x = ceil(x) + half_window_size + 1;
+ int max_y = ceil(y) + half_window_size + 1;
+ if (max_x > image1.cols() ||
+ max_y > image1.rows()) {
+ return false;
+ }
+
+ // Ok, we're good.
+ return true;
+}
+
+// Sample a region centered at x,y in image with size extending by half_width
+// from x,y. Channels specifies the number of channels to sample from.
+void SamplePattern(const FloatImage &image,
+ double x, double y,
+ int half_width,
+ int channels,
+ FloatImage *sampled) {
+ sampled->Resize(2 * half_width + 1, 2 * half_width + 1, channels);
+ for (int r = -half_width; r <= half_width; ++r) {
+ for (int c = -half_width; c <= half_width; ++c) {
+ for (int i = 0; i < channels; ++i) {
+ (*sampled)(r + half_width, c + half_width, i) =
+ SampleLinear(image, y + r, x + c, i);
+ }
+ }
+ }
+}
+
+// Estimate "reasonable" error by computing autocorrelation for a small shift.
+// TODO(keir): Add a facility for
+double EstimateReasonableError(const FloatImage &image,
+ double x, double y,
+ int half_width) {
+ double error = 0.0;
+ for (int r = -half_width; r <= half_width; ++r) {
+ for (int c = -half_width; c <= half_width; ++c) {
+ double s = SampleLinear(image, y + r, x + c, 0);
+ double e1 = SampleLinear(image, y + r + 0.5, x + c, 0) - s;
+ double e2 = SampleLinear(image, y + r, x + c + 0.5, 0) - s;
+ error += e1*e1 + e2*e2;
+ }
+ }
+ // XXX hack
+ return error / 2.0 * 16.0;
+}
+
+// This is implemented from "Lukas and Kanade 20 years on: Part 1. Page 42,
+// figure 14: the Levenberg-Marquardt-Inverse Compositional Algorithm".
+bool EsmRegionTracker::Track(const FloatImage &image1,
+ const FloatImage &image2,
+ double x1, double y1,
+ double *x2, double *y2) const {
+ if (!RegionIsInBounds(image1, x1, y1, half_window_size)) {
+ LG << "Fell out of image1's window with x1=" << x1 << ", y1=" << y1
+ << ", hw=" << half_window_size << ".";
+ return false;
+ }
+
+ int width = 2 * half_window_size + 1;
+
+ // TODO(keir): Avoid recomputing gradients for e.g. the pyramid tracker.
+ Array3Df image_and_gradient1;
+ Array3Df image_and_gradient2;
+ BlurredImageAndDerivativesChannels(image1, sigma, &image_and_gradient1);
+ BlurredImageAndDerivativesChannels(image2, sigma, &image_and_gradient2);
+
+ // Step -1: Resample the template (image1) since it is not pixel aligned.
+ //
+ // Take a sample of the gradient of the pattern area of image1 at the
+ // subpixel position x1, x2. This is reused for each iteration, so
+ // precomputing it saves time.
+ Array3Df image_and_gradient1_sampled;
+ SamplePattern(image_and_gradient1, x1, y1, half_window_size, 3,
+ &image_and_gradient1_sampled);
+
+ // Step 0: Initialize delta = 0.01.
+ //
+ // Ignored for my "normal" LM loop.
+
+ double reasonable_error =
+ EstimateReasonableError(image1, x1, y1, half_window_size);
+
+ // Step 1: Warp I with W(x, p) to compute I(W(x; p).
+ //
+ // Use two images for accepting / rejecting updates.
+ // XXX is this necessary?
+ int current_image = 0, new_image = 1;
+ Array3Df image_and_gradient2_sampled[2];
+ SamplePattern(image_and_gradient2, *x2, *y2, half_window_size, 3,
+ &image_and_gradient2_sampled[current_image]);
+
+ // Step 2: Compute the squared error I - J.
+ double error = 0;
+ for (int r = 0; r < width; ++r) {
+ for (int c = 0; c < width; ++c) {
+ double e = image_and_gradient1_sampled(r, c, 0) -
+ image_and_gradient2_sampled[current_image](r, c, 0);
+ error += e*e;
+ }
+ }
+
+ // Step 3: Evaluate the gradient of the template.
+ //
+ // This is done above when sampling the template (step -1).
+
+ // Step 4: Evaluate the jacobian dw/dp at (x; 0).
+ //
+ // The jacobian between dx,dy and the warp is constant 2x2 identity, so it
+ // doesn't have to get computed. The Gauss-Newton Hessian matrix computation
+ // below would normally have to include a multiply by the jacobian.
+
+ // Step 5: Compute the steepest descent images of the template.
+ //
+ // Since the jacobian of the position w.r.t. the sampled template position is
+ // the identity, the steepest descent images are the same as the gradient.
+
+ // Step 6: Compute the Gauss-Newton Hessian for the template (image1).
+ //
+ // This could get rolled into the previous loop, but split it for now for
+ // clarity.
+ Mat2 H_image1 = Mat2::Zero();
+ for (int r = 0; r < width; ++r) {
+ for (int c = 0; c < width; ++c) {
+ Vec2 g(image_and_gradient1_sampled(r, c, 1),
+ image_and_gradient1_sampled(r, c, 2));
+ H_image1 += g * g.transpose();
+ }
+ }
+
+ double tau = 1e-4, eps1, eps2, eps3;
+ eps1 = eps2 = eps3 = 1e-15;
+
+ double mu = tau * std::max(H_image1(0, 0), H_image1(1, 1));
+ double nu = M_E;
+
+ int i;
+ for (i = 0; i < max_iterations; ++i) {
+ // Check that the entire image patch is within the bounds of the images.
+ if (!RegionIsInBounds(image2, *x2, *y2, half_window_size)) {
+ LG << "Fell out of image2's window with x2=" << *x2 << ", y2=" << *y2
+ << ", hw=" << half_window_size << ".";
+ return false;
+ }
+
+ Mat2 H = Mat2::Zero();
+ for (int r = 0; r < width; ++r) {
+ for (int c = 0; c < width; ++c) {
+ Vec2 g1(image_and_gradient1_sampled(r, c, 1),
+ image_and_gradient1_sampled(r, c, 2));
+ Vec2 g2(image_and_gradient2_sampled[current_image](r, c, 1),
+ image_and_gradient2_sampled[current_image](r, c, 2));
+ Vec2 g = g1 + g2; // Should be / 2.0, but do that outside the loop.
+ H += g * g.transpose();
+ }
+ }
+ H /= 4.0;
+
+ // Step 7: Compute z
+ Vec2 z = Vec2::Zero();
+ for (int r = 0; r < width; ++r) {
+ for (int c = 0; c < width; ++c) {
+ double e = image_and_gradient2_sampled[current_image](r, c, 0) -
+ image_and_gradient1_sampled(r, c, 0);
+ z(0) += image_and_gradient1_sampled(r, c, 1) * e;
+ z(1) += image_and_gradient1_sampled(r, c, 2) * e;
+ z(0) += image_and_gradient2_sampled[current_image](r, c, 1) * e;
+ z(1) += image_and_gradient2_sampled[current_image](r, c, 2) * e;
+ }
+ }
+ z /= 2.0;
+
+ // Step 8: Compute Hlm and (dx,dy)
+ Mat2 diag = H.diagonal().asDiagonal();
+ diag *= mu;
+ Mat2 Hlm = H + diag;
+ Vec2 d = Hlm.lu().solve(z);
+
+ // TODO(keir): Use the usual LM termination and update criterion instead of
+ // this hacky version from the LK 20 years on paper.
+ LG << "x=" << *x2 << ", y=" << *y2 << ", dx=" << d[0] << ", dy=" << d[1]
+ << ", mu=" << mu << ", nu=" << nu;
+
+ // Step 9: Update the warp; W(x; p) <-- W(x;p) compose W(x, dp)^-1
+ double new_x2 = *x2 - d[0];
+ double new_y2 = *y2 - d[1];
+
+ // Step 9.1: Sample the image at the new position.
+ SamplePattern(image_and_gradient2, new_x2, new_y2, half_window_size, 3,
+ &image_and_gradient2_sampled[new_image]);
+
+ // Step 9.2: Compute the new error.
+ // TODO(keir): Eliminate duplication with above code.
+ double new_error = 0;
+ for (int r = 0; r < width; ++r) {
+ for (int c = 0; c < width; ++c) {
+ double e = image_and_gradient1_sampled(r, c, 0) -
+ image_and_gradient2_sampled[new_image](r, c, 0);
+ new_error += e*e;
+ }
+ }
+ //LG << "Old error: " << error << ", new error: " << new_error;
+
+ double rho = (error - new_error) / (d.transpose() * (mu * d + z));
+
+ // Step 10: Accept or reject step.
+ if (rho <= 0) {
+ // This was a bad step, so don't update.
+ mu *= nu;
+
+ // The standard Levenberg-Marquardt update multiplies nu by 2, but
+ // instead chose e. I chose a constant greater than 2 since in typical
+ // tracking examples, I saw that once updates started failing they should
+ // ramp towards steepest descent faster. This has no basis in theory, but
+ // appears to lead to faster tracking overall.
+ nu *= M_E;
+ LG << "Error increased, so reject update.";
+ } else {
+ // The step was good, so update.
+ *x2 = new_x2;
+ *y2 = new_y2;
+ std::swap(new_image, current_image);
+ error = new_error;
+
+ mu *= std::max(1/3., 1 - pow(2*rho - 1, 3));
+ nu = M_E; // See above for why to use e.
+ }
+
+ // If the step was accepted, then check for termination.
+ if (d.squaredNorm() < min_update_squared_distance) {
+ if (new_error > reasonable_error) {
+ LG << "Update size shrank but reasonable error ("
+ << reasonable_error << ") not achieved; failing.";
+ return true; // XXX
+ }
+ LG << "Successful track in " << (i + 1) << " iterations.";
+ return true;
+ }
+ }
+ // Getting here means we hit max iterations, so tracking failed.
+ LG << "Too many iterations; max is set to " << max_iterations << ".";
+ return false;
+}
+
+} // namespace libmv