首先,请注意我是Accessing certain pixel RGB value in openCV和Pixel 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
通道数量的笨拙乘法。
无论如何,我的问题是,这些都非常“不安全”,因为图片可能是RGB
或BGR
或ARGB
。此外,像素可能不是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 ++中有一种可靠的方法,我会接受它。
答案 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行)。可笑了。