C++:灰度位图头和实时绘制+OpenCV图像处理

4
我正在尝试显示来自单色相机(Adimec N5A/CXP,符合GenIcam标准)的实时图像。
从供应商提供的示例中(但是是RGB 24),我大致能够显示图像,但颜色呈现非常奇怪(彩色和阴影代替灰度)。我猜测在位图头声明方面做错了什么:
    bitmapInfo = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD));
    bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bitmapInfo->bmiHeader.biPlanes = 1;
    bitmapInfo->bmiHeader.biBitCount = 8; // 24
    bitmapInfo->bmiHeader.biCompression = BI_RGB;
    bitmapInfo->bmiHeader.biSizeImage = 0;
    bitmapInfo->bmiHeader.biXPelsPerMeter = 0;
    bitmapInfo->bmiHeader.biYPelsPerMeter = 0;
    bitmapInfo->bmiHeader.biClrUsed = 256;
    bitmapInfo->bmiHeader.biClrImportant = 0;
    bitmapInfo->bmiHeader.biWidth = (LONG)width;
    bitmapInfo->bmiHeader.biHeight = -(LONG)height;
    /*
    RGBQUAD* bmiColors = (RGBQUAD*)(bitmapInfo->bmiColors);
    for (size_t index = 0; index < 256; ++index)
    {
        bmiColors[index].rgbBlue = (BYTE)index;
        bmiColors[index].rgbGreen = (BYTE)index;
        bmiColors[index].rgbRed = (BYTE)index;
        bmiColors[index].rgbReserved = 0;
    }
    */

我在BITMAPINFO结构体中的bmiColors字段中发现,'biClrUsed'应该设置为256。然后我不知道是否需要写一个块来描述'bmiColors'。我想每个像素只使用一个字节而不是r、g和b组件。
然后,在程序的更进一步部分(在函数"OnPaint"中),它使用函数"SetDIBitsToDevice"来显示之前创建的窗口中的图像。首先检索图像指针:
unsigned char *imagePtr = liveState.currentBuffer->getInfo<unsigned char *>(liveState.grabber, gc::BUFFER_INFO_BASE);

然后显示图片:
::SetDIBitsToDevice(dc, 0, 0, (DWORD)liveState.width, (DWORD)liveState.height, 0, 0, 0, (UINT)liveState.height, imagePtr, liveState.bitmapInfo, DIB_RGB_COLORS);

我不知道在最后一个参数中应该放什么,代替DIB_RGB_COLORS。我只找到了另一个值作为这个参数,即DIB_PAL_COLORS。我猜应该有一个灰度选项?这是我的程序的第一步......如果您有任何关于如何将图像指针推入opencv容器的建议,我也会非常高兴:-)。提前致谢!

2
我使用调色板(非常容易生成)来显示灰度图像。以下是我的代码快速粘贴,因为我即将离开:https://pastebin.com/Yz0cQYT8 -- 数据指针是原始的 cv::Mat 负载。我回来后会写一个答案。(可能有一些 WTL 位,但那非常接近 WinAPI) - Dan Mašek
嗨,丹,我能请你再帮我一下吗?如何使用cv :: Mat从内存缓冲区加载图像?我猜我应该使用cv :: imdecode,但我做不到。我想缓冲区类型有问题。'Mat srcImg = imdecode(imagePointer,0);' - StephiCpp
内存缓冲区中是什么?原始图像数据(如上所述)?还是编码的图像数据,比如PNG、JPEG或类似的格式? - Dan Mašek
如果它已经是原始图像数据,那么您可以使用这个cv::Mat构造函数(请注意,它不会复制数据,您需要确保指针在Mat包装它的生命周期内保持有效)。 - Dan Mašek
1个回答

1
似乎你已经接近正确了。显示灰度图像的方法是使用调色板。这只是256个RGB条目,代表黑色和白色之间所有阴影的简单表示方式:
std::vector<RGBQUAD> pal(256);
for (int32_t i(0); i < 256; ++i) {
    pal[i].rgbRed = pal[i].rgbGreen = pal[i].rgbBlue = i;
    pal[i].rgbReserved = 0;
}

首先,您需要分配足够的内存来容纳BITMAPINFOHEADER以及256个RGBQUAD条目,以定义要使用的调色板。
int32_t const bmi_size(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);

分配结构体。我使用 _alloca 将其放在堆栈上,因此无需担心清理。

BITMAPINFO* bmi(static_cast<BITMAPINFO*>(alloca(bmi_size)));

您需要设置以下BITMAPINFOHEADER的成员,其余可以留为零。

bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi->bmiHeader.biWidth = static_cast<LONG>(width);
bmi->bmiHeader.biHeight = static_cast<LONG>(-height);
bmi->bmiHeader.biPlanes = 1;
bmi->bmiHeader.biBitCount = 8;
bmi->bmiHeader.biCompression = BI_RGB;

注意:由于我们有一个完整的256个条目的调色板,可以将biClrUsed设置为0。来自docs

如果此值为零,则位图使用与biBitCount成员的值相对应的最大颜色数...

接下来,设置调色板(这基本上是您的代码中被注释掉的部分)。
for (uint32_t i(0); i < 256; ++i) {
    if (pal.size() > i) {
        bmi->bmiColors[i] = pal[i];
    } else {
        bmi->bmiColors[i].rgbRed
            = bmi->bmiColors[i].rgbGreen
            = bmi->bmiColors[i].rgbBlue
            = bmi->bmiColors[i].rgbReserved = 0;
    }
}

注意:上面的代码来自一个通用的调色板图像渲染函数。对于较小的调色板,它会用黑色填充未使用的颜色。我认为这可以重构为使用较少的条目以及设置适当值的 biClrUsed
现在位图头已经准备好了。在您的情况下,对 SetDIBitsToDevice 的调用仍将使用 DIB_RGB_COLORS,因为“颜色表包含文字 RGB 值”。
我使用 CreateDIBitmap 创建 DDB,稍后可以使用 BitBlt 渲染。
HBITMAP bitmap = ::CreateDIBitmap(dc
    , &bmi->bmiHeader
    , CBM_INIT
    , data // Pointer to raw pixel data
    , bmi
    , DIB_RGB_COLORS);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接