C++ 在 OpenCV 中使用 HSV 有效地阈值红色

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12204522/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 16:01:24  来源:igfitidea点击:

efficiently threshold red using HSV in OpenCV

c++image-processingopencv

提问by Ian

I'm trying to threshold red pixels in a video stream using OpenCV. I have other colors working quite nicely, but red poses a problem because it wraps around the hue axis (ie. HSV(0, 255, 255) and HSV(179, 255, 255) are both red). The technique I'm using now is less than ideal. Basically:

我正在尝试使用 OpenCV 对视频流中的红色像素进行阈值处理。我还有其他颜色可以很好地工作,但红色会带来问题,因为它环绕着色调轴(即 HSV(0, 255, 255) 和 HSV(179, 255, 255) 都是红色)。我现在使用的技术不太理想。基本上:

cvInRangeS(src, cvScalar(0, 135, 135), cvScalar(20, 255, 255), dstA);
cvInRangeS(src, cvScalar(159, 135, 135), cvScalar(179, 255, 255), dstB);
cvOr(dstA, dstB, dst);

This is suboptimal because it requires a branch in the code for red (potential bugs), the allocation of two extra images, and two extra operations when compared to the easy case of blue:

这是次优的,因为与简单的蓝况相比,它需要代码中的红色分支(潜在错误)、两个额外图像的分配和两个额外操作:

cvInRangeS(src, cvScalar(100, 135, 135), cvScalar(140, 255, 255), dst);


The nicer alternative that occurred to me was to "rotate" the image's colors, so that the target hue is at 90 degrees. Eg.

我想到的更好的选择是“旋转”图像的颜色,使目标色调为 90 度。例如。

int rotation = 90 - 179; // 179 = red
cvAddS(src, cvScalar(rotation, 0, 0), dst1);
cvInRangeS(dst1, cvScalar(70, 135, 135), cvScalar(110, 255, 255), dst);

This allows me to treat all colors similarly.

这使我可以类似地处理所有颜色。

However, the cvAddSoperation doesn't wrap the hue values back to 180 when they go below 0, so you lose data. I looked at converting the image to CvMatso that I could subtract from it and then use modulus to wrap the negative values back to the top of the range, but CvMatdoesn't seem to support modulus. Of course, I could iterate over every pixel, but I'm concerned that that's going to be very slow.

但是,当cvAddS色调值低于 0 时,该操作不会将色调值包装回 180,因此您会丢失数据。我看着将图像转换为,CvMat以便我可以从中减去,然后使用模数将负值包装回范围的顶部,但CvMat似乎不支持模数。当然,我可以迭代每个像素,但我担心这会很慢。



I've read many tutorials and code samples, but they all seem to conveniently only look at ranges that don't wrap around the hue spectrum, or use solutions that are even uglier (eg. re-implementing cvInRangeSby iterating over every pixel and doing manual comparisons against a color table).

我已经阅读了许多教程和代码示例,但它们似乎都只方便地查看不包含色调频谱的范围,或者使用更难看的解决方案(例如,cvInRangeS通过迭代每个像素并执行重新实现)与颜色表进行手动比较)。

So, what's the usual way to solve this? What's the best way? What are the tradeoffs of each? Is iterating over pixels much slower than using built-in CV functions?

那么,解决这个问题的常用方法是什么?最好的方法是什么?每个人的权衡是什么?迭代像素是否比使用内置 CV 函数慢得多?

采纳答案by ArtemStorozhuk

You won't believe but I had exactly the same issue and I solved it using simple iteration through Hue (not whole HSV) image.

你不会相信,但我遇到了完全相同的问题,我通过 Hue(不是整个 HSV)图像使用简单的迭代解决了它。

Is iterating over pixels much slower than using built-in CV functions?

迭代像素是否比使用内置 CV 函数慢得多?

I've just tried to understood cv::inRangefunction but didn't get it at all (it seems that author used some specific iteration).

我只是试图理解cv::inRange函数,但根本没有理解(似乎作者使用了一些特定的迭代)。

回答by GaryK

This is kind of late, but this is what I'd try.

这有点晚了,但这就是我要尝试的。

Make the conversion: cvCvtColor(imageBgr, imageHsv, CV_RGB2HSV);

进行转换:cvCvtColor(imageBgr, imageHsv, CV_RGB2HSV);

Note, RGB vs Bgr are purposefully being crossed.

请注意,RGB 与 Bgr 是有意交叉的。

This way, red color will be treated in a blue channel and will be centered around 170. There would also be a flip in direction, but that is OK as long as you know to expect it.

这样,红色将在蓝色通道中处理,并以 170 为中心。也会有一个方向翻转,但只要你知道它是可以的。

回答by Oliv

You can calculate Hue channel in range 0..255 with CV_BGR2HSV_FULL. Your original hue difference of 10will become 14(10/180*256), i.e. the hue must be in range 128-14..128+14:

您可以使用 计算 0..255 范围内的色调通道CV_BGR2HSV_FULL。您的原始色调差异10将变为14( 10/180*256),即色调必须在范围内128-14..128+14

public void inColorRange(CvMat imageBgr, CvMat dst, int color, int threshold) {
    cvCvtColor(imageBgr, imageHsv, CV_BGR2HSV_FULL);
    int rotation = 128 - color;
    cvAddS(imageHsv, cvScalar(rotation, 0, 0), imageHsv);
    cvInRangeS(imageHsv, cvScalar(128-threshold, 135, 135), 
         cvScalar(128+threshold, 255, 255), dst);
}

回答by Sam

cvAddS(...)is equivalent, at element level, to:

cvAddS(...)在元素级别等效于:

 out = static_cast<dest> ( in + shift );

This static_cast is the problem, because is clips/truncates the values.

这个 static_cast 是问题所在,因为是剪辑/截断值。

A solution would be to shift the data from (0-180) to (x, 255), then apply a non-clipping add with overflow:

一种解决方案是将数据从 (0-180) 移至 (x, 255),然后应用带有溢出的非剪裁添加:

 out = uchar(in + (255-180) + rotation );

Now you should be able to use a single InRange call, just shift your red interval according to the above formula

现在您应该可以使用单个 InRange 调用,只需根据上述公式移动您的红色间隔

回答by Ethan

There is a really simple way of doing this.

有一种非常简单的方法可以做到这一点。

First make two different color ranges

首先制作两个不同的颜色范围

cv::Mat lower_red_hue_range;
cv::Mat upper_red_hue_range;
cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);

Then combine the two masks using addWeighted

然后使用 addWeighted 组合两个掩码

cv::Mat red_hue_mask;
cv::addWeighted(lower_red_hue_range, 1.0, upper_red_hue_range, 1.0, 0.0, red_hue_mask);

Now you can just apply the mask to the image

现在您可以将蒙版应用于图像

cv::Mat result;
inputImageMat.copyTo(result, red_hue_mask);

I got the idea from a blog postI found

我从我发现的一篇博客文章中得到了这个想法