【问题标题】:OpenCV get / set IplImage RGB valuesOpenCV 获取/设置 IplImage RGB 值
【发布时间】:2014-12-09 12:09:59
【问题描述】:

首先,请注意我很喜欢Accessing certain pixel RGB value in openCVPixel access in OpenCV 2.2。这些都不能解决我的问题。

我想做的是在 OpenCV 的IplImage 中可靠地读取/写入像素 RGB 值。到目前为止,我已经看到了这个:

uint8_t cr = CV_IMAGE_ELEM(color_img, uint8_t, y, x * color_img->nChannels + 2);
uint8_t cg = CV_IMAGE_ELEM(color_img, uint8_t, y, x * color_img->nChannels + 1);
uint8_t cb = CV_IMAGE_ELEM(color_img, uint8_t, y, x * color_img->nChannels + 0);

现在这对我来说显然是错误的。

const uint8_t *p_color_pixel = cvPtr2D(color_img, y, x, NULL);
uint8_t cr = p_color_pixel[2], cg = p_color_pixel[1], cb = p_color_pixel[0];

这仍然有点痛苦,但可能是最快的方法。也可以使用cvGet2D(),尽管这似乎还涉及将x 与频道数相乘的尴尬。

无论如何,我的问题是这些都非常“不安全”,因为图像可能是RGBBGRARGB。此外,像素可能不是uint8_t。所以结果感觉仍然很随机,具体取决于我正在阅读的图像和格式。我实际上正在处理存储一些 24 位整数值的图像,打包到 RGB,因此正确的顺序至关重要(错误的顺序基本上会产生噪音)。到目前为止,我见过的唯一一种看起来“半安全”的方法是相当病态的,无论是代码还是性能方面:

CvSize t_image_size = cvGetSize(image);
IplImage *ir = cvCreateImage(t_image_size, 8, 1);
IplImage *ig = cvCreateImage(t_image_size, 8, 1);
IplImage *ib = cvCreateImage(t_image_size, 8, 1);
cvSplit(image, left_b, left_g, left_r, 0);

double cr = cvGetReal2D(ir, x, y),
    cg = cvGetReal2D(ig, x, y), cvGetReal2D(ib, x, y);

cvReleaseImage(&ir);
cvReleaseImage(&ig);
cvReleaseImage(&ib);

我的意思是...拜托,我们是在 21 世纪,为什么要正确RGB 这么难?!

请注意,我更喜欢使用“C”接口,因为 C++ 接口目前非常不稳定。但如果 C++ 中有可靠的方法,我会接受。

【问题讨论】:

  • 在c和c++中你不知道BGR/RGB是什么样的数据布局。你应该知道,从你得到图像的地方;)在 IplImages 我通常处理如下:for(rows)for(cols)for(channels){compute array position and read pixel/channel value}(你可以在每个循环级别预先计算一个坐标的一部分)
  • 如果它是 RGB 而不是 BGR 和/或不是 24 位,您的“安全”代码示例也会失败?!?
  • @Micka 我认为应该在“安全”模式下正确处理颜色深度,因为使用需要执行转换的cvGetReal2D 访问像素(如果不是,那是 OpenCV 中的一个错误在那里)。确实cvSplit也需要我知道RGB的顺序,你说的没错。
  • “请注意,我更喜欢使用“C”接口,因为 C++ 接口目前非常不稳定” - 请不要。无论如何,您想要使用的过时的 c 包装器大多是围绕 c++ 功能的昂贵包装器。
  • @berak 嗯,有一个很大的代码库,使用“C”接口。我认为它不会很快消失。相反,在过去的几个2.x 版本中,C++ 接口发生了很大变化,我们不得不重写代码并将#ifdefs 到处放置。考虑到维护和兼容性,我看不出这是多么值得(如果有人试图用我们没有测试的 OpenCV 版本构建我们的代码会发生什么?)。这对我来说是不行的。也许几年后。

标签: c image opencv colors


【解决方案1】:

无论如何,这就是我们所知道的。从IplImage documentation,我们可以看到几个相关的字段:

