4

I am trying to display live images coming from a monochrome camera (Adimec N5A/CXP, with GenIcam standard).

From an example coming from the supplier (but in RGB 24), I am more or less able to display the image but the color rendering is very strange (colors and shadows instead of grayscale). I guess I did something wrong in the bitmap header declaration:

    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;
    }
    */

I found in bmiColors field of BITMAPINFO structure that the 'biClrUsed' should be set to 256. Then I do not know if I need to write a block to describe 'bmiColors'. I would like to use only one byte per pixel instead of the r,g and b components.

Then further in the program (in the function "OnPaint"), it uses the function "SetDIBitsToDevice" to display in a window previously created. The image pointer is first retrieved:

unsigned char *imagePtr = liveState.currentBuffer->getInfo<unsigned char *>(liveState.grabber, gc::BUFFER_INFO_BASE);

Then the image is displayed:

::SetDIBitsToDevice(dc, 0, 0, (DWORD)liveState.width, (DWORD)liveState.height, 0, 0, 0, (UINT)liveState.height, imagePtr, liveState.bitmapInfo, DIB_RGB_COLORS);

I don't know what to put instead of DIB_RGB_COLORS as the last parameter. I only found another value for this parameter that is DIB_PAL_COLORS. I guess there should be an option for grayscale?

This is the first step of my program... if you have any suggestion on how to push the image pointer into an opencv container I would also be very happy :-).

Many thanks in advance !

StephiCpp
  • 43
  • 3
  • 2
    I display grayscale images using a palette (very simple to generate). A quick paste of my code, as I'm about to head out: https://pastebin.com/Yz0cQYT8 -- the data pointer is the raw `cv::Mat` payload. I'll write up an answer when I get back. (There might be some WTL bits there, but that's very close to the WinAPI anyway) – Dan Mašek Apr 12 '18 at 14:14
  • Hi Dan, can I ask you some more help please? How to use cv::Mat to load image from memory buffer? I guess I should use cv::imdecode, but I am not able. There is a problem in the buffer type I guess. 'Mat srcImg = imdecode(imagePointer, 0);' – StephiCpp Apr 17 '18 at 15:14
  • What's in the memory buffer? Raw image data (as above)? Or is it encoded image data in say PNG, JPEG, or something like that? – Dan Mašek Apr 17 '18 at 16:40
  • If it's already raw image data, then you use [this `cv::Mat` constructor](https://docs.opencv.org/3.4.1/d3/d63/classcv_1_1Mat.html#a51615ebf17a64c968df0bf49b4de6a3a) (Note that it doesn't copy the data, you're responsible for making sure that the pointer stays valid as long as the `Mat` wrapping it lives) – Dan Mašek Apr 17 '18 at 16:46

1 Answers1

1

It seems you were quite close. The way to display grayscale images is to use a palette. This is simply 256 RGB entries representing all the shades between black and white:

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;
}

First of all, you need to allocate enough memory to hold BITMAPINFOHEADER as well as 256 RGBQUAD entries defining the palette to use.

int32_t const bmi_size(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);

Allocate the structure. I put it on stack using _alloca, so I don't need to worry about cleanup.

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

You need to set the following members of BITMAPINFOHEADER, the rest can be left as zeros.

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;

Note: Since we have a complete 256 entry palette, biClrUsed can be left set to 0. From docs:

If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member...

Next, set up the palette (that's basically the bit of your code that's commented out).

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;
    }
}

Note: The above code is from a generic paletted image rendering function. For smaller palettes it fills the unused colours with black. I suppose this could be refactored to use fewer entries along with biClrUsed set to appropriate value.

Now the bitmap header is ready. In your case, the call to SetDIBitsToDevice would still use DIB_RGB_COLORS since "The color table contains literal RGB values."

I use CreateDIBitmap to create a DDB, which I can later render using BitBlt.

HBITMAP bitmap = ::CreateDIBitmap(dc
    , &bmi->bmiHeader
    , CBM_INIT
    , data // Pointer to raw pixel data
    , bmi
    , DIB_RGB_COLORS);
Dan Mašek
  • 17,852
  • 6
  • 57
  • 85