/ocr/ocrservice/jni/opticalflow/feature_detector.cpp

http://eyes-free.googlecode.com/ · C++ · 382 lines · 216 code · 75 blank · 91 comment · 30 complexity · 1b03698e37a3ba59df9d247f52e7002f MD5 · raw file

  1. /*
  2. * Copyright 2011, Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. // Author: Andrew Harp
  17. //
  18. // Various feature detecting functions.
  19. #include <float.h>
  20. #include "utils.h"
  21. #include "time_log.h"
  22. #include "image.h"
  23. #include "feature_detector.h"
  24. // Threshold for pixels to be considered different.
  25. #define FAST_DIFF_AMOUNT 10
  26. // How far from edge of frame to stop looking for FAST features.
  27. #define FAST_BORDER_BUFFER 20
  28. // Minimum enforced distance between detected features.
  29. // Default
  30. #define MIN_FEATURE_DIST_NORMAL 24
  31. // Regions selected as "interesting" (aka have annotations) can have denser
  32. // feature coverage.
  33. #define MIN_FEATURE_DIST_INTEREST 12
  34. // How many FAST qualifying pixels must be connected to a pixel for it to be
  35. // considered a candidate feature for Harris filtering.
  36. #define MIN_NUM_CONNECTED 8
  37. // Size of the window to integrate over for Harris filtering.
  38. // Compare to WINDOW_SIZE in optical_flow.h.
  39. #define HARRIS_WINDOW_SIZE 2
  40. // Arbitrary parameter for how picky Harris filter is, the higher the more
  41. // discriminating.
  42. #define SENSITIVITY 0.2f
  43. namespace flow {
  44. void scoreFeatures(const Image<int32>& I_x, const Image<int32>& I_y,
  45. const int32 num_candidates,
  46. Point2D* const candidate_features) {
  47. // Score all the features
  48. for (int32 i = 0; i < num_candidates; ++i) {
  49. Point2D* const feature = candidate_features + i;
  50. feature->score = harrisFilter(I_x, I_y, feature->x, feature->y);
  51. }
  52. }
  53. // Quicksorts detected features by score and then selects them such that
  54. // they are separated by a minimum distance.
  55. int32 sortAndSelect(const int32 num_candidates, const int32 max_features,
  56. const Image<bool>& interest_map,
  57. Point2D* const candidate_features,
  58. Point2D* const final_features,
  59. Image<uint8>* const best_feature_map) {
  60. qsort(candidate_features, num_candidates);
  61. #ifdef SANITY_CHECKS
  62. // Verify that the array got sorted.
  63. float32 last_score = -FLT_MAX;
  64. for (int32 i = 0; i < num_candidates; ++i) {
  65. const float32 curr_score = (candidate_features + i)->score;
  66. // Scores should be monotonically increasing.
  67. CHECK(last_score <= curr_score,
  68. "Quicksort failure! %d: %.5f > %d: %.5f",
  69. i - 1, last_score, i, curr_score);
  70. last_score = curr_score;
  71. }
  72. #endif
  73. best_feature_map->clear(false);
  74. int32 num_features = 0;
  75. for (int32 i = num_candidates - 1; i >= 0; --i) {
  76. const Point2D& candidate = candidate_features[i];
  77. // Since features are sorted, the first 0 or less value means we can stop
  78. // looking.
  79. if (candidate.score <= 0.0f) {
  80. break;
  81. }
  82. // Lookup whether this feature is in an interest region. If so, other
  83. // features may appear closer to it than normal.
  84. const int32 distance = interest_map.getPixel(candidate.x, candidate.y) ?
  85. MIN_FEATURE_DIST_INTEREST : MIN_FEATURE_DIST_NORMAL;
  86. if (markImage(candidate.x, candidate.y, distance, best_feature_map)) {
  87. final_features[num_features] = candidate;
  88. num_features++;
  89. if (num_features >= max_features) {
  90. break;
  91. }
  92. }
  93. }
  94. return num_features;
  95. }
  96. // Walks along the given circle checking for pixels above or below the center.
  97. // Returns a score, or 0 if the feature did not pass the criteria.
  98. //
  99. // Parameters:
  100. // circle_perimeter: the circumference in pixels of the circle.
  101. // threshold: the minimum number of contiguous pixels that must be above or
  102. // below the center value.
  103. // center_ptr: the location of the center pixel in memory
  104. // offsets: the relative offsets from the center pixel of the edge pixels.
  105. inline int32 testCircle(const int32 circle_perimeter, const int32 threshold,
  106. const uint8* const center_ptr,
  107. const int32* offsets) {
  108. // Get the actual value of the center pixel for easier reference later on.
  109. const int32 center_value = static_cast<int32>(*center_ptr);
  110. // Number of total pixels to check. Have to wrap around some in case
  111. // the contiguous section is split by the array edges.
  112. const int32 num_total = circle_perimeter + threshold - 1;
  113. int32 num_above = 0;
  114. int32 above_diff = 0;
  115. int32 num_below = 0;
  116. int32 below_diff = 0;
  117. // Used to tell when this is definitely not going to meet the threshold so we
  118. // can early abort.
  119. int32 minimum_by_now = threshold - num_total + 1;
  120. // Go through every pixel along the perimeter of the circle, and then around
  121. // again a little bit.
  122. for (int32 i = 0; i < num_total; ++i) {
  123. // This should be faster than mod.
  124. const int32 perim_index = i < circle_perimeter ? i : i - circle_perimeter;
  125. // This gets the value of the current pixel along the perimeter by using
  126. // a precomputed offset.
  127. const int32 curr_value =
  128. static_cast<int32>(center_ptr[offsets[perim_index]]);
  129. const int32 difference = curr_value - center_value;
  130. if (difference > FAST_DIFF_AMOUNT) {
  131. above_diff += difference;
  132. ++num_above;
  133. num_below = 0;
  134. below_diff = 0;
  135. if (num_above >= threshold) {
  136. return above_diff;
  137. }
  138. } else if (difference < -FAST_DIFF_AMOUNT) {
  139. below_diff += difference;
  140. ++num_below;
  141. num_above = 0;
  142. above_diff = 0;
  143. if (num_below >= threshold) {
  144. return below_diff;
  145. }
  146. } else {
  147. num_above = 0;
  148. num_below = 0;
  149. above_diff = 0;
  150. below_diff = 0;
  151. }
  152. // See if there's any chance of making the threshold.
  153. if (max(num_above, num_below) < minimum_by_now) {
  154. // Didn't pass.
  155. return 0;
  156. }
  157. ++minimum_by_now;
  158. }
  159. // Didn't pass.
  160. return 0;
  161. }
  162. // Creates features in a regular grid, regardless of image contents.
  163. int32 seedFeatures(const Image<uint8>& frame,
  164. const int32 num_x, const int32 num_y,
  165. const float32 left, const float32 top,
  166. const float32 right, const float32 bottom,
  167. const int32 type, Point2D* const features) {
  168. int32 num_features = 0;
  169. const float32 step_x = ((right - left) / (num_x - 1));
  170. const float32 step_y = ((bottom - top) / (num_y - 1));
  171. for (int32 x = 0; x < num_x; ++x) {
  172. for (int32 y = 0; y < num_y; ++y) {
  173. const int32 x_pos = x * step_x + left;
  174. const int32 y_pos = y * step_y + top;
  175. if (inRange(x_pos, 0, frame.width_less_one_) &&
  176. inRange(y_pos, 0, frame.height_less_one_)) {
  177. Point2D* const feature = features + num_features;
  178. feature->x = x_pos;
  179. feature->y = y_pos;
  180. feature->type = type;
  181. ++num_features;
  182. }
  183. }
  184. }
  185. return num_features;
  186. }
  187. // Returns how likely a point in the image is to be a corner.
  188. float32 harrisFilter(const Image<int32>& I_x, const Image<int32>& I_y,
  189. const int32 x, const int32 y) {
  190. // Image gradient matrix.
  191. float32 G[] = { 0, 0, 0, 0 };
  192. calculateG(HARRIS_WINDOW_SIZE, x, y, I_x, I_y, G);
  193. const float32 g_sum = G[0] + G[1] + G[2] + G[3];
  194. const float32 a = G[0] / g_sum;
  195. const float32 b = G[1] / g_sum;
  196. const float32 c = G[2] / g_sum;
  197. const float32 d = G[3] / g_sum;
  198. const float32 det = a * d - b * c;
  199. const float32 trace = a + d;
  200. const float32 inner = square(trace) - 4 * det;
  201. if (inner >= 0.0f) {
  202. const float32 square_root_inner = sqrtf(inner);
  203. const float32 eig1 = (trace + square_root_inner) / 2.0f;
  204. const float32 eig2 = (trace - square_root_inner) / 2.0f;
  205. return eig1 * eig2 - SENSITIVITY * square(eig1 + eig2);
  206. }
  207. // Way negative.
  208. return -100.0f;
  209. }
  210. // FAST feature detector.
  211. int32 findFastFeatures(const Image<uint8>& frame, const int32 max_num_features,
  212. Point2D* const features,
  213. Image<uint8>* const best_feature_map) {
  214. /*
  215. // Reference for a circle of diameter 7.
  216. const int32 circle[] = {0, 0, 1, 1, 1, 0, 0,
  217. 0, 1, 0, 0, 0, 1, 0,
  218. 1, 0, 0, 0, 0, 0, 1,
  219. 1, 0, 0, 0, 0, 0, 1,
  220. 1, 0, 0, 0, 0, 0, 1,
  221. 0, 1, 0, 0, 0, 1, 0,
  222. 0, 0, 1, 1, 1, 0, 0};
  223. const int32 circle_offset[] =
  224. {2, 3, 4, 8, 12, 14, 20, 21, 27, 28, 34, 36, 40, 44, 45, 46};
  225. */
  226. // Quick test of compass directions. Any length 16 circle with a break of up
  227. // to 4 pixels will have at least 3 of these 4 pixels active.
  228. static const int32 short_circle_perimeter = 4;
  229. static const int32 short_threshold = 3;
  230. static const int32 short_circle_x[] = { -3, 0, +3, 0 };
  231. static const int32 short_circle_y[] = { 0, -3, 0, +3 };
  232. // Precompute image offsets.
  233. int32 short_offsets[short_circle_perimeter];
  234. for (int i = 0; i < short_circle_perimeter; ++i) {
  235. short_offsets[i] = short_circle_x[i] + short_circle_y[i] * frame.getWidth();
  236. }
  237. // Large circle values.
  238. static const int32 full_circle_perimeter = 16;
  239. static const int32 full_threshold = 12;
  240. static const int32 full_circle_x[] =
  241. { -1, 0, +1, +2, +3, +3, +3, +2, +1, +0, -1, -2, -3, -3, -3, -2 };
  242. static const int32 full_circle_y[] =
  243. { -3, -3, -3, -2, -1, 0, +1, +2, +3, +3, +3, +2, +1, +0, -1, -2 };
  244. // Precompute image offsets.
  245. int32 full_offsets[full_circle_perimeter];
  246. for (int i = 0; i < full_circle_perimeter; ++i) {
  247. full_offsets[i] = full_circle_x[i] + full_circle_y[i] * frame.getWidth();
  248. }
  249. const int frame_width = frame.getWidth();
  250. const int end_y = frame.getHeight() - FAST_BORDER_BUFFER;
  251. const int end_x = frame.getWidth() - FAST_BORDER_BUFFER;
  252. best_feature_map->clear(0);
  253. // Loop through once to find FAST feature clumps.
  254. for (int32 img_y = FAST_BORDER_BUFFER; img_y < end_y; ++img_y) {
  255. const uint8* curr_pixel_ptr =
  256. frame.getPixelPtrConst(FAST_BORDER_BUFFER, img_y);
  257. for (int32 img_x = FAST_BORDER_BUFFER; img_x < end_x; ++img_x) {
  258. // Only insert it if it meets the quick minimum requirements test.
  259. if (testCircle(short_circle_perimeter, short_threshold,
  260. curr_pixel_ptr, short_offsets) != 0) {
  261. // Longer test for actual feature score..
  262. const int32 fast_score = testCircle(full_circle_perimeter,
  263. full_threshold,
  264. curr_pixel_ptr,
  265. full_offsets);
  266. // Non-zero score means the feature was found.
  267. if (fast_score != 0) {
  268. uint8* const center_ptr = best_feature_map->getPixelPtr(img_x, img_y);
  269. // Increase the feature count on this pixel and the pixels in all
  270. // 4 cardinal directions.
  271. *center_ptr += 5;
  272. *(center_ptr - 1) += 1;
  273. *(center_ptr + 1) += 1;
  274. *(center_ptr - frame_width) += 1;
  275. *(center_ptr + frame_width) += 1;
  276. }
  277. }
  278. ++curr_pixel_ptr;
  279. } // x
  280. } // y
  281. timeLog("Found FAST features");
  282. int32 num_features = 0;
  283. // Loop through again and Harris filter pixels in the center of clumps.
  284. // We can shrink the window by 1 pixel on every side.
  285. for (int32 img_y = FAST_BORDER_BUFFER + 1; img_y < end_y - 1; ++img_y) {
  286. const int32 start_x = FAST_BORDER_BUFFER + 1;
  287. const uint8* curr_pixel_ptr =
  288. best_feature_map->getPixelPtrConst(start_x, img_y);
  289. for (int32 img_x = start_x; img_x < end_x - 1; ++img_x) {
  290. if (*curr_pixel_ptr >= MIN_NUM_CONNECTED) {
  291. Point2D* const feature = features + num_features;
  292. feature->x = img_x;
  293. feature->y = img_y;
  294. feature->score = 0;
  295. feature->type = FEATURE_FAST;
  296. ++num_features;
  297. if (num_features >= max_num_features) {
  298. return num_features;
  299. }
  300. }
  301. ++curr_pixel_ptr;
  302. } // x
  303. } // y
  304. timeLog("Filtered FAST features");
  305. return num_features;
  306. }
  307. } // namespace flow