使用 cvtColor 转换单个 BGR 颜色并使用结果从图像中提取该颜色

Posted

技术标签:

【中文标题】使用 cvtColor 转换单个 BGR 颜色并使用结果从图像中提取该颜色【英文标题】:Converting a single BGR color with cvtColor and using the result to extract that color from image 【发布时间】:2017-07-04 02:38:57 【问题描述】:

我使用的是 c++,但 python 中的答案也很好。

我需要将已知的 BGR 颜色转换为 HSV 才能使用 cv::inRange 方法,该方法允许您提取图像的某个部分。我已经有代码可以很好地估计我需要的 BGR 颜色,所以我认为我应该能够猜测它的范围。

所以我参考了这个链接:Convert a single color with cvtColor,它似乎可以将 BGR 转换为 HSV,尽管我仍然得到一些奇怪的输出,稍后我将解释。

无论如何,这是我的代码:

//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);

这里是一个简单的例子:我的代码确定了我想要的近似 BGR 颜色值 [126, 105, 98]。转换为 HSV 最初会给出 [225, 0.22, 126] 这似乎与 opencv 存储 hsv 的方式不正确(我认为色调是 0-179)所以我进行 2 次转换以获得 [112, 56, 126] 我认为应该是对的吧?

无论如何,当我尝试修改 inRange 函数中的值时,我无法真正获得任何好的提取效果,我往往只会出现黑屏(我测试了我的掩码是否有效,因此问题应该出在提供的代码)。

有没有更好的方法来完成这项任务?

【问题讨论】:

【参考方案1】:

您的第二次转换是正确的(好吧,如果您截断为 int 而不是四舍五入)。

您始终可以对OpenCV docs for cvtColor 中给出的值进行实际计算。

这是用 Python 编写的链接的确切公式。抱歉,它不是 C++,但我尽可能将其写成基本的,因此任何其他语言的人都可以理解它:

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

您可以采用几种不同的方法来获得良好的自动滤色值。我喜欢的一种方法是选择一个您想要的颜色区域,然后找到这些颜色值的标准偏差和平均值。这样您就可以轻松地将inRange() 函数的下限和上限分别设置为mean-stddevmean+stddev。或者您可以减少限制,将标准差乘以某个标量,然后您可以选择或多或少选择哪个方向。例如,lowerb = mean - 3*stddevupperb = mean + 1.5*stddev

如果您有多个 ROI,中间有一个您关心的对象,这会非常有用。您可以使用边界像素的均值和标准差分别过滤掉每个 ROI 边界中的颜色!

【讨论】:

好的,我已将这部分转换并在 c++ 中工作,但似乎我没有得到我想要一致提取的确切颜色。使用 cvInRange 真的是提取特定颜色的唯一方法吗?如果是,那么给出良好范围的最佳方法是什么? 是的,提取颜色正是您想要使用的。如果您想要一致地提取颜色,这取决于您的应用程序是什么以及您所说的一致是什么意思。例如,对于许多应用程序来说,一种相当稳健的方法是在图像中找到具有该颜色的区域,找到该区域中颜色的均值和标准差,然后使用 inRange() 过滤,下限 mean - n*stddev 和上限mean + n*stddev 其中n 让您选择要偏离特定颜色的标准偏差数。 真棒只是用一个区域做了标准偏差提示,似乎在我的图像上改进了很多。感谢您的帮助:) 太棒了!这是一种直观而体面的方法。一些直觉伴随着更好地理解色彩空间。例如在 HSV 中,H 通道代表原色,而 S 是饱和度,V 是亮度,所以如果你想获得相同的颜色但在不同的阴影条件下,你可能对 S 或 V 更宽容,但是对 H 仍然有些严格。标准偏差技巧通过了解您应该在每个通道中预期多少偏差来为您解决这个问题。

以上是关于使用 cvtColor 转换单个 BGR 颜色并使用结果从图像中提取该颜色的主要内容,如果未能解决你的问题,请参考以下文章

颜色空间转换

Python-颜色空间转换

OpenCV常用基本处理函数

opencv 10 -- 图像 颜色空间转换

opencv 10 -- 图像 颜色空间转换

美图工作记录-2-opencv学习