Canny on a concave grayscale, the result contours can not end in closure


i have a grayscale which has only binary color (on left of above image), run Canny on it, the right part of above image is result. The result contours can not end in closure, the contours just wrap the border like the right line. So points like (60,50)(70,60) are out of polygon from contours by using cv::pointPolygonTest.

simple test code as following:

#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"

using namespace cv;
using namespace std;

#define AREA_COLOR_IN_GRAYSCALE 100

Mat ReadGrayscale(const char* filename)
{
    Mat src = imread(filename);
    if( src.empty() )
    {
        printf("Could not open or find the image!\n");
    }
    return src;
}

void GetContoursAndGenPolygon(const char *filename)
{
    int32_t threshH = AREA_COLOR_IN_GRAYSCALE - 1;
    int32_t threshL = threshH >> 1;
    Mat grayscale = ReadGrayscale(filename);
    Mat edgeMat;
    Canny(grayscale, edgeMat, threshL, threshH);

    cv::imwrite("edge.ppm", edgeMat);

    vector<vector<Point> > contours;
    findContours(edgeMat, contours, CV_RETR_EXTERNAL, CHAIN_APPROX_SIMPLE );

    for( size_t i = 0; i< contours.size(); i++ )
    {
        printf("contour[%zd] has %zd points\n", i, contours[i].size());

        for (const auto& p : contours[i])
        {
            printf("(%d,%d) ", p.x, p.y);
        }
        printf("\n");        
    }

    auto checkPolygon = [&](cv::Point2f p)
    {
        double d = cv::pointPolygonTest(contours[0], p, false);
        printf("(%.0f,%.0f) is %s polygon, d %.2f\n", p.x, p.y, (d >= 0.0) ? "in" : "out of", d);
    };
    if (contours.size() >= 1)
    {
        checkPolygon(cv::Point2f(60,50));
        checkPolygon(cv::Point2f(70,60));
    }
}

int main( int argc, char** argv )
{
    printf("opencv verison %d.%d\n", CV_MAJOR_VERSION, CV_MINOR_VERSION);
    GetContoursAndGenPolygon("concave.ppm");
    return 0;
}

the output

opencv verison 3.3
contour[0] has 29 points
(57,46) (55,48) (55,51) (56,52) (64,52) (65,53) (65,64) (66,65) (72,65) (72,54) (71,53) (67,53) (71,53) (72,54) (72,64) (71,65) (66,65) (65,64) (65,53) (64,52) (56,52) (55,51) (55,48) (57,46) (65,46) (66,47) (66,51) (66,47) (65,46) 
(60,50) is out of polygon, d -1.00
(70,60) is out of polygon, d -1.00

My questions:
1. why contours are not closed around point (66,52)
2. how to avoid it, i want to get a correct polygon

concave
source image with extension jpg

don’t use Canny here. Canny is rarely useful and most often harms because beginners think they have to apply it to everything.

simply apply findContours.

who told you that you should use Canny for this?

1 Like

3ks a lot.
it works to just apply findContours on grayscale. great!
I reference some opencv doc, such as this, it Canny and then findContours, so i thought it was a correct routine

1 Like

wow that thing is rubbish. “you will learn”, yeah right, code dump, tada, curtain call. I’m sorry you had to see that article. one of these days I’ve got to put big red warning signs on every use of Canny in the docs.

it’s absolutely not something to just apply for the hell of it. just like an appendectomy, it’s an intervention with serious negative consequences, and only justified if all its effects are acknowledged.

as long as you have clear contrast, meaning two distinct “modes” in the histogram that are easy to threshold globally, all methods that need a mask (binarized image) will just work.

if you don’t have that, i.e. you need to deal with multiple contrasts at various levels, you would want to apply some kind of “highpass” filter, to move those modes such that you can threshold easily. that’s still not where Canny is used.

Canny implicitly calculates edges/gradients of the image. that is done when you don’t care about the levels in your picture, just the edges. Canny is an extremely crude postprocessing step applied after having an edge map. it adaptively thresholds the edge map of a picture, i.e. first an edge map is computed (a general operation) and then Canny comes along and binarizes that adaptively.

you don’t need a highpass or an edge map. you just needed to threshold the picture to clearly differentiate black from gray. in your case, the background is already black (zero) and the foreground is gray (nonzero), so that can already be interpreted as a binary mask.

1 Like

thank you for your explanation about Canny.
Let me simplify my case. My goal is to extract polygon (rectangle is preferred if possible) for gray pattern in the image.I have already generated a binarized image and smoothed it, so just findContours on it will work.
is there anything else i should take care, or any more advice?

thank you for your explanation about Canny.
Let me simplify my case. My goal is to extract polygon (rectangle is preferred if possible) for gray pattern in the image.I have already generated a binarized image and smoothed it, so just findContours on it will work.
is there anything else i should take care, or any more advice?

findContours is sufficient.