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

Add example of canny edge detection algorithm #258

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

Antropovi
Copy link

@Antropovi Antropovi commented Mar 15, 2019

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

@mloskot mloskot requested a review from a team March 15, 2019 17:28
Copy link
Member

@mloskot mloskot left a comment

Choose a reason for hiding this comment

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

Thank you for this contribution.

I've done quick review and made a few comments. LGTM.

//Example how to detect edges via Canny algorithm

template <typename SrcView>
void smoothing(const SrcView &src)
Copy link
Member

Choose a reason for hiding this comment

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

gaussian_blur or gaussian_smoothing wouldn't be a clearer name?

It is an example with learning as primary purpose, isn't it.

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) == 150) {
if (dst_loc(-1, -1) == 255 || dst_loc(-1, 0) == 255 ||
Copy link
Member

Choose a reason for hiding this comment

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

Please, add comment explaining where do the 150 and 255 come from.

Copy link
Member

@mloskot mloskot left a comment

Choose a reason for hiding this comment

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

@Antropovi This looks very good! Just a couple of minor comments.

using namespace boost::gil;

// TOP_VALUE - value of strong edge pixel
#define TOP_VALUE 255
Copy link
Member

Choose a reason for hiding this comment

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

Please, avoid macros and prefer C++ qualities like const, constexpr.

using namespace std;
using namespace boost::gil;

// TOP_VALUE - value of strong edge pixel
Copy link
Member

Choose a reason for hiding this comment

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

No need to repeat constant name

@mloskot mloskot requested a review from stefanseefeld March 20, 2019 11:57
@mloskot
Copy link
Member

mloskot commented Mar 29, 2019

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. #258

@simmplecoder
Copy link
Contributor

simmplecoder commented Apr 2, 2019

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.

@Antropovi
Copy link
Author

Antropovi commented Apr 3, 2019

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.

@simmplecoder
Copy link
Contributor

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.

@mloskot
Copy link
Member

mloskot commented Apr 9, 2019

@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.

@mloskot
Copy link
Member

mloskot commented Apr 28, 2019

@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?

@mloskot mloskot added status/need-help Issues where help and contributions are welcome status/work-in-progress Do NOT merge yet until this label has been removed! labels Sep 5, 2019
@mloskot mloskot added the cat/feature New feature or functionality label Jan 31, 2020
@mloskot mloskot added the google-summer-of-code All items related to GSoC activities label Feb 22, 2020
@mloskot mloskot added the example Examples of how to use GIL label Mar 25, 2020
@mloskot mloskot added status/need-feedback Asking for more details about the problem and removed status/need-help Issues where help and contributions are welcome labels Jun 3, 2022
@Bockeman
Copy link

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.

@mloskot
Copy link
Member

mloskot commented Oct 26, 2022

@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.

@Bockeman
Copy link

Bockeman commented Oct 26, 2022

@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)

@mloskot
Copy link
Member

mloskot commented Oct 27, 2022

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cat/feature New feature or functionality example Examples of how to use GIL google-summer-of-code All items related to GSoC activities status/need-feedback Asking for more details about the problem status/work-in-progress Do NOT merge yet until this label has been removed!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants