2
votes

I'm using c++ but answers in python are fine too.

I need to convert a known BGR color to HSV in order to use the cv::inRange method which allows you to extract a certain part of an image. I already have code that estimates the BGR color I need well enough so I thought I should be able to just guess around it's range.

So I refereed to this link: Convert a single color with cvtColor which seems to work for converting an BGR to HSV, though I still got some weird output as I will explain later.

Anyways here is my code:

//store BGR values and convert BGR to HSV
cv::Mat3f hairColor(cv::Vec3f(averageBlue, averageGreen, averageRed));
cv::Mat3f finalHSV;
cv::cvtColor(hairColor, finalHSV, CV_BGR2HSV);

//change values to be valid with the cv::inRange function (find this part odd)
finalHSV.ptr<float>(0)[0] /= 2;
finalHSV.ptr<float>(0)[1] *= 255;

//store hsv values as integers
int averageHue = finalHSV.ptr<float>(0)[0];
int averageSat = finalHSV.ptr<float>(0)[1];
int averageValue = finalHSV.ptr<float>(0)[2];

//try to appromimate ranges. I'm trying to mess wtih these values but can't calibrate it whatsoever
int hueMin = averageHue - 20;   int hueMax = averageHue + 100;
int saturationMin = averageSat - 20;    int saturationMax = averageSat + 20;
int valueMin = averageValue - 50;   int valueMax = averageValue + 50;


//bw is the output array for my mask.
cv::inRange(hsv, cv::Scalar(hueMin, saturationMin, valueMin), cv::Scalar(hueMax, saturationMax, valueMax), bw);

So here is quick example: my code determines the approximate BGR color I want has values [126, 105, 98]. Converting to HSV intially gives [225, 0.22, 126] which seems to oddly be incorrect with how opencv stores hsv (like hue is 0-179 I think) so I do 2 conversions to get [112, 56, 126] which I think should be correct?

Anyways when I try to tinker with values in my inRange function I can't really get any good extraction, I tend to just get a black screen (I tested that my mask works and it does so the issue should be in the code provided).

Is there a better way of going about this task?

1

1 Answers

1
votes

Your second conversion is correct (well, if you truncate to int instead of rounding).

You can always just do the actual calculations for the value as given in the OpenCV docs for cvtColor.

This is the exact formula linked written in Python. Sorry it's not C++ but I wrote it as basic as possible so it was followable for someone in any other language:

b, g, r = 126, 105, 98

b = b/255
g = g/255
r = r/255

v = max([b, g, r])

if v is 0:
    s = 0
else: 
    s = (v-min([b, g, r]))/v


if v is r:
    h = 60*(g-b)/(v-min([b,g,r]))
elif v is g:
    h = 120 + 60*(b-r)/(v-min([b,g,r]))
elif v is b:
    h = 240 + 60*(r-g)/(v-min([b,g,r]))

if h < 0:
    h = h + 360

v = np.round(255*v).astype(int)
s = np.round(255*s).astype(int)
h = np.round(h/2).astype(int)

print(h,s,v)

113 57 126

There are a few different methods you can employ to get good automatic values for color filtering. One method I like is to select an area that is the color you want, and finding the standard deviation and mean of those color values. That way you can easily set the lower and upper bounds of the inRange() function as mean-stddev and mean+stddev respectively. Or you can be less restrictive and multiply your standard deviation by some scalar, and you could choose which direction to be more or less choosy in. E.g., lowerb = mean - 3*stddev and upperb = mean + 1.5*stddev.

This can be super useful when say you have multiple ROIs with an object in the middle that you care about. You can filter out the colors that are in the border of each ROI separately using the mean and standard deviation of the border pixels!