1 /* Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #ifndef TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_UTILS_H_
17 #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_UTILS_H_
18
19 #include <stdint.h>
20
21 #include "tensorflow/tools/android/test/jni/object_tracking/geom.h"
22 #include "tensorflow/tools/android/test/jni/object_tracking/image-inl.h"
23 #include "tensorflow/tools/android/test/jni/object_tracking/image.h"
24 #include "tensorflow/tools/android/test/jni/object_tracking/utils.h"
25
26 namespace tf_tracking {
27
GetUV(const uint8_t * const input,Image<uint8_t> * const u,Image<uint8_t> * const v)28 inline void GetUV(const uint8_t* const input, Image<uint8_t>* const u,
29 Image<uint8_t>* const v) {
30 const uint8_t* pUV = input;
31
32 for (int row = 0; row < u->GetHeight(); ++row) {
33 uint8_t* u_curr = (*u)[row];
34 uint8_t* v_curr = (*v)[row];
35 for (int col = 0; col < u->GetWidth(); ++col) {
36 #ifdef __APPLE__
37 *u_curr++ = *pUV++;
38 *v_curr++ = *pUV++;
39 #else
40 *v_curr++ = *pUV++;
41 *u_curr++ = *pUV++;
42 #endif
43 }
44 }
45 }
46
47 // Marks every point within a circle of a given radius on the given boolean
48 // image true.
49 template <typename U>
MarkImage(const int x,const int y,const int radius,Image<U> * const img)50 inline static void MarkImage(const int x, const int y, const int radius,
51 Image<U>* const img) {
52 SCHECK(img->ValidPixel(x, y), "Marking invalid pixel in image! %d, %d", x, y);
53
54 // Precomputed for efficiency.
55 const int squared_radius = Square(radius);
56
57 // Mark every row in the circle.
58 for (int d_y = 0; d_y <= radius; ++d_y) {
59 const int squared_y_dist = Square(d_y);
60
61 const int min_y = MAX(y - d_y, 0);
62 const int max_y = MIN(y + d_y, img->height_less_one_);
63
64 // The max d_x of the circle must be strictly greater or equal to
65 // radius - d_y for any positive d_y. Thus, starting from radius - d_y will
66 // reduce the number of iterations required as compared to starting from
67 // either 0 and counting up or radius and counting down.
68 for (int d_x = radius - d_y; d_x <= radius; ++d_x) {
69 // The first time this criteria is met, we know the width of the circle at
70 // this row (without using sqrt).
71 if (squared_y_dist + Square(d_x) >= squared_radius) {
72 const int min_x = MAX(x - d_x, 0);
73 const int max_x = MIN(x + d_x, img->width_less_one_);
74
75 // Mark both above and below the center row.
76 bool* const top_row_start = (*img)[min_y] + min_x;
77 bool* const bottom_row_start = (*img)[max_y] + min_x;
78
79 const int x_width = max_x - min_x + 1;
80 memset(top_row_start, true, sizeof(*top_row_start) * x_width);
81 memset(bottom_row_start, true, sizeof(*bottom_row_start) * x_width);
82
83 // This row is marked, time to move on to the next row.
84 break;
85 }
86 }
87 }
88 }
89
90 #ifdef __ARM_NEON
91 void CalculateGNeon(
92 const float* const vals_x, const float* const vals_y,
93 const int num_vals, float* const G);
94 #endif
95
96 // Puts the image gradient matrix about a pixel into the 2x2 float array G.
97 // vals_x should be an array of the window x gradient values, whose indices
98 // can be in any order but are parallel to the vals_y entries.
99 // See http://robots.stanford.edu/cs223b04/algo_tracking.pdf for more details.
CalculateG(const float * const vals_x,const float * const vals_y,const int num_vals,float * const G)100 inline void CalculateG(const float* const vals_x, const float* const vals_y,
101 const int num_vals, float* const G) {
102 #ifdef __ARM_NEON
103 CalculateGNeon(vals_x, vals_y, num_vals, G);
104 return;
105 #endif
106
107 // Non-accelerated version.
108 for (int i = 0; i < num_vals; ++i) {
109 G[0] += Square(vals_x[i]);
110 G[1] += vals_x[i] * vals_y[i];
111 G[3] += Square(vals_y[i]);
112 }
113
114 // The matrix is symmetric, so this is a given.
115 G[2] = G[1];
116 }
117
CalculateGInt16(const int16_t * const vals_x,const int16_t * const vals_y,const int num_vals,int * const G)118 inline void CalculateGInt16(const int16_t* const vals_x,
119 const int16_t* const vals_y, const int num_vals,
120 int* const G) {
121 // Non-accelerated version.
122 for (int i = 0; i < num_vals; ++i) {
123 G[0] += Square(vals_x[i]);
124 G[1] += vals_x[i] * vals_y[i];
125 G[3] += Square(vals_y[i]);
126 }
127
128 // The matrix is symmetric, so this is a given.
129 G[2] = G[1];
130 }
131
132
133 // Puts the image gradient matrix about a pixel into the 2x2 float array G.
134 // Looks up interpolated pixels, then calls above method for implementation.
CalculateG(const int window_radius,const float center_x,const float center_y,const Image<int32_t> & I_x,const Image<int32_t> & I_y,float * const G)135 inline void CalculateG(const int window_radius, const float center_x,
136 const float center_y, const Image<int32_t>& I_x,
137 const Image<int32_t>& I_y, float* const G) {
138 SCHECK(I_x.ValidPixel(center_x, center_y), "Problem in calculateG!");
139
140 // Hardcoded to allow for a max window radius of 5 (9 pixels x 9 pixels).
141 static const int kMaxWindowRadius = 5;
142 SCHECK(window_radius <= kMaxWindowRadius,
143 "Window %d > %d!", window_radius, kMaxWindowRadius);
144
145 // Diameter of window is 2 * radius + 1 for center pixel.
146 static const int kWindowBufferSize =
147 (kMaxWindowRadius * 2 + 1) * (kMaxWindowRadius * 2 + 1);
148
149 // Preallocate buffers statically for efficiency.
150 static int16_t vals_x[kWindowBufferSize];
151 static int16_t vals_y[kWindowBufferSize];
152
153 const int src_left_fixed = RealToFixed1616(center_x - window_radius);
154 const int src_top_fixed = RealToFixed1616(center_y - window_radius);
155
156 int16_t* vals_x_ptr = vals_x;
157 int16_t* vals_y_ptr = vals_y;
158
159 const int window_size = 2 * window_radius + 1;
160 for (int y = 0; y < window_size; ++y) {
161 const int fp_y = src_top_fixed + (y << 16);
162
163 for (int x = 0; x < window_size; ++x) {
164 const int fp_x = src_left_fixed + (x << 16);
165
166 *vals_x_ptr++ = I_x.GetPixelInterpFixed1616(fp_x, fp_y);
167 *vals_y_ptr++ = I_y.GetPixelInterpFixed1616(fp_x, fp_y);
168 }
169 }
170
171 int32_t g_temp[] = {0, 0, 0, 0};
172 CalculateGInt16(vals_x, vals_y, window_size * window_size, g_temp);
173
174 for (int i = 0; i < 4; ++i) {
175 G[i] = g_temp[i];
176 }
177 }
178
ImageCrossCorrelation(const Image<float> & image1,const Image<float> & image2,const int x_offset,const int y_offset)179 inline float ImageCrossCorrelation(const Image<float>& image1,
180 const Image<float>& image2,
181 const int x_offset, const int y_offset) {
182 SCHECK(image1.GetWidth() == image2.GetWidth() &&
183 image1.GetHeight() == image2.GetHeight(),
184 "Dimension mismatch! %dx%d vs %dx%d",
185 image1.GetWidth(), image1.GetHeight(),
186 image2.GetWidth(), image2.GetHeight());
187
188 const int num_pixels = image1.GetWidth() * image1.GetHeight();
189 const float* data1 = image1.data();
190 const float* data2 = image2.data();
191 return ComputeCrossCorrelation(data1, data2, num_pixels);
192 }
193
194 // Copies an arbitrary region of an image to another (floating point)
195 // image, scaling as it goes using bilinear interpolation.
CopyArea(const Image<uint8_t> & image,const BoundingBox & area_to_copy,Image<float> * const patch_image)196 inline void CopyArea(const Image<uint8_t>& image,
197 const BoundingBox& area_to_copy,
198 Image<float>* const patch_image) {
199 VLOG(2) << "Copying from: " << area_to_copy << std::endl;
200
201 const int patch_width = patch_image->GetWidth();
202 const int patch_height = patch_image->GetHeight();
203
204 const float x_dist_between_samples = patch_width > 0 ?
205 area_to_copy.GetWidth() / (patch_width - 1) : 0;
206
207 const float y_dist_between_samples = patch_height > 0 ?
208 area_to_copy.GetHeight() / (patch_height - 1) : 0;
209
210 for (int y_index = 0; y_index < patch_height; ++y_index) {
211 const float sample_y =
212 y_index * y_dist_between_samples + area_to_copy.top_;
213
214 for (int x_index = 0; x_index < patch_width; ++x_index) {
215 const float sample_x =
216 x_index * x_dist_between_samples + area_to_copy.left_;
217
218 if (image.ValidInterpPixel(sample_x, sample_y)) {
219 // TODO(andrewharp): Do area averaging when downsampling.
220 (*patch_image)[y_index][x_index] =
221 image.GetPixelInterp(sample_x, sample_y);
222 } else {
223 (*patch_image)[y_index][x_index] = -1.0f;
224 }
225 }
226 }
227 }
228
229
230 // Takes a floating point image and normalizes it in-place.
231 //
232 // First, negative values will be set to the mean of the non-negative pixels
233 // in the image.
234 //
235 // Then, the resulting will be normalized such that it has mean value of 0.0 and
236 // a standard deviation of 1.0.
NormalizeImage(Image<float> * const image)237 inline void NormalizeImage(Image<float>* const image) {
238 const float* const data_ptr = image->data();
239
240 // Copy only the non-negative values to some temp memory.
241 float running_sum = 0.0f;
242 int num_data_gte_zero = 0;
243 {
244 float* const curr_data = (*image)[0];
245 for (int i = 0; i < image->data_size_; ++i) {
246 if (curr_data[i] >= 0.0f) {
247 running_sum += curr_data[i];
248 ++num_data_gte_zero;
249 } else {
250 curr_data[i] = -1.0f;
251 }
252 }
253 }
254
255 // If none of the pixels are valid, just set the entire thing to 0.0f.
256 if (num_data_gte_zero == 0) {
257 image->Clear(0.0f);
258 return;
259 }
260
261 const float corrected_mean = running_sum / num_data_gte_zero;
262
263 float* curr_data = (*image)[0];
264 for (int i = 0; i < image->data_size_; ++i) {
265 const float curr_val = *curr_data;
266 *curr_data++ = curr_val < 0 ? 0 : curr_val - corrected_mean;
267 }
268
269 const float std_dev = ComputeStdDev(data_ptr, image->data_size_, 0.0f);
270
271 if (std_dev > 0.0f) {
272 curr_data = (*image)[0];
273 for (int i = 0; i < image->data_size_; ++i) {
274 *curr_data++ /= std_dev;
275 }
276
277 #ifdef SANITY_CHECKS
278 LOGV("corrected_mean: %1.2f std_dev: %1.2f", corrected_mean, std_dev);
279 const float correlation =
280 ComputeCrossCorrelation(image->data(),
281 image->data(),
282 image->data_size_);
283
284 if (std::abs(correlation - 1.0f) > EPSILON) {
285 LOG(ERROR) << "Bad image!" << std::endl;
286 LOG(ERROR) << *image << std::endl;
287 }
288
289 SCHECK(std::abs(correlation - 1.0f) < EPSILON,
290 "Correlation wasn't 1.0f: %.10f", correlation);
291 #endif
292 }
293 }
294
295 } // namespace tf_tracking
296
297 #endif // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_UTILS_H_
298