gil
gil copied to clipboard
Add example of canny edge detection algorithm
Add example of canny edge detection using gil as my competency test for GSoC 2019
References
Find more about Canny edge detection on https://en.wikipedia.org/wiki/Canny_edge_detector
Tasklist
- [ ] Review
- [ ] Adjust for comments
- ~~All CI builds and checks have passed~~ ATM, examples are not built by CI jobs
For complete records and references, here is @Antropovi confirmation this is intended as competency test:
https://lists.boost.org/Archives/boost/2019/03/245630.php
As my competencytest, I have implement a simple Canny edge detection algorithm using GIL. https://github.com/boostorg/gil/pull/258
I believe there is a bug in the last hysteresis step: it will only detect Г shaped edges, and not horizontally flipped L shaped edges. Let me give an example (the values are somewhere in the image padded by zeroes on all sides, the values are intensity of the pixel's luminosity):
Case which current code will detect:
1 0.5 0.5 1 1 1
0.5 0 0 ====> 1 0 0
05. 0 0 1 0 0
Case which current code will not detect
0.5 0.5 0.5 1 1 1
0.5 0 0.5 ====> 1 0 1
0.5 0.5 1 1 1 1
Here are the lines of code which are connected to the issues:
// Weak pixels check
// If weak pixel has a strong neighbor, then it is strong one
// otherwise it is definitely not-edge pixel
auto dst_loc = dst.xy_at(1, 1);
for (int i = 1; i < dst.height() - 1; ++i) {
for (int j = 1; j < dst.width() - 1; ++j, ++dst_loc.x()) {
if (dst_loc(0, 0) == MIDDLE_VALUE) {
if (dst_loc(-1, -1) == TOP_VALUE || dst_loc(-1, 0) == TOP_VALUE ||
dst_loc(-1, 1) == TOP_VALUE || dst_loc(1, -1) == TOP_VALUE ||
dst_loc(1, 0) == TOP_VALUE || dst_loc(1, 1) == TOP_VALUE ||
dst_loc(0, -1) == TOP_VALUE || dst_loc(0, 1) == TOP_VALUE)
dst_loc(0, 0) = TOP_VALUE;
else
dst_loc(0, 0) = BOTTOM_VALUE;
}
}
dst_loc += point2<std::ptrdiff_t>(-dst.width() + 2, 1);
}
Instead, I believe there should be connected components search. The search process basically tries to hop over weak edge or strong edge and reach all of the reachable pixels from start location. If there is at least one strong pixel in the connected component, all should be marked as strong too.
Could you please verify if my theory is correct? I believe a good input image for this test case should be one of the O shaped Unicode characters with upper diagonal part blurred out.
This isn't a bug and there are some reasons, why I chose this implementation. First and most important - this is an example. It should be easy to understand. There are a lot of guides and blog posts about this specific implementation. Second - this is simple Canny algorithm. There are more precise and complex algorithms to detect edges. This one dont even construct a list of all edges. Third - you are right. If you use image and rotated on 180 copy of it, output will be slightly different, but the main point still how to choose best threshold values. And btw hysteresis step is not about detection edges. It's more about connection neighbour edges.
Sorry for choosing confusing wording. I meant that weak edges that should've been included in the output will not be included. Thanks for clarification on the decisions.
@Antropovi Thank you very much again for your example and explanation. I'd like to incorporate it into GIL's collection in example/
. Hopefully, I'll be able to do it in near future. So, let's keep this PR open.
@stefanseefeld Shall we add those as they are (optionally, with minor stylistic refactoring) to example/
folder? I think, it's never too many examples showing how to actually use GIL. What do you think?
OTOH, perhaps this could be reworked as a library feature, a callable algorithm, not just an example. Any takers?
With boost 1.80.0 and -std=c++20
I get the following compile errors:
/usr/include/boost/gil/extension/numeric/kernel.hpp:16:1: note: ?#pragma message: This header is deprecated. Use <boost/gil/image_processing/kernel.hpp> instead.?
16 | BOOST_HEADER_DEPRECATED("<boost/gil/image_processing/kernel.hpp>")
| ^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/boost/gil/extension/numeric/convolve.hpp:15:1: note: ?#pragma message: This header is deprecated. Use <boost/gil/image_processing/convolve.hpp> instead.?
15 | BOOST_HEADER_DEPRECATED("<boost/gil/image_processing/convolve.hpp>")
| ^~~~~~~~~~~~~~~~~~~~~~~
In file included from src/rgw16_edge_align.cpp:32:
src/rgw16_edge_detect.hpp: In function ?void gaussian_blur(const SrcView&)?:
src/rgw16_edge_detect.hpp:49:52: error: ?convolve_option_output_ignore? was not declared in this scope
49 | convolve_rows<gray32f_pixel_t>(src, kernel, src, convolve_option_output_ignore);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/rgw16_edge_detect.hpp: In function ?void sobel_filtering(const boost::gil::gray8c_view_t&, const boost::gil::gray8_view_t&, const boost::gil::gray32f_view_t&)?:
src/rgw16_edge_detect.hpp:76:75: error: ?convolve_option_output_zero? was not declared in this scope
76 | convolve_rows<gray32f_pixel_t>(src, first_sobel_kernel, view(vertical), convolve_option_output_zero);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
src/rgw16_edge_detect.hpp: In function ?int main_detect(int, char**)?:
src/rgw16_edge_detect.hpp:229:35: error: ISO C++ forbids converting a string constant to ?char*? [-Werror=write-strings]
229 | char* input = "test.jpg";
| ^~~~~~~~~~
src/rgw16_edge_detect.hpp:230:35: error: ISO C++ forbids converting a string constant to ?char*? [-Werror=write-strings]
230 | char* output = "canny.jpg";
| ^~~~~~~~~~~
src/rgw16_edge_detect.hpp: In function ?void non_maximal_suppression(const boost::gil::gray8c_view_t&, const boost::gil::gray32fc_view_t&, const boost::gil::gray8_view_t&)?:
src/rgw16_edge_detect.hpp:120:10: error: ?r? may be used uninitialized [-Werror=maybe-uninitialized]
120 | int q, r;
| ^
src/rgw16_edge_detect.hpp:120:7: error: ?q? may be used uninitialized [-Werror=maybe-uninitialized]
120 | int q, r;
| ^
All of which are trivially fixed.
It would be nice if example code did not contain compile errors.
@Bockeman it would be nice if you made it clear that your comment relates to this particular PR in any way. Otherwise, it would be nice if you posted it to discussion thread or report a bug or it would be even nicer if you could submit PR with the trivial fixes. Thanks in advance.
@mloskot Thanks for taking notice! (I have not yet figured out what PR stands for. Not doubt as soon as I submit this comment, it will dawn on me.)
My comment above relates to the example/canny.cpp code, the only one in the "files changed" tab above. (Forgive me, I had assumed this was obvious)
Here are my trivial fixes: (But I am fairly certain these are not in the most appropriate format, and I am nowhere near confident enough to attempt to edit/annoate the source code, as others have done beautifully [with coloured code segments, etc.] above.)
/usr/include/boost/gil/extension/numeric/kernel.hpp:16:1: note: ?#pragma message: This header is deprecated. Use <boost/gil/image_processing/kernel.hpp> instead.?
16 | BOOST_HEADER_DEPRECATED("<boost/gil/image_processing/kernel.hpp>")
| ^~~~~~~~~~~~~~~~~~~~~~~
my fix:
//#include <boost/gil/extension/numeric/kernel.hpp> // #pragma message: This header is deprecated.
#include <boost/gil/image_processing/kernel.hpp>
/usr/include/boost/gil/extension/numeric/convolve.hpp:15:1: note: ?#pragma message: This header is deprecated. Use <boost/gil/image_processing/convolve.hpp> instead.?
15 | BOOST_HEADER_DEPRECATED("<boost/gil/image_processing/convolve.hpp>")
| ^~~~~~~~~~~~~~~~~~~~~~~
my fix:
//#include <boost/gil/extension/numeric/convolve.hpp> // #pragma message: This header is deprecated.
#include <boost/gil/image_processing/convolve.hpp>
In file included from src/rgw16_edge_align.cpp:32:
src/rgw16_edge_detect.hpp: In function ?void gaussian_blur(const SrcView&)?:
src/rgw16_edge_detect.hpp:49:52: error: ?convolve_option_output_ignore? was not declared in this scope
49 | convolve_rows<gray32f_pixel_t>(src, kernel, src, convolve_option_output_ignore);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
my fix:
convolve_rows<gray32f_pixel_t>(src, kernel, src
, boundary_option::output_ignore);
//, convolve_option_output_ignore); // error: ?convolve_option_output_ignore? was not declared in this scope
convolve_cols<gray32f_pixel_t>(src, kernel, src
, boundary_option::output_ignore);
//, convolve_option_output_ignore); // error: ?convolve_option_output_ignore? was not declared in this scope
src/rgw16_edge_detect.hpp: In function ?void sobel_filtering(const boost::gil::gray8c_view_t&, const boost::gil::gray8_view_t&, const boost::gil::gray32f_view_t&)?:
src/rgw16_edge_detect.hpp:76:75: error: ?convolve_option_output_zero? was not declared in this scope
76 | convolve_rows<gray32f_pixel_t>(src, first_sobel_kernel, view(vertical), convolve_option_output_zero);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
my fix:
convolve_rows<gray32f_pixel_t>(src, first_sobel_kernel, view(vertical)
, boundary_option::output_ignore);
//, convolve_option_output_zero); // error: ?convolve_option_output_ignore? was not declared in this scope
convolve_cols<gray32f_pixel_t>(const_view(vertical), second_sobel_kernel, view(vertical)
, boundary_option::output_ignore);
//, convolve_option_output_zero); // error: ?convolve_option_output_ignore? was not declared in this scope
convolve_rows<gray32f_pixel_t>(src, second_sobel_kernel, view(horizontal)
, boundary_option::output_ignore);
//, convolve_option_output_zero); // error: ?convolve_option_output_ignore? was not declared in this scope
convolve_cols<gray32f_pixel_t>(const_view(horizontal), first_sobel_kernel, view(horizontal)
, boundary_option::output_ignore);
//, convolve_option_output_zero); // error: ?convolve_option_output_ignore? was not declared in this scope
src/rgw16_edge_detect.hpp: In function ?int main_detect(int, char**)?:
src/rgw16_edge_detect.hpp:229:35: error: ISO C++ forbids converting a string constant to ?char*? [-Werror=write-strings]
229 | char* input = "test.jpg";
| ^~~~~~~~~~
src/rgw16_edge_detect.hpp:230:35: error: ISO C++ forbids converting a string constant to ?char*? [-Werror=write-strings]
230 | char* output = "canny.jpg";
| ^~~~~~~~~~~
my fix:
//char* input = "test.jpg"; // error: ISO C++ forbids converting a string constant to ?char*? [-Werror=write-strings]
//char* output = "canny.jpg"; // error: ISO C++ forbids converting a string constant to ?char*? [-Werror=write-strings]
//char input[] = "test.jpg"; // below: error: incompatible types in assignment of ?char*? to ?char [9]?
//char output[] = "canny.jpg"; // below: error: incompatible types in assignment of ?char*? to ?char [10]?
std::string input = "test.jpg";
std::string output = "canny.jpg";
src/rgw16_edge_detect.hpp: In function ?void non_maximal_suppression(const boost::gil::gray8c_view_t&, const boost::gil::gray32fc_view_t&, const boost::gil::gray8_view_t&)?:
src/rgw16_edge_detect.hpp:120:10: error: ?r? may be used uninitialized [-Werror=maybe-uninitialized]
120 | int q, r;
| ^
src/rgw16_edge_detect.hpp:120:7: error: ?q? may be used uninitialized [-Werror=maybe-uninitialized]
120 | int q, r;
| ^
//int q, r; // error: ?r? may be used uninitialized [-Werror=maybe-uninitialized]
int q(0);
int r(0);
Thanks @Antropovi for contributing this code. It works well for me and saved me a lot of time and effort in painstakingly constucting such from scratch. (Aside: I am currently attempting some tweaks, because unwanted artefacts are also being detected as edges)
@Bockeman
(I have not yet figured out what PR stands for.
PR = Pull Request
My comment above relates to the example/canny.cpp code, the only one in the "files changed" tab above.
I see. Well, this PR is a proposal and it has not been accepted and merged into the GIL sources. So it should be considered as a work in progress.
(Aside: I am currently attempting some tweaks, because unwanted artefacts are also being detected as edges)
If you have an improved version of this example here, then please, feel free to contribute it to GIL and submit your version as new PR.