Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version
  1. You should adjust your grabbing chain because you have some error, maybe interlaced/progressive or noise, or something else.
  2. HoughCircle doesn't works because your binary image doesn't contains circles
  3. HoughCircle is better with gray images because it can follow a circumference using gradient of its edge. You could try houghCircle using Green channel from RGB (red is dark in green channel). Remember that Hough could be too slow in a real-time system.
  4. With threshold you cut the image upper than a value. You loose gradients, edges and shapes. After a threshold, output shape can be completely different from input shape.
  5. If you are looking for red in HSV you should remember that there are 2 ranges for Red [0..yellow] OR [magenta..180]! Using both ranges your binary will be more complete.

Instead of searching for a circle you could search for a blob with area within a given range. If threshold is well done largest blob should be the ball! You could enforce the filter using circularity too. Below is the code to catch this result:

image description image description

There are trackbars for understand and tune the parameters. Good luck with your robot!

#include "stdafx.h"
#include "Utility.hpp"
#pragma once

const char * winName = "Param";
int minH, minS, minV;
int maxH, maxS, maxV;
int minDiameterPerc, maxDiameterPerc; //relative to img.cols
int minCircularityPerc; // circularity: 0..100%;
cv::Mat src, src_hsv;

bool BlobDetector(const cv::Mat &src_binary, cv::KeyPoint &theBall)
{
    CV_Assert(src_binary.type() == CV_8UC1);
    cv::Mat dst;
    src.copyTo(dst);
    cv::Point ptTxt =cv::Point(10, 15);
    cv::Scalar clTxt = cv::Scalar(255, 255, 0);

    cv::SimpleBlobDetector::Params blob_params;
    // Set params for binary image 
    blob_params.filterByColor = true;
    blob_params.blobColor = 255; //blobs are white
    blob_params.minThreshold = 127;
    blob_params.thresholdStep = 1;
    blob_params.maxThreshold = blob_params.minThreshold + blob_params.thresholdStep;
    blob_params.minRepeatability = 1;
    // other parameters
    blob_params.filterByInertia = false;
    blob_params.filterByConvexity = false;

    // get values from trackbar variables
    double minRadii = minDiameterPerc * src_binary.cols / 100.0 / 2.0;
    double maxRadii = maxDiameterPerc * src_binary.cols / 100.0 / 2.0;
    double minArea = CV_PI * pow(minRadii, 2);
    double maxArea = CV_PI * pow(maxRadii, 2);
    double minCircularity = 1.0 * minCircularityPerc / 100; // circularity: 0..1;

    // set filters as required
    blob_params.filterByCircularity = (minCircularity>0);
    if (blob_params.filterByCircularity)
    {
        blob_params.minCircularity = minCircularity;
        blob_params.maxCircularity = 1.0;
        cv::putText(dst, "Min Circularity:" + to_string(minCircularity), ptTxt,
            cv::FONT_HERSHEY_PLAIN, 1, clTxt);
        ptTxt.y += 15;
    }
    blob_params.filterByArea = ((minArea + maxArea)>0);
    if (blob_params.filterByArea)
    {
        blob_params.minArea = max(1, minArea); //pix
        blob_params.maxArea = max(1, maxArea); //pix
        cv::putText(dst, "Min Diam[px]:" + to_string(cvRound(minRadii * 2)), ptTxt,
            cv::FONT_HERSHEY_PLAIN, 1, clTxt);
        ptTxt.y += 15;
        cv::putText(dst, "Max Diam[px]:" + to_string(cvRound(maxRadii * 2)), ptTxt,
            cv::FONT_HERSHEY_PLAIN, 1, clTxt);
        ptTxt.y += 15;
    }

    //detects blobs
    cv::SimpleBlobDetector detector(blob_params);
    std::vector<cv::KeyPoint> keypoints;
    detector.detect(src_binary, keypoints);

    // draw all blobs in RED
    double maxRadius = 0;
    int radius;
    cv::Point center;
    int idx=-1;
    for (int i = 0; i < keypoints.size(); i++)
    {
        radius = cvRound(keypoints[i].size);
        center = cv::Point(cvRound(keypoints[i].pt.x), cvRound(keypoints[i].pt.y));
        cv::circle(dst, center, radius, cv::Scalar(0, 0, 255), 2);
        //search for largest blob
        if (radius > maxRadius)
        {
            idx = i;
            maxRadius = radius;
        }
    }
    // draw largest blob in GREEN
    if (idx >= 0)
    {
        theBall = keypoints[idx];
        radius = cvRound(keypoints[idx].size);
        center = cv::Point(cvRound(keypoints[idx].pt.x), cvRound(keypoints[idx].pt.y));
        ptTxt.x = center.x + radius + 3;
        ptTxt.y = center.y;
        cv::putText(dst, "Diam[px]:" + to_string(radius * 2), ptTxt, 
            cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 0));
        cv::circle(dst, center, radius, cv::Scalar(0, 255, 0), 2);
    }

    imshow("Blob Final", dst);

    cv::resize(dst, dst, cv::Size(), 0.5, 0.5);
    imwrite("../img/ball_robot_dst.jpg", dst);
    return (idx >= 0);
}

