Fast Marching in 2D¶


This tour explores the use of Fast Marching methods in 2-D.

In [2]:
warning off
warning on


Shortest Path for Isotropic Metrics¶

Shortest paths are 2D curves that minimize a weighted length according to a given metric $W(x)$ for $x \in [0,1]^2$. The metric is usually computed from an input image $f(x)$.

The length of a curve $t \in [0,1] \mapsto \gamma(t) \in [0,1]^2$ is $$L(\gamma) = \int_0^1 W(\gamma(t)) \norm{\gamma'(t)} \text{d} t.$$

Note that $L(\gamma)$ is invariant under re-parameterization of the curve $\gamma$.

A geodesic curve $\gamma$ between two points $x_0$ and $x_1$ has minimum length among curves joining $x_0$ and $x_1$, $$\umin{\ga(0)=x_0, \ga(1)=x_1} L(\ga).$$ A shortest curve thus tends to pass in areas where $W$ is small.

The geodesic distance between the two points is then $d(x_0,x_1)=L(\gamma)$ is the geodesic distance according to the metric $W$.

Pixel values-based Geodesic Metric¶

The geodesic distance map $D(x)=d(x_0,x)$ to a fixed starting point $x_0$ is the unique viscosity solution of the Eikonal equation $$\norm{ \nabla D(x)} = W(x) \qandq D(x_0)=0.$$

This equation can be solved numerically in $O(N \log(N))$ operation on a discrete grid of $N$ points.

We load the input image $f$.

In [3]:
clear options;
n = 300;
f = rescale( load_image(name, n) );


Display the image.

In [4]:
clf;
imageplot(f);


Define start and end points $x_0$ and $x_1$ (note that you can use your own points).

In [5]:
x0 = [14;161];
x1 = [293;148];


The metric is defined according to $f$ in order to be low at pixel whose value is close to $f(x)$. A typical example is $$W(x) = \epsilon + \abs{f(x_0)-f(x)}$$ where the value of $\epsilon>0$ should be increased in order to obtain smoother paths.

In [6]:
epsilon = 1e-2;
W = epsilon + abs(f-f(x0(1),x0(2)));


Display the metric $W$.

In [7]:
clf;
imageplot(W);


Set options for the propagation: infinite number of iterations, and stop when the front hits the end point.

In [8]:
options.nb_iter_max = Inf;
options.end_points = x1;


Perform the propagation, so that $D(a,b)$ is the geodesic distance between the pixel $x_1=(a,b)$ and the starting point $x_0$. Note that the function |perform_fast_marching| takes as input the inverse of the metric $1/W(x)$.

In [9]:
[D,S] = perform_fast_marching(1./W, x0, options);


Display the propagated distance map $D$. We display in color the distance map in areas where the front has propagated, and leave in black and white the area where the front did not propagate.

In [10]:
clf;
hold on;
imageplot( convert_distance_color(D,f) );
h = plot(x0(2),x0(1), '.r'); set(h, 'MarkerSize', 25);
h = plot(x1(2),x1(1), '.b'); set(h, 'MarkerSize', 25);


Exercise 1

Using |options.nb_iter_max|, display the progressive propagation. This corresponds to displaying the front $\enscond{x}{D(x) \leq t}$ for various arrival times $t$.

In [11]:
exo1()

In [12]:
%% Insert your code here.


Geodesic Curve Extraction¶

Once the geodesic distance map $D(x)$ to a starting point $x_0$ is computed, the geodesic curve between any point $x_1$ and $x_0$ extracted through gradient descent $$\ga'(t) = - \eta_t \nabla D(\ga(t)),$$ where $\eta_t>0$ controls the parameterization speed of the resulting curve. To obtain unit speed parameterization, one can use $\eta_t = \norm{\nabla D(\ga(t))}^{-1}$.

Recompute the geodesic distance map $D$ on the whole grid.

In [13]:
options.nb_iter_max = Inf;
options.end_points = [];
[D,S] = perform_fast_marching(1./W, x0, options);


Display $D$.

In [14]:
clf;
imageplot(D);
colormap jet(256);


Compute the gradient $G_0(x) = \nabla D(x) \in \RR^2$ of the distance map. Use centered differences.

In [15]:
options.order = 2;


Normalize the gradient to obtained $G(x) = G_0(x)/\norm{G_0(x)}$, in order to have unit speed geodesic curve (parameterized by arc length).

In [16]:
G = G0 ./ repmat( sqrt( sum(G0.^2, 3) ), [1 1 2]);


Display $G$.

In [17]:
clf;
imageplot(G);
colormap jet(256);


The geodesic is then numerically computed using a discretized gradient descent, which defines a discret curve $(\ga_k)_k$ using $$\ga_{k+1} = \ga_k - \tau G(\ga_k)$$ where $\ga_k \in \RR^2$ is an approximation of $\ga(t)$ at time $t=k\tau$, and the step size $\tau>0$ should be small enough.

Step size $\tau$ for the gradient descent.

In [18]:
tau = .8;


Initialize the path with the ending point.

In [19]:
gamma = x1;


Define a shortcut to interpolate $G$ at a 2-D points. Warning: the |interp2| switches the role of the axis ...

In [20]:
Geval = @(G,x)[interp2(1:n,1:n,G(:,:,1),x(2),x(1)); ...
interp2(1:n,1:n,G(:,:,2),x(2),x(1)) ];


Compute the gradient at the last point in the path, using interpolation.

In [21]:
g = Geval(G, gamma(:,end));


Perform the descent and add the new point to the path.

In [22]:
gamma(:,end+1) = gamma(:,end) - tau*g;


Exercise 2

Perform the full geodesic path extraction by iterating the gradient descent. You must be very careful when the path become close to $x_0$, because the distance function is not differentiable at this point. You must stop the iteration when the path is close to $x_0$.

In [23]:
exo2()

In [24]:
%% Insert your code here.


Display the curve on the image background.

In [25]:
clf; hold on;
imageplot(f);
h = plot(gamma(2,:),gamma(1,:), '.b'); set(h, 'LineWidth', 2);
h = plot(x0(2),x0(1), '.r'); set(h, 'MarkerSize', 25);
h = plot(x1(2),x1(1), '.b'); set(h, 'MarkerSize', 25);
axis ij;