5

计划

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

MSPaint 测试窗口

然后,允许用户在图像内任意数量的像素上单击并拖动鼠标,以创建vector<cv::Scalar_<BYTE>>包含这些 RGB 颜色值的图像。
然后,在 的帮助下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);


...然后显示在一个 newcvNamedWindow中,看起来像:

颜色范围窗口

问题

现在,这里的想法是创建一个二进制阈值图像(或“蒙版”),其中这个选定的颜色范围变为白色,而源图像的其余部分变为黑色......类似于“按颜色选择”工具的方式在 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 结果窗口

请记住,由于颜色范围向量存储为 RGB (并且 OpenCV 在内部将此顺序反转为 BGR),因此我已将最小/最大值转换为 HLS,然后再将它们传递给ShowThreshedImage()如下所示:

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。But when a range of colors are selected, there doesn't appear to be any guarantee that this will be the case, often resulting in a black thresholded image. 并且交换值以强制执行此规则可能会导致新范围内出现不需要的颜色(在某些情况下,这可能包括所有颜色而不仅仅是所需的颜色)。

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

4

2 回答 2

1

您的问题可能是由 OpenCV 维护的值范围与实例 MSpaint 不同的简单事实引起的。例如,绘画中的 HSV 颜色空间为 360,100,100,而在 OpenCV 中为 180,255,255。检查您在 openCV 中的输入值,但在单击某个像素时输出像素值。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). 那应该使您能够获得所需的结果。

于 2013-05-24T08:04:54.260 回答
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 < min.r) || 结果为黑色 (pixel.r > max.r) || (pixel.g < min.g) || (pixel.g > max.g) || (pixel.b < min.b) || (pixel.b > max.b),否则结果是像素值。

这一切都应该很容易,只要它实际上是你想要的。

于 2013-05-24T03:55:35.280 回答