harris.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. //
  2. // Copyright 2019 Olzhas Zhumabek <anonymous.from.applecity@gmail.com>
  3. //
  4. // Use, modification and distribution are subject to the Boost Software License,
  5. // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
  6. // http://www.boost.org/LICENSE_1_0.txt)
  7. //
  8. #include <boost/gil/image.hpp>
  9. #include <boost/gil/image_view.hpp>
  10. #include <boost/gil/extension/io/png.hpp>
  11. #include <boost/gil/image_processing/numeric.hpp>
  12. #include <boost/gil/image_processing/harris.hpp>
  13. #include <boost/gil/extension/numeric/convolve.hpp>
  14. #include <vector>
  15. #include <functional>
  16. #include <set>
  17. #include <iostream>
  18. #include <fstream>
  19. namespace gil = boost::gil;
  20. // some images might produce artifacts
  21. // when converted to grayscale,
  22. // which was previously observed on
  23. // canny edge detector for test input
  24. // used for this example
  25. gil::gray8_image_t to_grayscale(gil::rgb8_view_t original)
  26. {
  27. gil::gray8_image_t output_image(original.dimensions());
  28. auto output = gil::view(output_image);
  29. constexpr double max_channel_intensity = (std::numeric_limits<std::uint8_t>::max)();
  30. for (long int y = 0; y < original.height(); ++y) {
  31. for (long int x = 0; x < original.width(); ++x) {
  32. // scale the values into range [0, 1] and calculate linear intensity
  33. double red_intensity = original(x, y).at(std::integral_constant<int, 0>{})
  34. / max_channel_intensity;
  35. double green_intensity = original(x, y).at(std::integral_constant<int, 1>{})
  36. / max_channel_intensity;
  37. double blue_intensity = original(x, y).at(std::integral_constant<int, 2>{})
  38. / max_channel_intensity;
  39. auto linear_luminosity = 0.2126 * red_intensity
  40. + 0.7152 * green_intensity
  41. + 0.0722 * blue_intensity;
  42. // perform gamma adjustment
  43. double gamma_compressed_luminosity = 0;
  44. if (linear_luminosity < 0.0031308) {
  45. gamma_compressed_luminosity = linear_luminosity * 12.92;
  46. } else {
  47. gamma_compressed_luminosity = 1.055 * std::pow(linear_luminosity, 1 / 2.4) - 0.055;
  48. }
  49. // since now it is scaled, descale it back
  50. output(x, y) = gamma_compressed_luminosity * max_channel_intensity;
  51. }
  52. }
  53. return output_image;
  54. }
  55. void apply_gaussian_blur(gil::gray8_view_t input_view, gil::gray8_view_t output_view)
  56. {
  57. constexpr static auto filterHeight = 5ull;
  58. constexpr static auto filterWidth = 5ull;
  59. constexpr static double filter[filterHeight][filterWidth] =
  60. {
  61. 2, 4, 6, 4, 2,
  62. 4, 9, 12, 9, 4,
  63. 5, 12, 15, 12, 5,
  64. 4, 9, 12, 9, 4,
  65. 2, 4, 5, 4, 2,
  66. };
  67. constexpr double factor = 1.0 / 159;
  68. constexpr double bias = 0.0;
  69. const auto height = input_view.height();
  70. const auto width = input_view.width();
  71. for (long x = 0; x < width; ++x) {
  72. for (long y = 0; y < height; ++y) {
  73. double intensity = 0.0;
  74. for (size_t filter_y = 0; filter_y < filterHeight; ++filter_y) {
  75. for (size_t filter_x = 0; filter_x < filterWidth; ++filter_x) {
  76. int image_x = x - filterWidth / 2 + filter_x;
  77. int image_y = y - filterHeight / 2 + filter_y;
  78. if (image_x >= input_view.width() || image_x < 0
  79. || image_y >= input_view.height() || image_y < 0) {
  80. continue;
  81. }
  82. auto& pixel = input_view(image_x, image_y);
  83. intensity += pixel.at(std::integral_constant<int, 0>{})
  84. * filter[filter_y][filter_x];
  85. }
  86. }
  87. auto& pixel = output_view(gil::point_t(x, y));
  88. pixel = (std::min)((std::max)(int(factor * intensity + bias), 0), 255);
  89. }
  90. }
  91. }
  92. std::vector<gil::point_t> suppress(
  93. gil::gray32f_view_t harris_response,
  94. double harris_response_threshold)
  95. {
  96. std::vector<gil::point_t> corner_points;
  97. for (gil::gray32f_view_t::coord_t y = 1; y < harris_response.height() - 1; ++y)
  98. {
  99. for (gil::gray32f_view_t::coord_t x = 1; x < harris_response.width() - 1; ++x)
  100. {
  101. auto value = [](gil::gray32f_pixel_t pixel) {
  102. return pixel.at(std::integral_constant<int, 0>{});
  103. };
  104. double values[9] = {
  105. value(harris_response(x - 1, y - 1)),
  106. value(harris_response(x, y - 1)),
  107. value(harris_response(x + 1, y - 1)),
  108. value(harris_response(x - 1, y)),
  109. value(harris_response(x, y)),
  110. value(harris_response(x + 1, y)),
  111. value(harris_response(x - 1, y + 1)),
  112. value(harris_response(x, y + 1)),
  113. value(harris_response(x + 1, y + 1))
  114. };
  115. auto maxima = *std::max_element(
  116. values,
  117. values + 9,
  118. [](double lhs, double rhs)
  119. {
  120. return lhs < rhs;
  121. }
  122. );
  123. if (maxima == value(harris_response(x, y))
  124. && std::count(values, values + 9, maxima) == 1
  125. && maxima >= harris_response_threshold)
  126. {
  127. corner_points.emplace_back(x, y);
  128. }
  129. }
  130. }
  131. return corner_points;
  132. }
  133. int main(int argc, char* argv[])
  134. {
  135. if (argc != 6)
  136. {
  137. std::cout << "usage: " << argv[0] << " <input.png> <odd-window-size>"
  138. " <discrimination-constant> <harris-response-threshold> <output.png>\n";
  139. return -1;
  140. }
  141. std::size_t window_size = std::stoul(argv[2]);
  142. double discrimnation_constant = std::stof(argv[3]);
  143. long harris_response_threshold = std::stol(argv[4]);
  144. gil::rgb8_image_t input_image;
  145. gil::read_image(argv[1], input_image, gil::png_tag{});
  146. auto input_view = gil::view(input_image);
  147. auto grayscaled = to_grayscale(input_view);
  148. gil::gray8_image_t smoothed_image(grayscaled.dimensions());
  149. auto smoothed = gil::view(smoothed_image);
  150. apply_gaussian_blur(gil::view(grayscaled), smoothed);
  151. gil::gray16s_image_t x_gradient_image(grayscaled.dimensions());
  152. gil::gray16s_image_t y_gradient_image(grayscaled.dimensions());
  153. auto x_gradient = gil::view(x_gradient_image);
  154. auto y_gradient = gil::view(y_gradient_image);
  155. auto scharr_x = gil::generate_dx_scharr();
  156. gil::detail::convolve_2d(smoothed, scharr_x, x_gradient);
  157. auto scharr_y = gil::generate_dy_scharr();
  158. gil::detail::convolve_2d(smoothed, scharr_y, y_gradient);
  159. gil::gray32f_image_t m11(x_gradient.dimensions());
  160. gil::gray32f_image_t m12_21(x_gradient.dimensions());
  161. gil::gray32f_image_t m22(x_gradient.dimensions());
  162. gil::compute_tensor_entries(
  163. x_gradient,
  164. y_gradient,
  165. gil::view(m11),
  166. gil::view(m12_21),
  167. gil::view(m22)
  168. );
  169. gil::gray32f_image_t harris_response(x_gradient.dimensions());
  170. auto gaussian_kernel = gil::generate_gaussian_kernel(window_size, 0.84089642);
  171. gil::compute_harris_responses(
  172. gil::view(m11),
  173. gil::view(m12_21),
  174. gil::view(m22),
  175. gaussian_kernel,
  176. discrimnation_constant,
  177. gil::view(harris_response)
  178. );
  179. auto corner_points = suppress(gil::view(harris_response), harris_response_threshold);
  180. for (auto point: corner_points)
  181. {
  182. input_view(point) = gil::rgb8_pixel_t(0, 0, 0);
  183. input_view(point).at(std::integral_constant<int, 1>{}) = 255;
  184. }
  185. gil::write_view(argv[5], input_view, gil::png_tag{});
  186. }