OpenCV获取/设置IplImage RGB值

时间:2014-10-14 16:02:39

标签: c image opencv colors

首先,请注意我是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 ++中有一种可靠的方法,我会接受它。

1 个答案:

答案 0 :(得分:0)

无论如何,这就是我们所知道的。从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。进一步调试还显示channelSeq IplImage实际上是一个字符串(该死的,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行)。可笑了。