Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added FAST feature detector by Edward Rosten #604

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Open
170 changes: 170 additions & 0 deletions include/boost/gil/image_processing/fast_feature_detector.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#ifndef BOOST_GIL_IMAGE_PROCESSING_FAST_HPP
#define BOOST_GIL_IMAGE_PROCESSING_FAST_HPP
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved

#include <boost/gil/image.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/gil/locator.hpp>
#include <boost/gil/point.hpp>
#include <algorithm>
#include <cmath>
#include <vector>
namespace boost { namespace gil {
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
namespace detail {
template <typename srcview>
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
bool fast_feature_detector(const srcview& buffer, int r, int c, std::vector<point_t>& points, int t)
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
{
int valid_points_count = 16;
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
auto src_loc = buffer.xy_at(c, r);
std::vector<int> threshold_indicator, intensity_array(16);
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
std::vector<decltype(src_loc.cache_location(0, -1))> pointers(valid_points_count);
//stroring intensities of pixels on circumference beforehand to decrease runtime
for (int i = 0; i < 16; i++)
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
{
pointers[i] = src_loc.cache_location(points[i][0], points[i][1]);
intensity_array[i] = src_loc[pointers[i]];
}
//calculating the flags to be used during segment test
auto const I_p = buffer(point_t(c, r));
//int low,high;
std::transform(
intensity_array.begin(),
intensity_array.end(),
back_inserter(threshold_indicator),
[low = I_p - t, hi = I_p + t](auto const& intensity) {
if (intensity < low)
return -1;
else if (intensity > hi)
return 1;
else
return 0;
});
std::transform(
intensity_array.begin(),
intensity_array.end(),
back_inserter(threshold_indicator),
[low = I_p - t, hi = I_p + t](auto const& intensity) {
if (intensity < low)
return -1;
else if (intensity > hi)
return 1;
else
return 0;
});
//high speed test for eliminating non-corners
for (int i = 0; i <= 6; i += 2)
{
if (threshold_indicator[i] == 0 && threshold_indicator[i + 8] == 0)
return false;
}
for (int i = 1; i <= 7; i += 2)
{
if (threshold_indicator[i] == 0 && threshold_indicator[i + 8] == 0)
return false;
}
//final segment test
bool is_feature_point =
threshold_indicator.end() !=
std::search_n(threshold_indicator.begin(), threshold_indicator.end(), 9, -1) ||
threshold_indicator.end() !=
std::search_n(threshold_indicator.begin(), threshold_indicator.end(), 9, 1);
return is_feature_point;
}
template <typename srcview>
std::ptrdiff_t
calculate_score(srcview& src, int i, int j, std::vector<point_t>& points, int threshold)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this formatting with indented calculate_score by the clang-format? /cc @lpranam

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lpranam I will answer myself, no, it is not clang-format output.

Your .clang-format generates expected output:

template <typename SrcView>
std::size_t calculate_score(
    SrcView const& src,
    std::size_t i,
    std::size_t j,
    std::vector<point_t>& points,
    std::size_t threshold)
{

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the .clang-format file given by @lpranam in the above links. But i was able to install clang-13 and not 12. May be that's the reason. Going to look into this matter again and update. Thanks for verifying @mloskot .

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to use clang-format 12 but
I'd also expect clang-format 13 to give output close to clang-format 12

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially unable to get clang-format 12 but now I have been able to do so.

int score = threshold;
std::ptrdiff_t low = threshold;
std::ptrdiff_t high = 255;
//score measure used= highest threshold for which a corner remains a corner. The cornerness of a corner decreases with increasing threshold
while (high - low > 1)
{
int mid = (low + high) / 2;
if (fast_feature_detector(src, i, j, points, mid))
{
low = mid;
score = std::max(score, mid);
}
else
{
high = mid - 1;
}
}
return low - 1;
}
} // namespace detail
template <typename srcview>
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
void fast(
srcview& src,
std::vector<point_t>& keypoints,
std::vector<int>& scores,
bool nonmax = true,
int threshold = 10)
{
//coordinates of a bresenham circle of radius 3
std::vector<point_t> final_points_clockwise{
point_t(3, 0),
point_t(3, 1),
point_t(2, 2),
point_t(1, 3),
point_t(0, 3),
point_t(-1, 3),
point_t(-2, 2),
point_t(-3, 1),
point_t(-3, 0),
point_t(-3, -1),
point_t(-2, -2),
point_t(-1, -3),
point_t(0, -3),
point_t(1, -3),
point_t(2, -2),
point_t(3, -1)};
//FAST features only calculated on grayscale images
auto input_image_view = color_converted_view<gray8_pixel_t>(src);
gray8_image_t FAST_image(src.dimensions());
// scores to be used during nonmaximum suppression
gray8_view_t FAST_SCORE_MATRIX = view(FAST_image);
fill_pixels(FAST_SCORE_MATRIX, gray8_pixel_t(0));
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
std::vector<point_t> kp;
for (int i = 3; i < src.height() - 3; i++)
{
for (int j = 3; j < src.width() - 3; j++)
{
if (detail::fast_feature_detector(
input_image_view, i, j, final_points_clockwise, threshold))
{
kp.push_back(point_t(j, i));
}
}
}

for (auto u : kp)
{
int score = 0;
score = detail::calculate_score(
input_image_view, u[1], u[0], final_points_clockwise, threshold);
FAST_SCORE_MATRIX(u[0], u[1])[0] = gray8_pixel_t(score);
}
for (auto u : kp)
{
int i = u[1];
int j = u[0];
int score = 0;
score = int(FAST_SCORE_MATRIX(j, i)[0]);
//performing nonmaximum suppression
if (!nonmax || score > FAST_SCORE_MATRIX(j - 1, i)[0] &&
score > FAST_SCORE_MATRIX(j + 1, i)[0] &&
score > FAST_SCORE_MATRIX(j - 1, i - 1)[0] &&
score > FAST_SCORE_MATRIX(j, i - 1)[0] &&
score > FAST_SCORE_MATRIX(j + 1, i - 1)[0] &&
score > FAST_SCORE_MATRIX(j - 1, i + 1)[0] &&
score > FAST_SCORE_MATRIX(j, i + 1)[0] &&
score > FAST_SCORE_MATRIX(j + 1, i + 1)[0])
{
keypoints.push_back(u);
scores.push_back(score);
}
}
}
}} // namespace boost::gil
#endif
Binary file added test/core/image_processing/box.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/core/image_processing/chessboard(2).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions test/core/image_processing/fast_feature_detector.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//References- Following papers of Dr.Edward Rosten
/*1.Fusing points and lines for high performance tracking.
2.Machine learning for high-speed corner detection.
3.Faster and better: A machine learning approach to corner detection*/

