findContours Duplicates

I am using OpenCV 4.5.0 on a Win10 system with Visual Studio 2019 C++.

My question relates to findContours returning duplicates.
The problem is that findContours intermittently returns duplicate contour points where the same point may be in the contour list multiple times (though not right next to each other). Because they are not right next to each other, you cannot simply delete them and preserve the contour.

For example, in the following images, the same item is imaged and in each there is a single contour. The contour difference is solely due to contours appearing twice.

2021-01-22_15-28-44
2021-01-22_15-48-20

After doing Canny on the image, I get the contours using:
findContours(MaskFrame, Contours, Hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);

I have also added a Gaussian blur such as:
GaussianBlur(tImageUnMap,tImageUnMap,cv::Size(5,5),1.5);
Adding Gaussian blur helps some but results in a 50% performance hit.
Though improved, even with Gaussian blur, a significant percentage of the contours have duplicates.

Questions have been asked about this in the past, including by me.
Now my question: Other than Gaussian blur and deleting contours that have duplicate points entirely, is there any other approach to minimizing the number of contours that have duplicate points and/or addressing them?

can you share code and orignal image?

Here is a link to the code and an XML file with the raw image.
https://drive.google.com/drive/folders/15fgQeUXAFV8LLjD6mn7UBVnZhnCxEW_k?usp=sharing

This code indicates that 10 contours are detected, 9 of which have duplicates.
Only the sixth (starting from zero) contour does not have duplicates.
Any help in figuring this out appreciated.

For the purposes of the forum, I am including a copy of the code.
The forum will not let me upload an XML file or any file big enough to store the raw image.

// constants for image processing
#define BILATERAL_DIAMETER	5
#define CANNY_SOBEL_SIZE	3

// constants for image cropping
#define CROPTOP				0
#define CROPLEFT			443
#define CROPRIGHT			545
#define CROPBOTTOM			169

#define OPENCV_BLACK		Scalar(0, 0, 0)

using namespace cv;

int main()
{
	    Mat LoadFrame, GrayFrame, AnalysisFrame, MaskFrame;
    bool Opened = false;
    std::vector<std::vector<Point>> Contours;
    std::vector<Vec4i> Hierarchy;
    bool duplicatesfound = false;
    int ContoursWithDupes = 0;
    int BiggestContour = 0;

    FileStorage fs("findContoursDuplicates.xml", FileStorage::READ);
    Opened = fs.isOpened();

    if (Opened)
	    fs["LoadFrame"] >> LoadFrame;

	fs.releaseAndGetString();

    if (Opened)
    {
	     // convert the image to gray for Canny
	    cvtColor(LoadFrame, GrayFrame, COLOR_BGR2GRAY);

	    // apply a bilateral filter to reduce noise
	    bilateralFilter(GrayFrame, AnalysisFrame, BILATERAL_DIAMETER, 25, 25);

	    // run Canny and make white lines
	    MaskFrame = Mat::zeros(AnalysisFrame.rows, AnalysisFrame.cols, CV_8UC3);
	    Canny(AnalysisFrame, MaskFrame, 50, 100, CANNY_SOBEL_SIZE, false);

	    // crop the image to get rid of unwanted areas appearing in camera
	    if (CROPTOP != 0)
		    cv::rectangle(MaskFrame, cv::Point(0, 0), cv::Point(MaskFrame.cols, CROPTOP), OPENCV_BLACK, -1, 8);
	    if (CROPLEFT != 0)
		    cv::rectangle(MaskFrame, cv::Point(0, 0), cv::Point(CROPLEFT, MaskFrame.rows), OPENCV_BLACK, -1, 8);
	    if (CROPBOTTOM != 0)
		    cv::rectangle(MaskFrame, cv::Point(0, MaskFrame.rows - CROPBOTTOM - 1), cv::Point(MaskFrame.cols - 1, MaskFrame.rows - 1), OPENCV_BLACK, -1, 8);
	    if (CROPRIGHT != 0)
		    cv::rectangle(MaskFrame, cv::Point(MaskFrame.cols - CROPRIGHT - 1, 0), cv::Point(MaskFrame.cols - 1, MaskFrame.rows - 1), OPENCV_BLACK, -1, 8);

	    // determine the contours and store result in Contours
	    findContours(MaskFrame, Contours, Hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);

	    // check for duplicates
	    for (int x = 0; x < Contours.size(); x++)
	    {
		    for (int y = 0; y < Contours[x].size(); y++)
		    {
			    if (Contours[x].size() > BiggestContour)
				    BiggestContour = Contours[x].size();

			    for (int z = y + 1; z < Contours[x].size(); z++)
			    {
				    if (Contours[x][y].x == Contours[x][z].x)
				    {
					    if (Contours[x][y].y == Contours[x][z].y)
					    {
						    duplicatesfound = true;
						    ContoursWithDupes++;
						    break;
					    }
				    }
			    }

			    if (duplicatesfound == true)
				    break;
		    }

		    if (duplicatesfound == true)
		    {
			    std::cout << "Duplicate Contour: " << x; 
			    for (int y = 0; y < Contours[x].size(); y++)
				    std::cout << " (" << Contours[x][y].x << "," << Contours[x][y].y << ")";
			    std::cout << std::endl;
		    }
		    duplicatesfound = false;
	    }

	    if (ContoursWithDupes != 0)
	    {
		    std::cout << "Total Contours: " << Contours.size() << std::endl;
		    std::cout << "Biggest Contour: " << BiggestContour << std::endl;
		    std::cout << "Contours With Dupes: " << ContoursWithDupes << std::endl;
	    }
	    else
	    {
		    imshow("Display Window", MaskFrame);
		    waitKey(4000);
	    }
    }
    else
	    std::cout << "File Not Found\n";

}

I extracted the picture, here’s a preview:

what you found is duplicate points in a contour, not duplicate contours. your check hits as soon as it finds a single repeated point. it does not check different contours against each other.

duplicate points can happen.

especially when you run on Cannied data, which is just lines. a contour does not describe a line, but an edge. a line has two edges.

you shouldn’t use Canny here. most problems are only made worse by canny.

use a simple threshold.

your MaskFrame should be CV_8U, not 8UC3. that is an issue but may not be related to your issue.

Thanks for your response and suggestions.

Yes, I am trying to eliminate duplicate contour points within a single contour.
Having duplicate points within a contour causes all sorts of problems for various shape description approaches.

For my purposes, I either have to:

  1. totally eliminate the possibility of there being duplicate points within a contour, or
  2. check each contour for duplicate points so I can delete the contour entirely.

Checking each contour for duplicate points would cause a serious performance hit as the cost of checking each contour is NxN comparisons, where N is the number of points within the contour. Indeed, my original example of a contour having more than 3,200 points within it would require more than 10 million comparisons to determine that there were no duplicates (using the simple nested loops I used).

As I am understanding it, findContours can return the same point within a contour if the starting image has an edge that is one pixel wide. Can the issue be solved by using dilate with a 2x2 kernel? Would that eliminate the chance of a single pixel wide edge and, therefore, of findContours returning duplicate points within a contour? Doing that on the test image eliminates the problem but, of course, I need a general solution. Is it possible that dilation could cause points that were previously separated to grow closer so that you could, again, get a single pixel wide edge?

I will play around with thresholding instead of Canny.

Thanks for any feedback.