1 | initial version |
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:
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);
}