void onBallTrackBar(int, void*)
{
    cv::Mat ball_mask;
    // There are 2 range for Red in the Hue !!
    // 0..orange OR purple..180
    uptoOrange, fromPurple;

    cv::inRange(src_hsv, cv::Scalar(0, minS, minV), cv::Scalar(minH, maxS, maxV), uptoOrange);
    cv::inRange(src_hsv, cv::Scalar(maxH, minS, minV), cv::Scalar(180, maxS, maxV), fromPurple);
    ball_mask = uptoOrange | fromPurple;
    cv::imshow("Step 1 - Threshold", ball_mask);
    imwrite("../img/ball_mask_threshold.jpg", ball_mask);

    //restore a bit
    int size = 4;
    cv::Point anchor = cv::Point(size, size);
    cv::Mat element = getStructuringElement(
        cv::MORPH_ELLIPSE, 
        cv::Size(2 * size + 1, 2 * size + 1), 
        anchor);
    morphologyEx(ball_mask, ball_mask, cv::MORPH_CLOSE, element, anchor);
    cv::imshow("Step 2 - Restore with Morph", ball_mask);

    cv::KeyPoint theBall;
    if (BlobDetector(ball_mask, theBall))
        cout << "Ball found in:" << theBall.pt << "\t"
        << "Radius: " << theBall.size << endl;
    else
        cout << "\tBall NOT found!!" << endl;

    //save the ball_mask for Q&A
    cv::resize(ball_mask, ball_mask, cv::Size(), 0.5, 0.5);
    string txt;
    cv::Point pt = cv::Point(10, 180);
    int font = cv::FONT_HERSHEY_PLAIN;
    txt = "Hue: 0.." + to_string(minH) + " + " + to_string(maxH) + "..180";
    cv::putText(ball_mask, txt, pt, font, 1, 255); pt.y += 15;
    txt = "Saturation: " + to_string(minS) + ".." + to_string(maxS);
    cv::putText(ball_mask, txt, pt, font, 1, 255); pt.y += 15;
    txt = "Value: " + to_string(minV) + ".." + to_string(maxV);
    cv::putText(ball_mask, txt, pt, font, 1, 255); pt.y += 15;
    txt = "MORPH:CLOSE:ELLIPSE:9";
    cv::putText(ball_mask, txt, pt, font, 1, 255); pt.y += 15;
    imwrite("../img/ball_mask_restored.jpg", ball_mask);
}

int main(int argc, char* argv[])
{
    src = cv::imread("../img/ball_robot.jpg");
    // remove a bit of noise
    cv::GaussianBlur(src, src, cv::Size(3, 3), 0, 0);
    // take HSV image
    cv::cvtColor(src, src_hsv, cv::COLOR_BGR2HSV);
    //default values for trackbars
    minH = 11; minS = 50; minV = 50;
    maxH = 160; maxS = 255; maxV = 255;
    minDiameterPerc = 2;
    maxDiameterPerc = 7;
    minCircularityPerc = 70;
    cv::namedWindow(winName, cv::WINDOW_NORMAL);
    cv::createTrackbar("Hue\nMin", winName, &minH, 180, onBallTrackBar);
    cv::createTrackbar("Hue\nMax", winName, &maxH, 180, onBallTrackBar);
    cv::createTrackbar("Sat.\nMin", winName, &minS, 255, onBallTrackBar);
    cv::createTrackbar("Sat.\nMax", winName, &maxS, 255, onBallTrackBar);
    cv::createTrackbar("Value\nMin", winName, &minV, 255, onBallTrackBar);
    cv::createTrackbar("Value\nMax", winName, &maxV, 255, onBallTrackBar);
    cv::createTrackbar("Diam%\nMin", winName, &minDiameterPerc, 20, onBallTrackBar);
    cv::createTrackbar("Diam%\nMax", winName, &maxDiameterPerc, 20, onBallTrackBar);
    cv::createTrackbar("Circularity", winName, &minCircularityPerc, 100, onBallTrackBar);
    cv::resizeWindow(winName, 600, 1);
    cv::moveWindow(winName, 0, 0);
    onBallTrackBar(0, 0);
    cv::waitKey(0);
}