#include <boost/gil/extension/io/jpeg.hpp>
#include <boost/gil/extension/io/png.hpp>
#include <boost/gil/image_processing/fast_feature_detector.hpp>
#include <boost/assert.hpp>
#include <boost/core/lightweight_test.hpp>
#include <iostream>
std::uint8_t null_matrix[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
//testing an image without a feature point
void test1()
{
boost::gil::gray8_view_t image1 = boost::gil::interleaved_view(
5, 8, reinterpret_cast<boost::gil::gray8_pixel_t*>(null_matrix), 5);
std::vector<boost::gil::point_t> keypoints;
std::vector<int> scores;
boost::gil::fast(image1, keypoints, scores, 20);
std::vector<boost::gil::point_t> expected_keypoints;
BOOST_ASSERT_MSG(
expected_keypoints.size() == keypoints.size(), "dimensions do not match for keypoints");
}
//testing color image
void test2()
Sayan-Chaudhuri marked this conversation as resolved.
Show resolved Hide resolved
{
boost::gil::rgb8_image_t input_color_image;
boost::gil::read_image("box.jpg", input_color_image, boost::gil::jpeg_tag{});
std::vector<boost::gil::point_t> keypoints;
std::vector<int> scores;
boost::gil::fast(boost::gil::view(input_color_image), keypoints, scores, 20);
std::vector<boost::gil::point_t> expected_keypoints{
boost::gil::point_t(218, 19), boost::gil::point_t(44, 56), boost::gil::point_t(280, 62),
boost::gil::point_t(302, 77), boost::gil::point_t(321, 93), boost::gil::point_t(323, 93),
boost::gil::point_t(329, 96), boost::gil::point_t(45, 105), boost::gil::point_t(101, 110),
boost::gil::point_t(55, 118), boost::gil::point_t(140, 141), boost::gil::point_t(326, 141),
boost::gil::point_t(138, 143), boost::gil::point_t(314, 151), boost::gil::point_t(120, 179),
boost::gil::point_t(130, 186), boost::gil::point_t(128, 187), boost::gil::point_t(132, 191),
boost::gil::point_t(137, 195), boost::gil::point_t(139, 195), boost::gil::point_t(143, 197),
boost::gil::point_t(59, 219), boost::gil::point_t(63, 223), boost::gil::point_t(70, 231),
boost::gil::point_t(75, 237), boost::gil::point_t(81, 244), boost::gil::point_t(303, 261),
boost::gil::point_t(107, 273), boost::gil::point_t(266, 273), boost::gil::point_t(260, 275),
boost::gil::point_t(245, 280), boost::gil::point_t(115, 283), boost::gil::point_t(234, 284),
boost::gil::point_t(231, 285), boost::gil::point_t(216, 289), boost::gil::point_t(125, 293),
boost::gil::point_t(205, 294), boost::gil::point_t(132, 297), boost::gil::point_t(187, 299),
boost::gil::point_t(171, 304), boost::gil::point_t(142, 313)};

BOOST_ASSERT_MSG(
expected_keypoints.size() == keypoints.size(), "dimensions do not match for keypoints");
BOOST_ASSERT_MSG(expected_keypoints == keypoints, "keypoints do not match");
}
//testing grayscale image
void test3()
{
boost::gil::rgb8_image_t input_color_image;
boost::gil::read_image("box.jpg", input_color_image, boost::gil::jpeg_tag{});
std::vector<boost::gil::point_t> keypoints;
std::vector<int> scores;
std::vector<boost::gil::point_t> expected_keypoints{
boost::gil::point_t(218, 19), boost::gil::point_t(44, 56), boost::gil::point_t(280, 62),
boost::gil::point_t(302, 77), boost::gil::point_t(321, 93), boost::gil::point_t(323, 93),
boost::gil::point_t(329, 96), boost::gil::point_t(45, 105), boost::gil::point_t(101, 110),
boost::gil::point_t(55, 118), boost::gil::point_t(140, 141), boost::gil::point_t(326, 141),
boost::gil::point_t(138, 143), boost::gil::point_t(314, 151), boost::gil::point_t(120, 179),
boost::gil::point_t(130, 186), boost::gil::point_t(128, 187), boost::gil::point_t(132, 191),
boost::gil::point_t(137, 195), boost::gil::point_t(139, 195), boost::gil::point_t(143, 197),
boost::gil::point_t(59, 219), boost::gil::point_t(63, 223), boost::gil::point_t(70, 231),
boost::gil::point_t(75, 237), boost::gil::point_t(81, 244), boost::gil::point_t(303, 261),
boost::gil::point_t(107, 273), boost::gil::point_t(266, 273), boost::gil::point_t(260, 275),
boost::gil::point_t(245, 280), boost::gil::point_t(115, 283), boost::gil::point_t(234, 284),
boost::gil::point_t(231, 285), boost::gil::point_t(216, 289), boost::gil::point_t(125, 293),
boost::gil::point_t(205, 294), boost::gil::point_t(132, 297), boost::gil::point_t(187, 299),
boost::gil::point_t(171, 304), boost::gil::point_t(142, 313)};
boost::gil::fast(boost::gil::view(input_color_image), keypoints, scores, 10);

BOOST_ASSERT_MSG(
expected_keypoints.size() == keypoints.size(), "dimensions do not match for keypoints");
BOOST_ASSERT_MSG(expected_keypoints == keypoints, "keypoints do not match");
}
int main()
{
test1();
test2();
test3();
return boost::report_errors();
}