从图像中对一系列颜色进行阈值处理

时间:2013-05-23 23:59:24

标签: c++ c opencv colors pixel

计划

我的项目能够捕获目标窗口的位图并将其转换为IplImage,然后在cvNamedWindow中显示该图像,以便进行进一步处理。
为了测试,我已将图像加载到MSPaint中,如下所示:

MSPaint Test Window

然后,用户可以单击并将鼠标拖动到图像中的任意数量的像素上,以创建包含这些RGB颜色值的vector<cv::Scalar_<BYTE>>。 然后,在ColorRGBToHLS()的帮助下,然后通过色调从左到右对此数组进行排序,如下所示:

// PixelColor is just a cv::Scalar_<BYTE>
bool comparePixelColors( PixelColor& pc1, PixelColor& pc2 ) {
    WORD h1 = 0, h2 = 0;
    WORD s1 = 0, s2 = 0;
    WORD l1 = 0, l2 = 0;
    ColorRGBToHLS(RGB(pc1.val[2], pc1.val[1], pc1.val[0]), &h1, &l1, &s1);
    ColorRGBToHLS(RGB(pc2.val[2], pc2.val[1], pc2.val[0]), &h2, &l2, &s2);
    return ( h1 < h2 );
}

//..(elsewhere in code)
std::sort(m_colorRange.begin(), m_colorRange.end(), comparePixelColors);


...然后以新的cvNamedWindow显示,类似于:

ColorRange Window

问题

现在,这里的想法是创建一个二进制阈值图像(或“掩码”),其中所选颜色范围变为白色,而源图像的其余部分变为黑色......类似“按颜色选择”工具在GIMP中运行的方式,或“魔术棒”工具在Photoshop中工作...除了不限于特定的轮廓选择,我们实际上是对整个图像进行操作。

我读过cvInRangeS,听起来这正是我所需要的 然而,无论出于何种原因, 阈值图像总是最终变成黑色 ......

VOID ShowThreshedImage(const IplImage* src, const PixelColor& min, const PixelColor& max)
{
    IplImage* imgHSV = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
    cvCvtColor(src, imgHSV, CV_RGB2HLS);
    cvNamedWindow("T1");
    cvShowImage("T1", imgHSV); // <-- Shows up like the image below

    IplImage* imgThreshed = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
    cvInRangeS(imgHSV, min, max, imgThreshed);
    cvNamedWindow("T2");
    cvShowImage("T2", imgThreshed); // <-- SHOWS UP PITCH BLACK!
}


这就是"T1"窗口最终看起来像(我认为是正确的?)

HSV Result Window

请记住,因为颜色范围向量存储为RGB (并且OpenCV内部将此顺序反转为BGR),我已经转换了最小值/最大值在将它们传递到ShowThreshedImage()之前进入HLS:

CvScalar rgbPixelToHSV(const PixelColor& pixelColor)
{
    WORD h = 0, s = 0, l = 0;
    ColorRGBToHLS(RGB(pixelColor.val[2], pixelColor.val[1], pixelColor.val[0]), &h, &l, &s);
    return PixelColor(h, s, l);
}

//...(elsewhere in code)
if(m_colorRange.size() > 0)
    m_minHSV = rgbPixelToHSV(m_colorRange[0]);
if(m_colorRange.size() > 1)
    m_maxHSV = rgbPixelToHSV(m_colorRange[m_colorRange.size() - 1]);

ShowThreshedImage(m_imgSrc,  m_minHSV, m_maxHSV);


...但即使没有这种转换,只是简单地传递RGB值,结果仍然是一个完全黑色的图像。我甚至尝试手动插入某些最小/最大值,我得到的最好结果是几个像素(尽管不正确的)。

问题:

我在这里做错了什么?
是否有一些我不了解的cvInRangeS方法? 我是否需要逐步浏览每种颜色,以便正确地将所选范围限制在源图像之外? 有没有其他方法可以实现这个目标?

感谢您的时间。

更新

我发现cvInRangeS期望min所有值低于max的值。但是,当选择一系列颜色时,似乎不能保证会出现这种情况,通常会导致黑色阈值图像。 并且交换值以强制执行此规则可能会在新范围内导致不需要的颜色(在某些情况下,这可能包括 所有 颜色而不仅仅是所需的颜色)。

所以我想这里真正的问题是:
“如何分割RGB颜色数组,并使用它们来阈值图像?”

2 个答案:

答案 0 :(得分:1)

您的问题可能是由OpenCV维持的值不同于instanc MSpaint的简单事实引起的。例如,油漆中的HSV颜色空间为360,100,100,而在OpenCV中为180,255,255。在openCV bu中检查输入值,在单击某个像素时输出像素值。 inRangeS应该是这项工作的正确工具。也就是说,在RGB中,它应该也能正常工作,因为范围与油漆相同。

cvSetMouseCallback("MyWindow", mouseEvent, (void*) &myImage);

void mouseEvent(int evt, int x, int y, int flags, void *param) {
    if (evt == CV_EVENT_LBUTTONDOWN) {
        printf("%d %d\n", x, y);
        IplImage* imageSource = (IplImage*) param;
        Mat image(imageSource);
        cout << "Image cols " << image.cols << " rows " << image.rows << endl;
        Mat imageHSV;
        cvtColor(image, imageHSV, CV_BGR2HSV);
        Vec3b p = imageHSV.at<Vec3b > (y, x);
        char text[20];
        sprintf(text, "H=%d, S=%d, V=%d", p[0], p[1], p[2]);
        cout << text << endl;
    }
}

当您使用此值了解HSV值时,请在使用cvtColor(image, imageHSV, CV_BGR2HSV)将图像转换为HSV后,将这些值用作范围内方法的下限和上限。这应该可以让你获得理想的结果。

答案 1 :(得分:0)

迭代每个像素并不会太低效。这正是cvInRangeS会做的事情 - 请参阅:http://docs.opencv.org/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#the-efficient-way(我一直这样做,对于合理大小的图像,它是即时的)。

我会将数组中的颜色视为3D RGB空间中的点。找到两个颜色点,指定包含所有其他颜色点的棱镜。这只是找到所有r,g和b值的最小值和最大值。如果这个想法不合适那么你可能必须检查每个图像像素与矢量中的每个像素。

然后对于图像中的每个像素:如果(pixel.r max.g)|| (pixel.b&lt; min.b)|| (pixel.b&gt; max.b),否则结果是像素值。

这一切都应该非常简单,只要它实际上是你想要的。