c++opencvimage-processingcomputer-vision

Edge Extraction Suggections OpenCV


Im looking for suggestions to improve my algorithm to search for parts in the following image

enter image description here

so far I have the following

GaussianBlur(canny, canny, Size(5, 5), 2, 2);
Canny(canny, canny, 100, 200, 5);
HoughCircles(canny, Part_Centroids, CV_HOUGH_GRADIENT, 2, 30, 100, 50, 50, 60);

My edge detect output looks like this

enter image description here

and Im using a HoughCircle to try to find the parts. I havent been having great success though because the HoughCircle seems very fussy and often returns a circle that isnt really the best match for a part.

Any suggestions on improving this search algorithm

EDIT:

I have tried the suggestions in the comments below. The normalization made some improvements but removing the canny before hough circles altered the required settings but not the stability.

I think now that I need to do something like the hough circles with very open thresholds and then find a way to score the results. Are there any good methods to score the results of hough circle or correlate the results with the canny output for percentage of match


Solution

  • I thought I would post my solution as someone may find my lessons learned valuable.

    I started by taking several frames and averaging them out. This solved some of the noise issues I was having while preserving the strong edges. Next I did a basic filter and canny edge to extract a decent edge map.

        Scalar cannyThreshold = mean(filter);
        // Canny Edge Detection
        Canny(filter, canny, cannyThreshold[0]*(2/3), cannyThreshold[0]*(1+(1/3)), 3);
    

    Next I use a cross correlation with increasing diametered templates and store matches that score over a threshold

        // Iterate through diameter ranges
        for (int r = 40; r < 70; r++)
        {
            Mat _mask, _template(Size((r * 2) + 4, (r * 2) + 4), CV_8U);
            _template = Scalar(0, 0, 0);
            _mask = _template.clone();
            _mask = Scalar(0, 0, 0);
            circle(_template, Point(r + 4, r + 4), r, Scalar(255, 255, 255), 2, CV_AA);
            circle(_template, Point(r + 4, r + 4), r / 3.592, Scalar(255, 255, 255), 2, CV_AA);
            circle(_mask, Point(r + 4, r + 4), r + 4, Scalar(255, 255, 255), -1);
    
            Mat res_32f(canny.rows, canny.cols, CV_32FC1);
            matchTemplate(canny, _template, res_32f, CV_TM_CCORR_NORMED, _mask);
            Mat resize(canny.rows, canny.cols, CV_32FC1);
            resize = Scalar(0, 0, 0);
            res_32f.copyTo(resize(Rect((resize.cols - res_32f.cols) / 2, (resize.rows - res_32f.rows) / 2, res_32f.cols, res_32f.rows)));
            // Strore Well Scoring Results
            double minVal, maxVal;
            double threshold = .25;
            do
            {
                Point minLoc, maxLoc;
                minMaxLoc(resize, &minVal, &maxVal, &minLoc, &maxLoc);
                if (maxVal > threshold)
                {
                    matches.push_back(CircleScore(maxLoc.x, maxLoc.y, r, maxVal,1));
                    circle(resize, maxLoc, 30, Scalar(0, 0, 0), -1);
                }
    
            } while (maxVal > threshold);
        }
    

    I filter out circles for the best match in each zone

    // Sort Matches For Best Match
        for (size_t i = 0; i < matches.size(); i++)
        {
            size_t j = i + 1;
            while (j < matches.size())
            {
                if (norm(Point2f(matches[i].X, matches[i].Y) - Point2f(matches[j].X, matches[j].Y)) - abs(matches[i].Radius - matches[j].Radius) < 15)
                {
                    if (matches[j].Score > matches[i].Score)
                    {
                        matches[i] = matches[j];
                    }
                    matches[j] = matches[matches.size() - 1];
                    matches.pop_back();
                    j = i + 1;
                }
                else j++;
            }
        }
    

    Next was the tricky one. I wanted to see which part was likely to be on top. I did this by examining every set of parts that are closer then the sum of there radii, then seeing if the edges in the overlap zone are a stronger match for one over the other. Any covered circle should have little strong edges in the overlap zone.

        // Layer Sort On Intersection
        for (size_t i = 0; i < matches.size(); i++)
        {
            size_t j = i + 1;
            while (j < matches.size())
            {
                double distance = norm(Point2f(matches[i].X, matches[i].Y) - Point2f(matches[j].X, matches[j].Y));
                // Potential Overlapping Part
                if (distance < ((matches[i].Radius+matches[j].Radius) - 10))
                {
                    int score_i = 0, score_j = 0;
                    Mat intersect_a(canny.rows, canny.cols, CV_8UC1);
                    Mat intersect_b(canny.rows, canny.cols, CV_8UC1);
                    intersect_a = Scalar(0, 0, 0);
                    intersect_b = Scalar(0, 0, 0);
                    circle(intersect_a, Point(cvRound(matches[i].X), cvRound(matches[i].Y)), cvRound(matches[i].Radius) +4, Scalar(255, 255, 255), -1);
                    circle(intersect_a, Point(cvRound(matches[i].X), cvRound(matches[i].Y)), cvRound(matches[i].Radius / 3.592-4), Scalar(0, 0, 0), -1);
                    circle(intersect_b, Point(cvRound(matches[j].X), cvRound(matches[j].Y)), cvRound(matches[j].Radius) + 4, Scalar(255, 255, 255), -1);
                    circle(intersect_b, Point(cvRound(matches[j].X), cvRound(matches[j].Y)), cvRound(matches[j].Radius / 3.592-4), Scalar(0, 0, 0), -1);
                    bitwise_and(intersect_a, intersect_b, intersect_a);
                    double a, h;
                    a = (matches[i].Radius*matches[i].Radius - matches[j].Radius*matches[j].Radius + distance*distance) / (2 * distance);
                    h = sqrt(matches[i].Radius*matches[i].Radius - a*a);
                    Point2f p0((matches[j].X - matches[i].X)*(a / distance) + matches[i].X, (matches[j].Y - matches[i].Y)*(a / distance) + matches[i].Y);
                    circle(intersect_a, Point2f(p0.x + h*(matches[j].Y - matches[i].Y) / distance, p0.y - h*(matches[j].X - matches[i].X) / distance), 6, Scalar(0, 0, 0), -1);
                    circle(intersect_a, Point2f(p0.x - h*(matches[j].Y - matches[i].Y) / distance, p0.y + h*(matches[j].X - matches[i].X) / distance), 6, Scalar(0, 0, 0), -1);
                    bitwise_and(intersect_a, canny, intersect_a);
                    intersect_b = Scalar(0, 0, 0);
                    circle(intersect_b, Point(cvRound(matches[i].X), cvRound(matches[i].Y)), cvRound(matches[i].Radius), Scalar(255, 255, 255), 6);
                    bitwise_and(intersect_a, intersect_b, intersect_b);
                    score_i = countNonZero(intersect_b);
                    intersect_b = Scalar(0, 0, 0);
                    circle(intersect_b, Point(cvRound(matches[j].X), cvRound(matches[j].Y)), cvRound(matches[j].Radius), Scalar(255, 255, 255), 6);
                    bitwise_and(intersect_a, intersect_b, intersect_b);
                    score_j = countNonZero(intersect_b);
                    if (score_i < score_j)matches[i].Layer = matches[j].Layer + 1;
                    if (score_j < score_i)matches[j].Layer = matches[i].Layer + 1;
                }
                j++;
            }
        }
    

    After that it was easy to extract the best part to pick(Im correlating to depth data as well

    enter image description here

    The blue circles are parts, the green circle is the tallest stack and red circles are part that are under other parts.

    I hope this may help someone else working on similar problems