1 #include <algorithm>
2 #include <cmath>
3 #include "vil_distance_transform.h"
4 //:
5 // \file
6 // \brief Compute distance function
7 // \author Tim Cootes
8 
9 #include "vil/vil_fill.h"
10 #ifdef _MSC_VER
11 #  include "vcl_msvc_warnings.h"
12 #endif
13 #include <cassert>
14 
15 //: Compute distance function from zeros in original image
16 //  Image is assumed to be filled with max_dist where there
17 //  is background, and zero at the places of interest.
18 //  On exit, the values are the 8-connected distance to the
19 //  nearest original zero region.
20 void
vil_distance_transform(vil_image_view<float> & image)21 vil_distance_transform(vil_image_view<float> & image)
22 {
23   // Low to high pass
24   vil_distance_transform_one_way(image);
25 
26   // Flip to achieve high to low pass
27   // Don't use vil_flip* as they assume const images.
28   unsigned ni = image.ni(), nj = image.nj();
29   vil_image_view<float> flip_image(
30     image.memory_chunk(), &image(ni - 1, nj - 1), ni, nj, 1, -image.istep(), -image.jstep(), image.nplanes());
31   vil_distance_transform_one_way(flip_image);
32 }
33 
34 //: Compute directed distance function from zeros in original image
35 //  Image is assumed to be filled with max_dist where there
36 //  is background, and zero at the places of interest.
37 //  On exit, the values are the 8-connected distance to the
38 //  nearest original zero region above or to the left of current point.
39 //  One pass of distance transform, going from low to high i,j.
40 void
vil_distance_transform_one_way(vil_image_view<float> & image)41 vil_distance_transform_one_way(vil_image_view<float> & image)
42 {
43   assert(image.nplanes() == 1);
44   unsigned ni = image.ni();
45   unsigned nj = image.nj();
46   unsigned ni1 = ni - 1;
47   std::ptrdiff_t istep = image.istep(), jstep = image.jstep();
48   std::ptrdiff_t o1 = -istep, o2 = -jstep - istep, o3 = -jstep, o4 = istep - jstep;
49   float * row0 = image.top_left_ptr();
50 
51   const float sqrt2 = std::sqrt(2.0f);
52 
53   // Process the first row
54   float * p0 = row0 + istep;
55   for (unsigned i = 1; i < ni; ++i, p0 += istep)
56   {
57     *p0 = std::min(p0[-istep] + 1.0f, *p0);
58   }
59 
60   row0 += jstep; // Move to next row
61 
62   // Process each subsequent row from low to high values of j
63   for (unsigned j = 1; j < nj; ++j, row0 += jstep)
64   {
65     // Check first element against first two in previous row
66     *row0 = std::min(row0[o3] + 1.0f, *row0);
67     *row0 = std::min(row0[o4] + sqrt2, *row0);
68 
69     float * p0 = row0 + istep;
70     for (unsigned i = 1; i < ni1; ++i, p0 += istep)
71     {
72       *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
73       *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
74       *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
75       *p0 = std::min(p0[o4] + sqrt2, *p0); // (1,-1)
76     }
77 
78     // Check last element in row
79     *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
80     *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
81     *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
82   }
83 }
84 
85 //: Compute distance function from true elements in mask
86 //  On exit, the values are the 8-connected distance to the
87 //  nearest original zero region (or max_dist, if that is smaller).
88 void
vil_distance_transform(const vil_image_view<bool> & mask,vil_image_view<float> & distance_image,float max_dist)89 vil_distance_transform(const vil_image_view<bool> & mask, vil_image_view<float> & distance_image, float max_dist)
90 {
91   distance_image.set_size(mask.ni(), mask.nj());
92   distance_image.fill(max_dist);
93   vil_fill_mask(distance_image, mask, 0.0f);
94 
95   vil_distance_transform(distance_image);
96 }
97 
98 //: Distance function, using neighbours +/-2 in x,y
99 //  More accurate than vil_distance_function_one_way
100 void
vil_distance_transform_r2_one_way(vil_image_view<float> & image)101 vil_distance_transform_r2_one_way(vil_image_view<float> & image)
102 {
103   assert(image.nplanes() == 1);
104   unsigned ni = image.ni();
105   unsigned nj = image.nj();
106   unsigned ni2 = ni - 2;
107   std::ptrdiff_t istep = image.istep(), jstep = image.jstep();
108 
109   //   Kernel defining points to consider (relative to XX)
110   //   -- o6 -- o7 --
111   //   o5 o2 o3 o4 o8
112   //   -- o1 XX -- --
113   std::ptrdiff_t o1 = -istep, o2 = -jstep - istep;
114   std::ptrdiff_t o3 = -jstep, o4 = istep - jstep;
115   std::ptrdiff_t o5 = -2 * istep - jstep;
116   std::ptrdiff_t o6 = -istep - 2 * jstep;
117   std::ptrdiff_t o7 = istep - 2 * jstep;
118   std::ptrdiff_t o8 = 2 * istep - jstep;
119 
120   float * row0 = image.top_left_ptr();
121 
122   const float sqrt2 = std::sqrt(2.0f);
123   const float sqrt5 = std::sqrt(5.0f);
124 
125   // Process the first row
126   float * p0 = row0 + istep;
127   for (unsigned i = 1; i < ni; ++i, p0 += istep)
128   {
129     *p0 = std::min(p0[-istep] + 1.0f, *p0);
130   }
131 
132   row0 += jstep; // Move to next row
133 
134   // ==== Process second row ====
135   // Check first element against elements in previous row
136   *row0 = std::min(row0[o3] + 1.0f, *row0);  // (0,-1)
137   *row0 = std::min(row0[o4] + sqrt5, *row0); // (1,-1)
138   *row0 = std::min(row0[o8] + sqrt5, *row0); // (2,-1)
139 
140   p0 = row0 + istep;                   // Move to element 1
141   *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
142   *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
143   *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
144   *p0 = std::min(p0[o4] + sqrt2, *p0); // (1,-1)
145   *p0 = std::min(p0[o8] + sqrt5, *p0); // (2,-1)
146 
147   p0 += istep; // Move to element 2
148   for (unsigned i = 2; i < ni2; ++i, p0 += istep)
149   {
150     *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
151     *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
152     *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
153     *p0 = std::min(p0[o4] + sqrt2, *p0); // (1,-1)
154     *p0 = std::min(p0[o5] + sqrt5, *p0); // (-2,-1)
155     *p0 = std::min(p0[o8] + sqrt5, *p0); // (2,-1)
156   }
157 
158   // Check element ni-2
159   *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
160   *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
161   *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
162   *p0 = std::min(p0[o4] + sqrt2, *p0); // (1,-1)
163   *p0 = std::min(p0[o5] + sqrt5, *p0); // (-2,-1)
164 
165   p0 += istep; // Move to element ni-1
166   // Check last element in row
167   *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
168   *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
169   *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
170   *p0 = std::min(p0[o5] + sqrt5, *p0); // (-2,-1)
171 
172   row0 += jstep; // Move to next row (2)
173 
174   // Process each subsequent row from low to high values of j
175   for (unsigned j = 2; j < nj; ++j, row0 += jstep)
176   {
177     // Check first element
178     *row0 = std::min(row0[o3] + 1.0f, *row0);  // (0,-1)
179     *row0 = std::min(row0[o4] + sqrt2, *row0); // (1,-1)
180     *row0 = std::min(row0[o7] + sqrt5, *row0); // (1,-2)
181     *row0 = std::min(row0[o8] + sqrt5, *row0); // (2,-1)
182 
183     float * p0 = row0 + istep; // Element 1
184     // Check second element, allowing for boundary conditions
185     *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
186     *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
187     *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
188     *p0 = std::min(p0[o4] + sqrt2, *p0); // (1,-1)
189     *p0 = std::min(p0[o6] + sqrt5, *p0); // (-1,-2)
190     *p0 = std::min(p0[o7] + sqrt5, *p0); // (1,-2)
191     *p0 = std::min(p0[o8] + sqrt5, *p0); // (2,-1)
192 
193     p0 += istep; // Move to next element (2)
194     for (unsigned i = 2; i < ni2; ++i, p0 += istep)
195     {
196       *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
197       *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
198       *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
199       *p0 = std::min(p0[o4] + sqrt2, *p0); // (1,-1)
200       *p0 = std::min(p0[o5] + sqrt5, *p0); // (-2,-1)
201       *p0 = std::min(p0[o6] + sqrt5, *p0); // (-1,-2)
202       *p0 = std::min(p0[o7] + sqrt5, *p0); // (1,-2)
203       *p0 = std::min(p0[o8] + sqrt5, *p0); // (2,-1)
204     }
205     // p0 points to element (ni-2,y)
206 
207     // Check last but one element in row
208     *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
209     *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
210     *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
211     *p0 = std::min(p0[o4] + sqrt2, *p0); // (1,-1)
212     *p0 = std::min(p0[o5] + sqrt5, *p0); // (-2,-1)
213     *p0 = std::min(p0[o6] + sqrt5, *p0); // (-1,-2)
214     *p0 = std::min(p0[o7] + sqrt5, *p0); // (1,-2)
215 
216     p0 += istep; // Move to last element (ni-1,y)
217     // Process last element in row
218     *p0 = std::min(p0[o1] + 1.0f, *p0);  // (-1,0)
219     *p0 = std::min(p0[o2] + sqrt2, *p0); // (-1,-1)
220     *p0 = std::min(p0[o3] + 1.0f, *p0);  // (0,-1)
221     *p0 = std::min(p0[o5] + sqrt5, *p0); // (-2,-1)
222     *p0 = std::min(p0[o6] + sqrt5, *p0); // (-1,-2)
223   }
224 }
225 
226 //: Compute distance function from zeros in original image
227 //  Image is assumed to be filled with max_dist where there
228 //  is background, and zero at the places of interest.
229 //  On exit, the values are the 24-connected distance to the
230 //  nearest original zero region. (ie considers neighbours in
231 //  a +/-2 pixel region around each point).
232 //  More accurate than vil_distance_transform(image), but
233 //  approximately twice the processing required.
234 void
vil_distance_transform_r2(vil_image_view<float> & image)235 vil_distance_transform_r2(vil_image_view<float> & image)
236 {
237   // Low to high pass
238   vil_distance_transform_r2_one_way(image);
239 
240   // Flip to achieve high to low pass
241   // Don't use vil_flip* as they assume const images.
242   unsigned ni = image.ni(), nj = image.nj();
243   vil_image_view<float> flip_image(
244     image.memory_chunk(), &image(ni - 1, nj - 1), ni, nj, 1, -image.istep(), -image.jstep(), image.nplanes());
245   vil_distance_transform_r2_one_way(flip_image);
246 }
247