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_INL_H_
17 #define TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_INL_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.h"
23 #include "tensorflow/tools/android/test/jni/object_tracking/utils.h"
24 
25 namespace tf_tracking {
26 
27 template <typename T>
Image(const int width,const int height)28 Image<T>::Image(const int width, const int height)
29     : width_less_one_(width - 1),
30       height_less_one_(height - 1),
31       data_size_(width * height),
32       own_data_(true),
33       width_(width),
34       height_(height),
35       stride_(width) {
36   Allocate();
37 }
38 
39 template <typename T>
Image(const Size & size)40 Image<T>::Image(const Size& size)
41     : width_less_one_(size.width - 1),
42       height_less_one_(size.height - 1),
43       data_size_(size.width * size.height),
44       own_data_(true),
45       width_(size.width),
46       height_(size.height),
47       stride_(size.width) {
48   Allocate();
49 }
50 
51 // Constructor that creates an image from preallocated data.
52 // Note: The image takes ownership of the data lifecycle, unless own_data is
53 // set to false.
54 template <typename T>
Image(const int width,const int height,T * const image_data,const bool own_data)55 Image<T>::Image(const int width, const int height, T* const image_data,
56       const bool own_data) :
57     width_less_one_(width - 1),
58     height_less_one_(height - 1),
59     data_size_(width * height),
60     own_data_(own_data),
61     width_(width),
62     height_(height),
63     stride_(width) {
64   image_data_ = image_data;
65   SCHECK(image_data_ != NULL, "Can't create image with NULL data!");
66 }
67 
68 template <typename T>
~Image()69 Image<T>::~Image() {
70   if (own_data_) {
71     delete[] image_data_;
72   }
73   image_data_ = NULL;
74 }
75 
76 template<typename T>
77 template<class DstType>
ExtractPatchAtSubpixelFixed1616(const int fp_x,const int fp_y,const int patchwidth,const int patchheight,DstType * to_data)78 bool Image<T>::ExtractPatchAtSubpixelFixed1616(const int fp_x,
79                                                const int fp_y,
80                                                const int patchwidth,
81                                                const int patchheight,
82                                                DstType* to_data) const {
83   // Calculate weights.
84   const int trunc_x = fp_x >> 16;
85   const int trunc_y = fp_y >> 16;
86 
87   if (trunc_x < 0 || trunc_y < 0 ||
88       (trunc_x + patchwidth) >= width_less_one_ ||
89       (trunc_y + patchheight) >= height_less_one_) {
90     return false;
91   }
92 
93   // Now walk over destination patch and fill from interpolated source image.
94   for (int y = 0; y < patchheight; ++y, to_data += patchwidth) {
95     for (int x = 0; x < patchwidth; ++x) {
96       to_data[x] =
97           static_cast<DstType>(GetPixelInterpFixed1616(fp_x + (x << 16),
98                                                        fp_y + (y << 16)));
99     }
100   }
101 
102   return true;
103 }
104 
105 template <typename T>
Crop(const int left,const int top,const int right,const int bottom)106 Image<T>* Image<T>::Crop(
107     const int left, const int top, const int right, const int bottom) const {
108   SCHECK(left >= 0 && left < width_, "out of bounds at %d!", left);
109   SCHECK(right >= 0 && right < width_, "out of bounds at %d!", right);
110   SCHECK(top >= 0 && top < height_, "out of bounds at %d!", top);
111   SCHECK(bottom >= 0 && bottom < height_, "out of bounds at %d!", bottom);
112 
113   SCHECK(left <= right, "mismatch!");
114   SCHECK(top <= bottom, "mismatch!");
115 
116   const int new_width = right - left + 1;
117   const int new_height = bottom - top + 1;
118 
119   Image<T>* const cropped_image = new Image(new_width, new_height);
120 
121   for (int y = 0; y < new_height; ++y) {
122     memcpy((*cropped_image)[y], ((*this)[y + top] + left),
123            new_width * sizeof(T));
124   }
125 
126   return cropped_image;
127 }
128 
129 template <typename T>
GetPixelInterp(const float x,const float y)130 inline float Image<T>::GetPixelInterp(const float x, const float y) const {
131   // Do int conversion one time.
132   const int floored_x = static_cast<int>(x);
133   const int floored_y = static_cast<int>(y);
134 
135   // Note: it might be the case that the *_[min|max] values are clipped, and
136   // these (the a b c d vals) aren't (for speed purposes), but that doesn't
137   // matter. We'll just be blending the pixel with itself in that case anyway.
138   const float b = x - floored_x;
139   const float a = 1.0f - b;
140 
141   const float d = y - floored_y;
142   const float c = 1.0f - d;
143 
144   SCHECK(ValidInterpPixel(x, y),
145         "x or y out of bounds! %.2f [0 - %d), %.2f [0 - %d)",
146         x, width_less_one_, y, height_less_one_);
147 
148   const T* const pix_ptr = (*this)[floored_y] + floored_x;
149 
150   // Get the pixel values surrounding this point.
151   const T& p1 = pix_ptr[0];
152   const T& p2 = pix_ptr[1];
153   const T& p3 = pix_ptr[width_];
154   const T& p4 = pix_ptr[width_ + 1];
155 
156   // Simple bilinear interpolation between four reference pixels.
157   // If x is the value requested:
158   //     a  b
159   //   -------
160   // c |p1 p2|
161   //   |  x  |
162   // d |p3 p4|
163   //   -------
164   return  c * ((a * p1) + (b * p2)) +
165           d * ((a * p3) + (b * p4));
166 }
167 
168 
169 template <typename T>
GetPixelInterpFixed1616(const int fp_x_whole,const int fp_y_whole)170 inline T Image<T>::GetPixelInterpFixed1616(
171     const int fp_x_whole, const int fp_y_whole) const {
172   static const int kFixedPointOne = 0x00010000;
173   static const int kFixedPointHalf = 0x00008000;
174   static const int kFixedPointTruncateMask = 0xFFFF0000;
175 
176   int trunc_x = fp_x_whole & kFixedPointTruncateMask;
177   int trunc_y = fp_y_whole & kFixedPointTruncateMask;
178   const int fp_x = fp_x_whole - trunc_x;
179   const int fp_y = fp_y_whole - trunc_y;
180 
181   // Scale the truncated values back to regular ints.
182   trunc_x >>= 16;
183   trunc_y >>= 16;
184 
185   const int one_minus_fp_x = kFixedPointOne - fp_x;
186   const int one_minus_fp_y = kFixedPointOne - fp_y;
187 
188   const T* trunc_start = (*this)[trunc_y] + trunc_x;
189 
190   const T a = trunc_start[0];
191   const T b = trunc_start[1];
192   const T c = trunc_start[stride_];
193   const T d = trunc_start[stride_ + 1];
194 
195   return (
196       (one_minus_fp_y * static_cast<int64_t>(one_minus_fp_x * a + fp_x * b) +
197        fp_y * static_cast<int64_t>(one_minus_fp_x * c + fp_x * d) +
198        kFixedPointHalf) >>
199       32);
200 }
201 
202 template <typename T>
ValidPixel(const int x,const int y)203 inline bool Image<T>::ValidPixel(const int x, const int y) const {
204   return InRange(x, ZERO, width_less_one_) &&
205          InRange(y, ZERO, height_less_one_);
206 }
207 
208 template <typename T>
GetContainingBox()209 inline BoundingBox Image<T>::GetContainingBox() const {
210   return BoundingBox(
211       0, 0, width_less_one_ - EPSILON, height_less_one_ - EPSILON);
212 }
213 
214 template <typename T>
Contains(const BoundingBox & bounding_box)215 inline bool Image<T>::Contains(const BoundingBox& bounding_box) const {
216   // TODO(andrewharp): Come up with a more elegant way of ensuring that bounds
217   // are ok.
218   return GetContainingBox().Contains(bounding_box);
219 }
220 
221 template <typename T>
ValidInterpPixel(const float x,const float y)222 inline bool Image<T>::ValidInterpPixel(const float x, const float y) const {
223   // Exclusive of max because we can be more efficient if we don't handle
224   // interpolating on or past the last pixel.
225   return (x >= ZERO) && (x < width_less_one_) &&
226          (y >= ZERO) && (y < height_less_one_);
227 }
228 
229 template <typename T>
DownsampleAveraged(const T * const original,const int stride,const int factor)230 void Image<T>::DownsampleAveraged(const T* const original, const int stride,
231                                   const int factor) {
232 #ifdef __ARM_NEON
233   if (factor == 4 || factor == 2) {
234     DownsampleAveragedNeon(original, stride, factor);
235     return;
236   }
237 #endif
238 
239   // TODO(andrewharp): delete or enable this for non-uint8_t downsamples.
240   const int pixels_per_block = factor * factor;
241 
242   // For every pixel in resulting image.
243   for (int y = 0; y < height_; ++y) {
244     const int orig_y = y * factor;
245     const int y_bound = orig_y + factor;
246 
247     // Sum up the original pixels.
248     for (int x = 0; x < width_; ++x) {
249       const int orig_x = x * factor;
250       const int x_bound = orig_x + factor;
251 
252       // Making this int32_t because type U or T might overflow.
253       int32_t pixel_sum = 0;
254 
255       // Grab all the pixels that make up this pixel.
256       for (int curr_y = orig_y; curr_y < y_bound; ++curr_y) {
257         const T* p = original + curr_y * stride + orig_x;
258 
259         for (int curr_x = orig_x; curr_x < x_bound; ++curr_x) {
260           pixel_sum += *p++;
261         }
262       }
263 
264       (*this)[y][x] = pixel_sum / pixels_per_block;
265     }
266   }
267 }
268 
269 template <typename T>
DownsampleInterpolateNearest(const Image<T> & original)270 void Image<T>::DownsampleInterpolateNearest(const Image<T>& original) {
271   // Calculating the scaling factors based on target image size.
272   const float factor_x = static_cast<float>(original.GetWidth()) /
273       static_cast<float>(width_);
274   const float factor_y = static_cast<float>(original.GetHeight()) /
275       static_cast<float>(height_);
276 
277   // Calculating initial offset in x-axis.
278   const float offset_x = 0.5f * (original.GetWidth() - width_) / width_;
279 
280   // Calculating initial offset in y-axis.
281   const float offset_y = 0.5f * (original.GetHeight() - height_) / height_;
282 
283   float orig_y = offset_y;
284 
285   // For every pixel in resulting image.
286   for (int y = 0; y < height_; ++y) {
287     float orig_x = offset_x;
288 
289     // Finding nearest pixel on y-axis.
290     const int nearest_y = static_cast<int>(orig_y + 0.5f);
291     const T* row_data = original[nearest_y];
292 
293     T* pixel_ptr = (*this)[y];
294 
295     for (int x = 0; x < width_; ++x) {
296       // Finding nearest pixel on x-axis.
297       const int nearest_x = static_cast<int>(orig_x + 0.5f);
298 
299       *pixel_ptr++ = row_data[nearest_x];
300 
301       orig_x += factor_x;
302     }
303 
304     orig_y += factor_y;
305   }
306 }
307 
308 template <typename T>
DownsampleInterpolateLinear(const Image<T> & original)309 void Image<T>::DownsampleInterpolateLinear(const Image<T>& original) {
310   // TODO(andrewharp): Turn this into a general compare sizes/bulk
311   // copy method.
312   if (original.GetWidth() == GetWidth() &&
313       original.GetHeight() == GetHeight() &&
314       original.stride() == stride()) {
315     memcpy(image_data_, original.data(), data_size_ * sizeof(T));
316     return;
317   }
318 
319   // Calculating the scaling factors based on target image size.
320   const float factor_x = static_cast<float>(original.GetWidth()) /
321       static_cast<float>(width_);
322   const float factor_y = static_cast<float>(original.GetHeight()) /
323       static_cast<float>(height_);
324 
325   // Calculating initial offset in x-axis.
326   const float offset_x = 0;
327   const int offset_x_fp = RealToFixed1616(offset_x);
328 
329   // Calculating initial offset in y-axis.
330   const float offset_y = 0;
331   const int offset_y_fp = RealToFixed1616(offset_y);
332 
333   // Get the fixed point scaling factor value.
334   // Shift by 8 so we can fit everything into a 4 byte int later for speed
335   // reasons. This means the precision is limited to 1 / 256th of a pixel,
336   // but this should be good enough.
337   const int factor_x_fp = RealToFixed1616(factor_x) >> 8;
338   const int factor_y_fp = RealToFixed1616(factor_y) >> 8;
339 
340   int src_y_fp = offset_y_fp >> 8;
341 
342   static const int kFixedPointOne8 = 0x00000100;
343   static const int kFixedPointHalf8 = 0x00000080;
344   static const int kFixedPointTruncateMask8 = 0xFFFFFF00;
345 
346   // For every pixel in resulting image.
347   for (int y = 0; y < height_; ++y) {
348     int src_x_fp = offset_x_fp >> 8;
349 
350     int trunc_y = src_y_fp & kFixedPointTruncateMask8;
351     const int fp_y = src_y_fp - trunc_y;
352 
353     // Scale the truncated values back to regular ints.
354     trunc_y >>= 8;
355 
356     const int one_minus_fp_y = kFixedPointOne8 - fp_y;
357 
358     T* pixel_ptr = (*this)[y];
359 
360     // Make sure not to read from an invalid row.
361     const int trunc_y_b = MIN(original.height_less_one_, trunc_y + 1);
362     const T* other_top_ptr = original[trunc_y];
363     const T* other_bot_ptr = original[trunc_y_b];
364 
365     int last_trunc_x = -1;
366     int trunc_x = -1;
367 
368     T a = 0;
369     T b = 0;
370     T c = 0;
371     T d = 0;
372 
373     for (int x = 0; x < width_; ++x) {
374       trunc_x = src_x_fp & kFixedPointTruncateMask8;
375 
376       const int fp_x = (src_x_fp - trunc_x) >> 8;
377 
378       // Scale the truncated values back to regular ints.
379       trunc_x >>= 8;
380 
381       // It's possible we're reading from the same pixels
382       if (trunc_x != last_trunc_x) {
383         // Make sure not to read from an invalid column.
384         const int trunc_x_b = MIN(original.width_less_one_, trunc_x + 1);
385         a = other_top_ptr[trunc_x];
386         b = other_top_ptr[trunc_x_b];
387         c = other_bot_ptr[trunc_x];
388         d = other_bot_ptr[trunc_x_b];
389         last_trunc_x = trunc_x;
390       }
391 
392       const int one_minus_fp_x = kFixedPointOne8 - fp_x;
393 
394       const int32_t value =
395           ((one_minus_fp_y * one_minus_fp_x * a + fp_x * b) +
396            (fp_y * one_minus_fp_x * c + fp_x * d) + kFixedPointHalf8) >>
397           16;
398 
399       *pixel_ptr++ = value;
400 
401       src_x_fp += factor_x_fp;
402     }
403     src_y_fp += factor_y_fp;
404   }
405 }
406 
407 template <typename T>
DownsampleSmoothed3x3(const Image<T> & original)408 void Image<T>::DownsampleSmoothed3x3(const Image<T>& original) {
409   for (int y = 0; y < height_; ++y) {
410     const int orig_y = Clip(2 * y, ZERO, original.height_less_one_);
411     const int min_y = Clip(orig_y - 1, ZERO, original.height_less_one_);
412     const int max_y = Clip(orig_y + 1, ZERO, original.height_less_one_);
413 
414     for (int x = 0; x < width_; ++x) {
415       const int orig_x = Clip(2 * x, ZERO, original.width_less_one_);
416       const int min_x = Clip(orig_x - 1, ZERO, original.width_less_one_);
417       const int max_x = Clip(orig_x + 1, ZERO, original.width_less_one_);
418 
419       // Center.
420       int32_t pixel_sum = original[orig_y][orig_x] * 4;
421 
422       // Sides.
423       pixel_sum += (original[orig_y][max_x] +
424                     original[orig_y][min_x] +
425                     original[max_y][orig_x] +
426                     original[min_y][orig_x]) * 2;
427 
428       // Diagonals.
429       pixel_sum += (original[min_y][max_x] +
430                     original[min_y][min_x] +
431                     original[max_y][max_x] +
432                     original[max_y][min_x]);
433 
434       (*this)[y][x] = pixel_sum >> 4;  // 16
435     }
436   }
437 }
438 
439 template <typename T>
DownsampleSmoothed5x5(const Image<T> & original)440 void Image<T>::DownsampleSmoothed5x5(const Image<T>& original) {
441   const int max_x = original.width_less_one_;
442   const int max_y = original.height_less_one_;
443 
444   // The JY Bouget paper on Lucas-Kanade recommends a
445   // [1/16 1/4 3/8 1/4 1/16]^2 filter.
446   // This works out to a [1 4 6 4 1]^2 / 256 array, precomputed below.
447   static const int window_radius = 2;
448   static const int window_size = window_radius*2 + 1;
449   static const int window_weights[] = {1,  4,  6,  4, 1,   // 16 +
450                                        4, 16, 24, 16, 4,   // 64 +
451                                        6, 24, 36, 24, 6,   // 96 +
452                                        4, 16, 24, 16, 4,   // 64 +
453                                        1,  4,  6,  4, 1};  // 16 = 256
454 
455   // We'll multiply and sum with the whole numbers first, then divide by
456   // the total weight to normalize at the last moment.
457   for (int y = 0; y < height_; ++y) {
458     for (int x = 0; x < width_; ++x) {
459       int32_t pixel_sum = 0;
460 
461       const int* w = window_weights;
462       const int start_x = Clip((x << 1) - window_radius, ZERO, max_x);
463 
464       // Clip the boundaries to the size of the image.
465       for (int window_y = 0; window_y < window_size; ++window_y) {
466         const int start_y =
467             Clip((y << 1) - window_radius + window_y, ZERO, max_y);
468 
469         const T* p = original[start_y] + start_x;
470 
471         for (int window_x = 0; window_x < window_size; ++window_x) {
472           pixel_sum +=  *p++ * *w++;
473         }
474       }
475 
476       // Conversion to type T will happen here after shifting right 8 bits to
477       // divide by 256.
478       (*this)[y][x] = pixel_sum >> 8;
479     }
480   }
481 }
482 
483 template <typename T>
484 template <typename U>
ScharrPixelX(const Image<U> & original,const int center_x,const int center_y)485 inline T Image<T>::ScharrPixelX(const Image<U>& original,
486                       const int center_x, const int center_y) const {
487   const int min_x = Clip(center_x - 1, ZERO, original.width_less_one_);
488   const int max_x = Clip(center_x + 1, ZERO, original.width_less_one_);
489   const int min_y = Clip(center_y - 1, ZERO, original.height_less_one_);
490   const int max_y = Clip(center_y + 1, ZERO, original.height_less_one_);
491 
492   // Convolution loop unrolled for performance...
493   return (3 * (original[min_y][max_x]
494                + original[max_y][max_x]
495                - original[min_y][min_x]
496                - original[max_y][min_x])
497           + 10 * (original[center_y][max_x]
498                   - original[center_y][min_x])) / 32;
499 }
500 
501 template <typename T>
502 template <typename U>
ScharrPixelY(const Image<U> & original,const int center_x,const int center_y)503 inline T Image<T>::ScharrPixelY(const Image<U>& original,
504                       const int center_x, const int center_y) const {
505   const int min_x = Clip(center_x - 1, 0, original.width_less_one_);
506   const int max_x = Clip(center_x + 1, 0, original.width_less_one_);
507   const int min_y = Clip(center_y - 1, 0, original.height_less_one_);
508   const int max_y = Clip(center_y + 1, 0, original.height_less_one_);
509 
510   // Convolution loop unrolled for performance...
511   return (3 * (original[max_y][min_x]
512                + original[max_y][max_x]
513                - original[min_y][min_x]
514                - original[min_y][max_x])
515           + 10 * (original[max_y][center_x]
516                   - original[min_y][center_x])) / 32;
517 }
518 
519 template <typename T>
520 template <typename U>
ScharrX(const Image<U> & original)521 inline void Image<T>::ScharrX(const Image<U>& original) {
522   for (int y = 0; y < height_; ++y) {
523     for (int x = 0; x < width_; ++x) {
524       SetPixel(x, y, ScharrPixelX(original, x, y));
525     }
526   }
527 }
528 
529 template <typename T>
530 template <typename U>
ScharrY(const Image<U> & original)531 inline void Image<T>::ScharrY(const Image<U>& original) {
532   for (int y = 0; y < height_; ++y) {
533     for (int x = 0; x < width_; ++x) {
534       SetPixel(x, y, ScharrPixelY(original, x, y));
535     }
536   }
537 }
538 
539 template <typename T>
540 template <typename U>
DerivativeX(const Image<U> & original)541 void Image<T>::DerivativeX(const Image<U>& original) {
542   for (int y = 0; y < height_; ++y) {
543     const U* const source_row = original[y];
544     T* const dest_row = (*this)[y];
545 
546     // Compute first pixel. Approximated with forward difference.
547     dest_row[0] = source_row[1] - source_row[0];
548 
549     // All the pixels in between. Central difference method.
550     const U* source_prev_pixel = source_row;
551     T* dest_pixel = dest_row + 1;
552     const U* source_next_pixel = source_row + 2;
553     for (int x = 1; x < width_less_one_; ++x) {
554       *dest_pixel++ = HalfDiff(*source_prev_pixel++, *source_next_pixel++);
555     }
556 
557     // Last pixel. Approximated with backward difference.
558     dest_row[width_less_one_] =
559         source_row[width_less_one_] - source_row[width_less_one_ - 1];
560   }
561 }
562 
563 template <typename T>
564 template <typename U>
DerivativeY(const Image<U> & original)565 void Image<T>::DerivativeY(const Image<U>& original) {
566   const int src_stride = original.stride();
567 
568   // Compute 1st row. Approximated with forward difference.
569   {
570     const U* const src_row = original[0];
571     T* dest_row = (*this)[0];
572     for (int x = 0; x < width_; ++x) {
573       dest_row[x] = src_row[x + src_stride] - src_row[x];
574     }
575   }
576 
577   // Compute all rows in between using central difference.
578   for (int y = 1; y < height_less_one_; ++y) {
579     T* dest_row = (*this)[y];
580 
581     const U* source_prev_pixel = original[y - 1];
582     const U* source_next_pixel = original[y + 1];
583     for (int x = 0; x < width_; ++x) {
584       *dest_row++ = HalfDiff(*source_prev_pixel++, *source_next_pixel++);
585     }
586   }
587 
588   // Compute last row. Approximated with backward difference.
589   {
590     const U* const src_row = original[height_less_one_];
591     T* dest_row = (*this)[height_less_one_];
592     for (int x = 0; x < width_; ++x) {
593       dest_row[x] = src_row[x] - src_row[x - src_stride];
594     }
595   }
596 }
597 
598 template <typename T>
599 template <typename U>
ConvolvePixel3x3(const Image<U> & original,const int * const filter,const int center_x,const int center_y,const int total)600 inline T Image<T>::ConvolvePixel3x3(const Image<U>& original,
601                                     const int* const filter,
602                                     const int center_x, const int center_y,
603                                     const int total) const {
604   int32_t sum = 0;
605   for (int filter_y = 0; filter_y < 3; ++filter_y) {
606     const int y = Clip(center_y - 1 + filter_y, 0, original.GetHeight());
607     for (int filter_x = 0; filter_x < 3; ++filter_x) {
608       const int x = Clip(center_x - 1 + filter_x, 0, original.GetWidth());
609       sum += original[y][x] * filter[filter_y * 3 + filter_x];
610     }
611   }
612   return sum / total;
613 }
614 
615 template <typename T>
616 template <typename U>
Convolve3x3(const Image<U> & original,const int32_t * const filter)617 inline void Image<T>::Convolve3x3(const Image<U>& original,
618                                   const int32_t* const filter) {
619   int32_t sum = 0;
620   for (int i = 0; i < 9; ++i) {
621     sum += abs(filter[i]);
622   }
623   for (int y = 0; y < height_; ++y) {
624     for (int x = 0; x < width_; ++x) {
625       SetPixel(x, y, ConvolvePixel3x3(original, filter, x, y, sum));
626     }
627   }
628 }
629 
630 template <typename T>
FromArray(const T * const pixels,const int stride,const int factor)631 inline void Image<T>::FromArray(const T* const pixels, const int stride,
632                       const int factor) {
633   if (factor == 1 && stride == width_) {
634     // If not subsampling, memcpy per line should be faster.
635     memcpy(this->image_data_, pixels, data_size_ * sizeof(T));
636     return;
637   }
638 
639   DownsampleAveraged(pixels, stride, factor);
640 }
641 
642 }  // namespace tf_tracking
643 
644 #endif  // TENSORFLOW_EXAMPLES_ANDROID_JNI_OBJECT_TRACKING_IMAGE_INL_H_
645