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