| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include <functional> | |
| 6 #include <numeric> | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/basictypes.h" | |
| 10 #include "base/files/file_path.h" | |
| 11 #include "base/files/file_util.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/time/time.h" | |
| 14 #include "skia/ext/convolver.h" | |
| 15 #include "skia/ext/recursive_gaussian_convolution.h" | |
| 16 #include "testing/gtest/include/gtest/gtest.h" | |
| 17 #include "third_party/skia/include/core/SkPoint.h" | |
| 18 #include "third_party/skia/include/core/SkRect.h" | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 int ComputeRowStride(int width, int channel_count, int stride_slack) { | |
| 23 return width * channel_count + stride_slack; | |
| 24 } | |
| 25 | |
| 26 SkIPoint MakeImpulseImage(std::vector<unsigned char>* image, | |
| 27 int width, | |
| 28 int height, | |
| 29 int channel_index, | |
| 30 int channel_count, | |
| 31 int stride_slack) { | |
| 32 const int src_row_stride = ComputeRowStride( | |
| 33 width, channel_count, stride_slack); | |
| 34 const int src_byte_count = src_row_stride * height; | |
| 35 const int signal_x = width / 2; | |
| 36 const int signal_y = height / 2; | |
| 37 | |
| 38 image->resize(src_byte_count, 0); | |
| 39 const int non_zero_pixel_index = | |
| 40 signal_y * src_row_stride + signal_x * channel_count + channel_index; | |
| 41 (*image)[non_zero_pixel_index] = 255; | |
| 42 return SkIPoint::Make(signal_x, signal_y); | |
| 43 } | |
| 44 | |
| 45 SkIRect MakeBoxImage(std::vector<unsigned char>* image, | |
| 46 int width, | |
| 47 int height, | |
| 48 int channel_index, | |
| 49 int channel_count, | |
| 50 int stride_slack, | |
| 51 int box_width, | |
| 52 int box_height, | |
| 53 unsigned char value) { | |
| 54 const int src_row_stride = ComputeRowStride( | |
| 55 width, channel_count, stride_slack); | |
| 56 const int src_byte_count = src_row_stride * height; | |
| 57 const SkIRect box = SkIRect::MakeXYWH((width - box_width) / 2, | |
| 58 (height - box_height) / 2, | |
| 59 box_width, box_height); | |
| 60 | |
| 61 image->resize(src_byte_count, 0); | |
| 62 for (int y = box.top(); y < box.bottom(); ++y) { | |
| 63 for (int x = box.left(); x < box.right(); ++x) | |
| 64 (*image)[y * src_row_stride + x * channel_count + channel_index] = value; | |
| 65 } | |
| 66 | |
| 67 return box; | |
| 68 } | |
| 69 | |
| 70 int ComputeBoxSum(const std::vector<unsigned char>& image, | |
| 71 const SkIRect& box, | |
| 72 int image_width) { | |
| 73 // Compute the sum of all pixels in the box. Assume byte stride 1 and row | |
| 74 // stride same as image_width. | |
| 75 int sum = 0; | |
| 76 for (int y = box.top(); y < box.bottom(); ++y) { | |
| 77 for (int x = box.left(); x < box.right(); ++x) | |
| 78 sum += image[y * image_width + x]; | |
| 79 } | |
| 80 | |
| 81 return sum; | |
| 82 } | |
| 83 | |
| 84 } // namespace | |
| 85 | |
| 86 namespace skia { | |
| 87 | |
| 88 TEST(RecursiveGaussian, SmoothingMethodComparison) { | |
| 89 static const int kImgWidth = 512; | |
| 90 static const int kImgHeight = 220; | |
| 91 static const int kChannelIndex = 3; | |
| 92 static const int kChannelCount = 3; | |
| 93 static const int kStrideSlack = 22; | |
| 94 | |
| 95 std::vector<unsigned char> input; | |
| 96 SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); | |
| 97 MakeImpulseImage( | |
| 98 &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, | |
| 99 kStrideSlack); | |
| 100 | |
| 101 // Destination will be a single channel image with stide matching width. | |
| 102 const int dest_row_stride = kImgWidth; | |
| 103 const int dest_byte_count = dest_row_stride * kImgHeight; | |
| 104 std::vector<unsigned char> intermediate(dest_byte_count); | |
| 105 std::vector<unsigned char> intermediate2(dest_byte_count); | |
| 106 std::vector<unsigned char> control(dest_byte_count); | |
| 107 std::vector<unsigned char> output(dest_byte_count); | |
| 108 | |
| 109 const int src_row_stride = ComputeRowStride( | |
| 110 kImgWidth, kChannelCount, kStrideSlack); | |
| 111 | |
| 112 const float kernel_sigma = 2.5f; | |
| 113 ConvolutionFilter1D filter; | |
| 114 SetUpGaussianConvolutionKernel(&filter, kernel_sigma, false); | |
| 115 // Process the control image. | |
| 116 SingleChannelConvolveX1D(&input[0], src_row_stride, | |
| 117 kChannelIndex, kChannelCount, | |
| 118 filter, image_size, | |
| 119 &intermediate[0], dest_row_stride, 0, 1, false); | |
| 120 SingleChannelConvolveY1D(&intermediate[0], dest_row_stride, 0, 1, | |
| 121 filter, image_size, | |
| 122 &control[0], dest_row_stride, 0, 1, false); | |
| 123 | |
| 124 // Now try the same using the other method. | |
| 125 RecursiveFilter recursive_filter(kernel_sigma, RecursiveFilter::FUNCTION); | |
| 126 SingleChannelRecursiveGaussianY(&input[0], src_row_stride, | |
| 127 kChannelIndex, kChannelCount, | |
| 128 recursive_filter, image_size, | |
| 129 &intermediate2[0], dest_row_stride, | |
| 130 0, 1, false); | |
| 131 SingleChannelRecursiveGaussianX(&intermediate2[0], dest_row_stride, 0, 1, | |
| 132 recursive_filter, image_size, | |
| 133 &output[0], dest_row_stride, 0, 1, false); | |
| 134 | |
| 135 // We cannot expect the results to be really the same. In particular, | |
| 136 // the standard implementation is computed in completely fixed-point, while | |
| 137 // recursive is done in floating point and squeezed back into char*. On top | |
| 138 // of that, its characteristics are a bit different (consult the paper). | |
| 139 EXPECT_NEAR(std::accumulate(intermediate.begin(), intermediate.end(), 0), | |
| 140 std::accumulate(intermediate2.begin(), intermediate2.end(), 0), | |
| 141 50); | |
| 142 int difference_count = 0; | |
| 143 std::vector<unsigned char>::const_iterator i1, i2; | |
| 144 for (i1 = control.begin(), i2 = output.begin(); | |
| 145 i1 != control.end(); ++i1, ++i2) { | |
| 146 if ((*i1 != 0) != (*i2 != 0)) | |
| 147 difference_count++; | |
| 148 } | |
| 149 | |
| 150 EXPECT_LE(difference_count, 44); // 44 is 2 * PI * r (r == 7, spot size). | |
| 151 } | |
| 152 | |
| 153 TEST(RecursiveGaussian, SmoothingImpulse) { | |
| 154 static const int kImgWidth = 200; | |
| 155 static const int kImgHeight = 300; | |
| 156 static const int kChannelIndex = 3; | |
| 157 static const int kChannelCount = 3; | |
| 158 static const int kStrideSlack = 22; | |
| 159 | |
| 160 std::vector<unsigned char> input; | |
| 161 SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); | |
| 162 const SkIPoint centre_point = MakeImpulseImage( | |
| 163 &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, | |
| 164 kStrideSlack); | |
| 165 | |
| 166 // Destination will be a single channel image with stide matching width. | |
| 167 const int dest_row_stride = kImgWidth; | |
| 168 const int dest_byte_count = dest_row_stride * kImgHeight; | |
| 169 std::vector<unsigned char> intermediate(dest_byte_count); | |
| 170 std::vector<unsigned char> output(dest_byte_count); | |
| 171 | |
| 172 const int src_row_stride = ComputeRowStride( | |
| 173 kImgWidth, kChannelCount, kStrideSlack); | |
| 174 | |
| 175 const float kernel_sigma = 5.0f; | |
| 176 RecursiveFilter recursive_filter(kernel_sigma, RecursiveFilter::FUNCTION); | |
| 177 SingleChannelRecursiveGaussianY(&input[0], src_row_stride, | |
| 178 kChannelIndex, kChannelCount, | |
| 179 recursive_filter, image_size, | |
| 180 &intermediate[0], dest_row_stride, | |
| 181 0, 1, false); | |
| 182 SingleChannelRecursiveGaussianX(&intermediate[0], dest_row_stride, 0, 1, | |
| 183 recursive_filter, image_size, | |
| 184 &output[0], dest_row_stride, 0, 1, false); | |
| 185 | |
| 186 // Check we got the expected impulse response. | |
| 187 const int cx = centre_point.x(); | |
| 188 const int cy = centre_point.y(); | |
| 189 unsigned char value_x = output[dest_row_stride * cy + cx]; | |
| 190 unsigned char value_y = value_x; | |
| 191 EXPECT_GT(value_x, 0); | |
| 192 for (int offset = 0; | |
| 193 offset < std::min(kImgWidth, kImgHeight) && (value_y > 0 || value_x > 0); | |
| 194 ++offset) { | |
| 195 // Symmetricity and monotonicity along X. | |
| 196 EXPECT_EQ(output[dest_row_stride * cy + cx - offset], | |
| 197 output[dest_row_stride * cy + cx + offset]); | |
| 198 EXPECT_LE(output[dest_row_stride * cy + cx - offset], value_x); | |
| 199 value_x = output[dest_row_stride * cy + cx - offset]; | |
| 200 | |
| 201 // Symmetricity and monotonicity along Y. | |
| 202 EXPECT_EQ(output[dest_row_stride * (cy - offset) + cx], | |
| 203 output[dest_row_stride * (cy + offset) + cx]); | |
| 204 EXPECT_LE(output[dest_row_stride * (cy - offset) + cx], value_y); | |
| 205 value_y = output[dest_row_stride * (cy - offset) + cx]; | |
| 206 | |
| 207 // Symmetricity along X/Y (not really assured, but should be close). | |
| 208 EXPECT_NEAR(value_x, value_y, 1); | |
| 209 } | |
| 210 | |
| 211 // Smooth the inverse now. | |
| 212 std::vector<unsigned char> output2(dest_byte_count); | |
| 213 std::transform(input.begin(), input.end(), input.begin(), | |
| 214 std::bind1st(std::minus<unsigned char>(), 255U)); | |
| 215 SingleChannelRecursiveGaussianY(&input[0], src_row_stride, | |
| 216 kChannelIndex, kChannelCount, | |
| 217 recursive_filter, image_size, | |
| 218 &intermediate[0], dest_row_stride, | |
| 219 0, 1, false); | |
| 220 SingleChannelRecursiveGaussianX(&intermediate[0], dest_row_stride, 0, 1, | |
| 221 recursive_filter, image_size, | |
| 222 &output2[0], dest_row_stride, 0, 1, false); | |
| 223 // The image should be the reverse of output, but permitting for rounding | |
| 224 // we will only claim that wherever output is 0, output2 should be 255. | |
| 225 // There still can be differences at the edges of the object. | |
| 226 std::vector<unsigned char>::const_iterator i1, i2; | |
| 227 int difference_count = 0; | |
| 228 for (i1 = output.begin(), i2 = output2.begin(); | |
| 229 i1 != output.end(); ++i1, ++i2) { | |
| 230 // The line below checks (*i1 == 0 <==> *i2 == 255). | |
| 231 if ((*i1 != 0 && *i2 == 255) && ! (*i1 == 0 && *i2 != 255)) | |
| 232 ++difference_count; | |
| 233 } | |
| 234 EXPECT_LE(difference_count, 8); | |
| 235 } | |
| 236 | |
| 237 TEST(RecursiveGaussian, FirstDerivative) { | |
| 238 static const int kImgWidth = 512; | |
| 239 static const int kImgHeight = 1024; | |
| 240 static const int kChannelIndex = 2; | |
| 241 static const int kChannelCount = 4; | |
| 242 static const int kStrideSlack = 22; | |
| 243 static const int kBoxSize = 400; | |
| 244 | |
| 245 std::vector<unsigned char> input; | |
| 246 const SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); | |
| 247 const SkIRect box = MakeBoxImage( | |
| 248 &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, | |
| 249 kStrideSlack, kBoxSize, kBoxSize, 200); | |
| 250 | |
| 251 // Destination will be a single channel image with stide matching width. | |
| 252 const int dest_row_stride = kImgWidth; | |
| 253 const int dest_byte_count = dest_row_stride * kImgHeight; | |
| 254 std::vector<unsigned char> output_x(dest_byte_count); | |
| 255 std::vector<unsigned char> output_y(dest_byte_count); | |
| 256 std::vector<unsigned char> output(dest_byte_count); | |
| 257 | |
| 258 const int src_row_stride = ComputeRowStride( | |
| 259 kImgWidth, kChannelCount, kStrideSlack); | |
| 260 | |
| 261 const float kernel_sigma = 3.0f; | |
| 262 const int spread = 4 * kernel_sigma; | |
| 263 RecursiveFilter recursive_filter(kernel_sigma, | |
| 264 RecursiveFilter::FIRST_DERIVATIVE); | |
| 265 SingleChannelRecursiveGaussianX(&input[0], src_row_stride, | |
| 266 kChannelIndex, kChannelCount, | |
| 267 recursive_filter, image_size, | |
| 268 &output_x[0], dest_row_stride, | |
| 269 0, 1, true); | |
| 270 SingleChannelRecursiveGaussianY(&input[0], src_row_stride, | |
| 271 kChannelIndex, kChannelCount, | |
| 272 recursive_filter, image_size, | |
| 273 &output_y[0], dest_row_stride, | |
| 274 0, 1, true); | |
| 275 | |
| 276 // In test code we can assume adding the two up should do fine. | |
| 277 std::vector<unsigned char>::const_iterator ix, iy; | |
| 278 std::vector<unsigned char>::iterator target; | |
| 279 for (target = output.begin(), ix = output_x.begin(), iy = output_y.begin(); | |
| 280 target < output.end(); ++target, ++ix, ++iy) { | |
| 281 *target = *ix + *iy; | |
| 282 } | |
| 283 | |
| 284 SkIRect inflated_rect(box); | |
| 285 inflated_rect.outset(spread, spread); | |
| 286 SkIRect deflated_rect(box); | |
| 287 deflated_rect.inset(spread, spread); | |
| 288 | |
| 289 int image_total = ComputeBoxSum(output, | |
| 290 SkIRect::MakeWH(kImgWidth, kImgHeight), | |
| 291 kImgWidth); | |
| 292 int box_inflated = ComputeBoxSum(output, inflated_rect, kImgWidth); | |
| 293 int box_deflated = ComputeBoxSum(output, deflated_rect, kImgWidth); | |
| 294 EXPECT_EQ(box_deflated, 0); | |
| 295 EXPECT_EQ(image_total, box_inflated); | |
| 296 | |
| 297 // Try inverted image. Behaviour should be very similar (modulo rounding). | |
| 298 std::transform(input.begin(), input.end(), input.begin(), | |
| 299 std::bind1st(std::minus<unsigned char>(), 255U)); | |
| 300 SingleChannelRecursiveGaussianX(&input[0], src_row_stride, | |
| 301 kChannelIndex, kChannelCount, | |
| 302 recursive_filter, image_size, | |
| 303 &output_x[0], dest_row_stride, | |
| 304 0, 1, true); | |
| 305 SingleChannelRecursiveGaussianY(&input[0], src_row_stride, | |
| 306 kChannelIndex, kChannelCount, | |
| 307 recursive_filter, image_size, | |
| 308 &output_y[0], dest_row_stride, | |
| 309 0, 1, true); | |
| 310 | |
| 311 for (target = output.begin(), ix = output_x.begin(), iy = output_y.begin(); | |
| 312 target < output.end(); ++target, ++ix, ++iy) { | |
| 313 *target = *ix + *iy; | |
| 314 } | |
| 315 | |
| 316 image_total = ComputeBoxSum(output, | |
| 317 SkIRect::MakeWH(kImgWidth, kImgHeight), | |
| 318 kImgWidth); | |
| 319 box_inflated = ComputeBoxSum(output, inflated_rect, kImgWidth); | |
| 320 box_deflated = ComputeBoxSum(output, deflated_rect, kImgWidth); | |
| 321 | |
| 322 EXPECT_EQ(box_deflated, 0); | |
| 323 EXPECT_EQ(image_total, box_inflated); | |
| 324 } | |
| 325 | |
| 326 TEST(RecursiveGaussian, SecondDerivative) { | |
| 327 static const int kImgWidth = 700; | |
| 328 static const int kImgHeight = 500; | |
| 329 static const int kChannelIndex = 0; | |
| 330 static const int kChannelCount = 2; | |
| 331 static const int kStrideSlack = 42; | |
| 332 static const int kBoxSize = 200; | |
| 333 | |
| 334 std::vector<unsigned char> input; | |
| 335 SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); | |
| 336 const SkIRect box = MakeBoxImage( | |
| 337 &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, | |
| 338 kStrideSlack, kBoxSize, kBoxSize, 200); | |
| 339 | |
| 340 // Destination will be a single channel image with stide matching width. | |
| 341 const int dest_row_stride = kImgWidth; | |
| 342 const int dest_byte_count = dest_row_stride * kImgHeight; | |
| 343 std::vector<unsigned char> output_x(dest_byte_count); | |
| 344 std::vector<unsigned char> output_y(dest_byte_count); | |
| 345 std::vector<unsigned char> output(dest_byte_count); | |
| 346 | |
| 347 const int src_row_stride = ComputeRowStride( | |
| 348 kImgWidth, kChannelCount, kStrideSlack); | |
| 349 | |
| 350 const float kernel_sigma = 5.0f; | |
| 351 const int spread = 8 * kernel_sigma; | |
| 352 RecursiveFilter recursive_filter(kernel_sigma, | |
| 353 RecursiveFilter::SECOND_DERIVATIVE); | |
| 354 SingleChannelRecursiveGaussianX(&input[0], src_row_stride, | |
| 355 kChannelIndex, kChannelCount, | |
| 356 recursive_filter, image_size, | |
| 357 &output_x[0], dest_row_stride, | |
| 358 0, 1, true); | |
| 359 SingleChannelRecursiveGaussianY(&input[0], src_row_stride, | |
| 360 kChannelIndex, kChannelCount, | |
| 361 recursive_filter, image_size, | |
| 362 &output_y[0], dest_row_stride, | |
| 363 0, 1, true); | |
| 364 | |
| 365 // In test code we can assume adding the two up should do fine. | |
| 366 std::vector<unsigned char>::const_iterator ix, iy; | |
| 367 std::vector<unsigned char>::iterator target; | |
| 368 for (target = output.begin(),ix = output_x.begin(), iy = output_y.begin(); | |
| 369 target < output.end(); ++target, ++ix, ++iy) { | |
| 370 *target = *ix + *iy; | |
| 371 } | |
| 372 | |
| 373 int image_total = ComputeBoxSum(output, | |
| 374 SkIRect::MakeWH(kImgWidth, kImgHeight), | |
| 375 kImgWidth); | |
| 376 int box_inflated = ComputeBoxSum(output, | |
| 377 SkIRect::MakeLTRB(box.left() - spread, | |
| 378 box.top() - spread, | |
| 379 box.right() + spread, | |
| 380 box.bottom() + spread), | |
| 381 kImgWidth); | |
| 382 int box_deflated = ComputeBoxSum(output, | |
| 383 SkIRect::MakeLTRB(box.left() + spread, | |
| 384 box.top() + spread, | |
| 385 box.right() - spread, | |
| 386 box.bottom() - spread), | |
| 387 kImgWidth); | |
| 388 // Since second derivative is not really used and implemented mostly | |
| 389 // for the sake of completeness, we do not verify the detail (that dip | |
| 390 // in the middle). But it is there. | |
| 391 EXPECT_EQ(box_deflated, 0); | |
| 392 EXPECT_EQ(image_total, box_inflated); | |
| 393 } | |
| 394 | |
| 395 } // namespace skia | |
| OLD | NEW |