/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
- /*
- * Copyright 2011, Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- // Author: Andrew Harp
- //
- // Various feature detecting functions.
- #include <float.h>
- #include "utils.h"
- #include "time_log.h"
- #include "image.h"
- #include "feature_detector.h"
- // Threshold for pixels to be considered different.
- #define FAST_DIFF_AMOUNT 10
- // How far from edge of frame to stop looking for FAST features.
- #define FAST_BORDER_BUFFER 20
- // Minimum enforced distance between detected features.
- // Default
- #define MIN_FEATURE_DIST_NORMAL 24
- // Regions selected as "interesting" (aka have annotations) can have denser
- // feature coverage.
- #define MIN_FEATURE_DIST_INTEREST 12
- // How many FAST qualifying pixels must be connected to a pixel for it to be
- // considered a candidate feature for Harris filtering.
- #define MIN_NUM_CONNECTED 8
- // Size of the window to integrate over for Harris filtering.
- // Compare to WINDOW_SIZE in optical_flow.h.
- #define HARRIS_WINDOW_SIZE 2
- // Arbitrary parameter for how picky Harris filter is, the higher the more
- // discriminating.
- #define SENSITIVITY 0.2f
- namespace flow {
- void scoreFeatures(const Image<int32>& I_x, const Image<int32>& I_y,
- const int32 num_candidates,
- Point2D* const candidate_features) {
- // Score all the features
- for (int32 i = 0; i < num_candidates; ++i) {
- Point2D* const feature = candidate_features + i;
- feature->score = harrisFilter(I_x, I_y, feature->x, feature->y);
- }
- }
- // Quicksorts detected features by score and then selects them such that
- // they are separated by a minimum distance.
- int32 sortAndSelect(const int32 num_candidates, const int32 max_features,
- const Image<bool>& interest_map,
- Point2D* const candidate_features,
- Point2D* const final_features,
- Image<uint8>* const best_feature_map) {
- qsort(candidate_features, num_candidates);
- #ifdef SANITY_CHECKS
- // Verify that the array got sorted.
- float32 last_score = -FLT_MAX;
- for (int32 i = 0; i < num_candidates; ++i) {
- const float32 curr_score = (candidate_features + i)->score;
- // Scores should be monotonically increasing.
- CHECK(last_score <= curr_score,
- "Quicksort failure! %d: %.5f > %d: %.5f",
- i - 1, last_score, i, curr_score);
- last_score = curr_score;
- }
- #endif
- best_feature_map->clear(false);
- int32 num_features = 0;
- for (int32 i = num_candidates - 1; i >= 0; --i) {
- const Point2D& candidate = candidate_features[i];
- // Since features are sorted, the first 0 or less value means we can stop
- // looking.
- if (candidate.score <= 0.0f) {
- break;
- }
- // Lookup whether this feature is in an interest region. If so, other
- // features may appear closer to it than normal.
- const int32 distance = interest_map.getPixel(candidate.x, candidate.y) ?
- MIN_FEATURE_DIST_INTEREST : MIN_FEATURE_DIST_NORMAL;
- if (markImage(candidate.x, candidate.y, distance, best_feature_map)) {
- final_features[num_features] = candidate;
- num_features++;
- if (num_features >= max_features) {
- break;
- }
- }
- }
- return num_features;
- }
- // Walks along the given circle checking for pixels above or below the center.
- // Returns a score, or 0 if the feature did not pass the criteria.
- //
- // Parameters:
- // circle_perimeter: the circumference in pixels of the circle.
- // threshold: the minimum number of contiguous pixels that must be above or
- // below the center value.
- // center_ptr: the location of the center pixel in memory
- // offsets: the relative offsets from the center pixel of the edge pixels.
- inline int32 testCircle(const int32 circle_perimeter, const int32 threshold,
- const uint8* const center_ptr,
- const int32* offsets) {
- // Get the actual value of the center pixel for easier reference later on.
- const int32 center_value = static_cast<int32>(*center_ptr);
- // Number of total pixels to check. Have to wrap around some in case
- // the contiguous section is split by the array edges.
- const int32 num_total = circle_perimeter + threshold - 1;
- int32 num_above = 0;
- int32 above_diff = 0;
- int32 num_below = 0;
- int32 below_diff = 0;
- // Used to tell when this is definitely not going to meet the threshold so we
- // can early abort.
- int32 minimum_by_now = threshold - num_total + 1;
- // Go through every pixel along the perimeter of the circle, and then around
- // again a little bit.
- for (int32 i = 0; i < num_total; ++i) {
- // This should be faster than mod.
- const int32 perim_index = i < circle_perimeter ? i : i - circle_perimeter;
- // This gets the value of the current pixel along the perimeter by using
- // a precomputed offset.
- const int32 curr_value =
- static_cast<int32>(center_ptr[offsets[perim_index]]);
- const int32 difference = curr_value - center_value;
- if (difference > FAST_DIFF_AMOUNT) {
- above_diff += difference;
- ++num_above;
- num_below = 0;
- below_diff = 0;
- if (num_above >= threshold) {
- return above_diff;
- }
- } else if (difference < -FAST_DIFF_AMOUNT) {
- below_diff += difference;
- ++num_below;
- num_above = 0;
- above_diff = 0;
- if (num_below >= threshold) {
- return below_diff;
- }
- } else {
- num_above = 0;
- num_below = 0;
- above_diff = 0;
- below_diff = 0;
- }
- // See if there's any chance of making the threshold.
- if (max(num_above, num_below) < minimum_by_now) {
- // Didn't pass.
- return 0;
- }
- ++minimum_by_now;
- }
- // Didn't pass.
- return 0;
- }
- // Creates features in a regular grid, regardless of image contents.
- int32 seedFeatures(const Image<uint8>& frame,
- const int32 num_x, const int32 num_y,
- const float32 left, const float32 top,
- const float32 right, const float32 bottom,
- const int32 type, Point2D* const features) {
- int32 num_features = 0;
- const float32 step_x = ((right - left) / (num_x - 1));
- const float32 step_y = ((bottom - top) / (num_y - 1));
- for (int32 x = 0; x < num_x; ++x) {
- for (int32 y = 0; y < num_y; ++y) {
- const int32 x_pos = x * step_x + left;
- const int32 y_pos = y * step_y + top;
- if (inRange(x_pos, 0, frame.width_less_one_) &&
- inRange(y_pos, 0, frame.height_less_one_)) {
- Point2D* const feature = features + num_features;
- feature->x = x_pos;
- feature->y = y_pos;
- feature->type = type;
- ++num_features;
- }
- }
- }
- return num_features;
- }
- // Returns how likely a point in the image is to be a corner.
- float32 harrisFilter(const Image<int32>& I_x, const Image<int32>& I_y,
- const int32 x, const int32 y) {
- // Image gradient matrix.
- float32 G[] = { 0, 0, 0, 0 };
- calculateG(HARRIS_WINDOW_SIZE, x, y, I_x, I_y, G);
- const float32 g_sum = G[0] + G[1] + G[2] + G[3];
- const float32 a = G[0] / g_sum;
- const float32 b = G[1] / g_sum;
- const float32 c = G[2] / g_sum;
- const float32 d = G[3] / g_sum;
- const float32 det = a * d - b * c;
- const float32 trace = a + d;
- const float32 inner = square(trace) - 4 * det;
- if (inner >= 0.0f) {
- const float32 square_root_inner = sqrtf(inner);
- const float32 eig1 = (trace + square_root_inner) / 2.0f;
- const float32 eig2 = (trace - square_root_inner) / 2.0f;
- return eig1 * eig2 - SENSITIVITY * square(eig1 + eig2);
- }
- // Way negative.
- return -100.0f;
- }
- // FAST feature detector.
- int32 findFastFeatures(const Image<uint8>& frame, const int32 max_num_features,
- Point2D* const features,
- Image<uint8>* const best_feature_map) {
- /*
- // Reference for a circle of diameter 7.
- const int32 circle[] = {0, 0, 1, 1, 1, 0, 0,
- 0, 1, 0, 0, 0, 1, 0,
- 1, 0, 0, 0, 0, 0, 1,
- 1, 0, 0, 0, 0, 0, 1,
- 1, 0, 0, 0, 0, 0, 1,
- 0, 1, 0, 0, 0, 1, 0,
- 0, 0, 1, 1, 1, 0, 0};
- const int32 circle_offset[] =
- {2, 3, 4, 8, 12, 14, 20, 21, 27, 28, 34, 36, 40, 44, 45, 46};
- */
- // Quick test of compass directions. Any length 16 circle with a break of up
- // to 4 pixels will have at least 3 of these 4 pixels active.
- static const int32 short_circle_perimeter = 4;
- static const int32 short_threshold = 3;
- static const int32 short_circle_x[] = { -3, 0, +3, 0 };
- static const int32 short_circle_y[] = { 0, -3, 0, +3 };
- // Precompute image offsets.
- int32 short_offsets[short_circle_perimeter];
- for (int i = 0; i < short_circle_perimeter; ++i) {
- short_offsets[i] = short_circle_x[i] + short_circle_y[i] * frame.getWidth();
- }
- // Large circle values.
- static const int32 full_circle_perimeter = 16;
- static const int32 full_threshold = 12;
- static const int32 full_circle_x[] =
- { -1, 0, +1, +2, +3, +3, +3, +2, +1, +0, -1, -2, -3, -3, -3, -2 };
- static const int32 full_circle_y[] =
- { -3, -3, -3, -2, -1, 0, +1, +2, +3, +3, +3, +2, +1, +0, -1, -2 };
- // Precompute image offsets.
- int32 full_offsets[full_circle_perimeter];
- for (int i = 0; i < full_circle_perimeter; ++i) {
- full_offsets[i] = full_circle_x[i] + full_circle_y[i] * frame.getWidth();
- }
- const int frame_width = frame.getWidth();
- const int end_y = frame.getHeight() - FAST_BORDER_BUFFER;
- const int end_x = frame.getWidth() - FAST_BORDER_BUFFER;
- best_feature_map->clear(0);
- // Loop through once to find FAST feature clumps.
- for (int32 img_y = FAST_BORDER_BUFFER; img_y < end_y; ++img_y) {
- const uint8* curr_pixel_ptr =
- frame.getPixelPtrConst(FAST_BORDER_BUFFER, img_y);
- for (int32 img_x = FAST_BORDER_BUFFER; img_x < end_x; ++img_x) {
- // Only insert it if it meets the quick minimum requirements test.
- if (testCircle(short_circle_perimeter, short_threshold,
- curr_pixel_ptr, short_offsets) != 0) {
- // Longer test for actual feature score..
- const int32 fast_score = testCircle(full_circle_perimeter,
- full_threshold,
- curr_pixel_ptr,
- full_offsets);
- // Non-zero score means the feature was found.
- if (fast_score != 0) {
- uint8* const center_ptr = best_feature_map->getPixelPtr(img_x, img_y);
- // Increase the feature count on this pixel and the pixels in all
- // 4 cardinal directions.
- *center_ptr += 5;
- *(center_ptr - 1) += 1;
- *(center_ptr + 1) += 1;
- *(center_ptr - frame_width) += 1;
- *(center_ptr + frame_width) += 1;
- }
- }
- ++curr_pixel_ptr;
- } // x
- } // y
- timeLog("Found FAST features");
- int32 num_features = 0;
- // Loop through again and Harris filter pixels in the center of clumps.
- // We can shrink the window by 1 pixel on every side.
- for (int32 img_y = FAST_BORDER_BUFFER + 1; img_y < end_y - 1; ++img_y) {
- const int32 start_x = FAST_BORDER_BUFFER + 1;
- const uint8* curr_pixel_ptr =
- best_feature_map->getPixelPtrConst(start_x, img_y);
- for (int32 img_x = start_x; img_x < end_x - 1; ++img_x) {
- if (*curr_pixel_ptr >= MIN_NUM_CONNECTED) {
- Point2D* const feature = features + num_features;
- feature->x = img_x;
- feature->y = img_y;
- feature->score = 0;
- feature->type = FEATURE_FAST;
- ++num_features;
- if (num_features >= max_num_features) {
- return num_features;
- }
- }
- ++curr_pixel_ptr;
- } // x
- } // y
- timeLog("Filtered FAST features");
- return num_features;
- }
- } // namespace flow