Finding HSV Thresholds Via Histograms with OpenCV
c++, image-processing, opencv
Solution
Mat img = ... // from camera or some other source
// STEP 1: learning phase
Mat hsv, imgThreshed, processed, denoised;
cv::GaussianBlur(img, denoised, cv::Size(5,5), 2, 2); // remove noise
cv::cvtColor(denoised, hsv, CV_BGR2HSV);
// lets say we picked manually a region of 100x100 px with the interested color/object using mouse
cv::Mat roi = hsv (cv::Range(mousex-50, mousey+50), cv::Range(mousex-50, mousey+50));
// must split all channels to get Hue only
std::vector<cv::Mat> hsvPlanes;
cv::split(roi, hsvPlanes);
// compute statistics for Hue value
cv::Scalar mean, stddev;
cv::meanStdDev(hsvPlanes[0], mean, stddev);
// ensure we get 95% of all valid Hue samples (statistics 3*sigma rule)
float minHue = mean[0] - stddev[0]*3;
float maxHue = mean[0] + stddev[0]*3;
// STEP 2: detection phase
cv::inRange(hsvPlanes[0], cv::Scalar(minHue), cv::Scalar(maxHue), imgThreshed);
imshow("thresholded", imgThreshed);
cv_erode(imgThreshed, processed, 5); // minimizes noise
cv_dilate(processed, processed, 20); // maximize left regions
imshow("final", processed);
//STEP 3: do some blob/contour detection on processed image & find maximum blob/region, etc ...
A much simpler solution - just calculate mean & std. deviation for a region of interest, i.e. containing the Hue value. Since Hue is the most stable component in the image, the other components saturation & value should be discarded as they vary too much. However you can still compute mean for them if needed.
Problem
I'm trying to write a method that will find the proper threshold values in HSV space for an object placed at the center of the screen. These values are used for an object tracking algorithm. I've tested that piece of code with hand coded threshold values and it works well. The idea behind the method is that it should calculate the histograms for each of the channels and then return the 5th and 95th percentile for each to be used as the threshold values. (credit: How to find RGB/HSV color parameters for color tracking?) The image being passed is a picture of the object to be tracked (which is set by the user before the whole process begins. Here is the code ``` std::vector<cv::Scalar> HSV_Threshold_Determiner::Get_Threshold_Values(const cv::Mat& image) { cv::Mat inputImage; cv::cvtColor(image, inputImage, CV_BGR2HSV); std::vector<cv::Mat> bgrPlanes; cv::split(inputImage, bgrPlanes); cv::Mat hHist, sHist, vHist; int hMax = 180, svMax = 256; float hRanges[] = { 0, (float)hMax }; const float* hRange = { hRanges }; float svRanges[] = { 0, (float)svMax }; const float* svRange = { svRanges }; //float sRanges[] = { 0, 256 }; cv::calcHist(&bgrPlanes[0], 1, 0, cv::Mat(), hHist, 1, &hMax, &hRange); cv::calcHist(&bgrPlanes[1], 1, 0, cv::Mat(), sHist, 1, &svMax, &svRange); cv::calcHist(&bgrPlanes[2], 1, 0, cv::Mat(), vHist, 1, &svMax, &svRange); int totalEntries = image.cols * image.rows; int fiveCutoff = (int)(totalEntries * .05); int ninetyFiveCutoff = (int)(totalEntries * .95); float hTotal = 0, sTotal = 0, vTotal = 0; bool hMinFound = false, hMaxFound = false, sMinFound = false, sMaxFound = false, vMinFound = false, vMaxFound = false; cv::Scalar hThresholds; cv::Scalar sThresholds; cv::Scalar vThresholds; for(int i = 0; i < vHist.rows; ++i) { if(i < hHist.rows) { hTotal += hHist.at<float>(i, 0); if(hTotal >= fiveCutoff && !hMinFound) { hThresholds.val[0] = i; hMinFound = true; } else if(hTotal>= ninetyFiveCutoff && !hMaxFound) { hThresholds.val[1] = i; hMaxFound = true; } } sTotal += sHist.at<float>(i, 0); vTotal += vHist.at<float>(i, 0); if(sTotal >= fiveCutoff && !sMinFound) { sThresholds.val[0] = i; sMinFound = true; } else if(sTotal >= ninetyFiveCutoff && !sMaxFound) { sThresholds.val[1] = i; sMaxFound = true; } if(vTotal >= fiveCutoff && !vMinFound) { vThresholds.val[0] = i; vMinFound = true; } else if(vTotal >= ninetyFiveCutoff && !vMaxFound) { vThresholds.val[1] = i; vMaxFound = true; } if(vMaxFound && sMaxFound && hMaxFound) { break; } } std::vector<cv::Scalar> returnVect; returnVect.push_back(hThresholds); returnVect.push_back(sThresholds); returnVect.push_back(vThresholds); return returnVect; } ``` What I am trying to do is sum up the number of entries in each bucket until I get to a number that is greater than or equal to five percent and ninety-five percent of the total. Unfortunately the numbers I get are never close to the ones I get if I do the thresholding by hand.