23

我正在尝试使用 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);

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

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

我想到的更好的选择是“旋转”图像的颜色,使目标色调为 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);

这使我可以类似地对待所有颜色。

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


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

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

4

5 回答 5

8

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

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

Note, RGB vs Bgr are purposefully being crossed.

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.

于 2015-06-27T03:36:09.603 回答
2

You can calculate Hue channel in range 0..255 with CV_BGR2HSV_FULL. Your original hue difference of 10 will become 14 (10/180*256), i.e. the hue must be in range 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);
}
于 2013-04-24T07:00:32.093 回答
1

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

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

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

于 2012-08-30T20:55:54.670 回答
1

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

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 post I found

于 2017-01-12T01:00:19.390 回答
0

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

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

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

一种解决方案是将数据从 (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

于 2012-08-31T09:13:45.420 回答