nChannels // Number of channels. Most OpenCV functions support 1-4 channels.
depth // The supported depths are: IPL_DEPTH_8U, IPL_DEPTH_16U, ...
channelSeq // Ignored by OpenCV
colorModel // Ignored by OpenCV
dataOrder // 0 - interleaved color channels, 1 - separate color channels.
          // CreateImage() only creates images with interleaved channels.
          // For example, the usual layout of a color image is: b00r00g00b10r10g10

所以至少深度和通常的布局是可知的。但是,不寻常的布局呢?我们也可以查看imread documentation。最后,它说:

注意:在彩色图像的情况下,解码图像的通道将按 B G R 顺序存储。

看来是这样。在读取图像时似乎没有实现“不寻常”的布局。

让我们看看cvGet2D()是如何实现的(OpenCV 2.4.7):

CV_IMPL CvScalar cvGet2D(const CvArr* arr, int y, int x)
{
    CvScalar scalar = {{0,0,0,0}};
    int type = 0;
    uchar* ptr;

    if(CV_IS_MAT(arr)) {
        CvMat* mat = (CvMat*)arr;
        if((unsigned)y >= (unsigned)(mat->rows) ||
           (unsigned)x >= (unsigned)(mat->cols))
            CV_Error(CV_StsOutOfRange, "index is out of range");
        type = CV_MAT_TYPE(mat->type);
        ptr = mat->data.ptr + (size_t)y * mat->step + x * CV_ELEM_SIZE(type);
    } else if(!CV_IS_SPARSE_MAT(arr))
        ptr = cvPtr2D(arr, y, x, &type);
    else {
        int idx[] = {y, x};
        ptr = icvGetNodePtr((CvSparseMat*)arr, idx, &type, 0, 0);
    }
    if(ptr)
        cvRawDataToScalar(ptr, type, &scalar);
    return scalar;
}

这其实很有趣。调试显示在IplImage 上,它调用cvPtr2D,然后调用cvRawDataToScalar。进一步调试还显示IplImagechannelSeq 实际上是一个字符串(该死的,OpenCV 实现,你为什么要保密?!)。因此,用于读取 OpenCV 图像 RGB 值的健壮代码是:

const char *rpos = strchr(color_img->channelSeq, "R"),
           *gpos = strchr(color_img->channelSeq, "G"),
           *bpos = strchr(color_img->channelSeq, "B");
if(!rpos || !gpos || !bpos)
    throw std::runtime_error("unknown image channel sequence");
const int rIdx = rpos - color_img->channelSeq,
          gIdx = gpos - color_img->channelSeq,
          bIdx = bpos - color_img->channelSeq;
// determine channel mapping

CvScalar color = cvGet2D(color_img, y, x); // note y, x
uint8_t cr = uint8_t(color.val[rIdx]),
        cg = uint8_t(color.val[gIdx]),
        cb = uint8_t(color.val[bIdx]);
// do not multiply by 255, it is already in that range

这将正确处理任何通道顺序和任何深度。或者,如果您想更快,请使用imread(filename, CV_LOAD_IMAGE_COLOR) 将值转换为每像素 8 位。那么这个就可以用了:

assert(color_img->depth == IPL_DEPTH_8U); // note that imread does not really say whether it converts always to IPL_DEPTH_8U or sometimes to IPL_DEPTH_8S!
const uint8_t *p_color_pixel = cvPtr2D(color_img, y, x, NULL);
uint8_t cr = p_color_pixel[rIdx], cg = p_color_pixel[gIdx],
    cb = p_color_pixel[bIdx];

就是这样。它不漂亮,但我想这是使用 OpenCV 带来的。请注意,它实际上可能不可靠,因为文档说 OpenCV 没有使用 IplImage::channelSeq 字段,但这是现在可以做的最好的。在这一点上,我不相信 OpenCV 文档中写的任何东西,这是最糟糕的。

我有点希望有一种方法可以做到这一点,比如cvGetRGBA2D(),它会返回CvScalar,并且总是以RGBA 的顺序(一行而不是我的12 行)。可笑。

【讨论】:

    猜你喜欢
    • 2012-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 2013-09-12
    • 1970-01-01
    相关资源
    最近更新 更多