RussTheAerialist
RussTheAerialist

Reputation: 31

Converting GDIPlus::Bitmap to cv::Mat (OpenCV C++ interface)

I'm trying to convert a GDIPlus::Bitmap into an openCV Mat object, but I'm running into problems with Access Violations, which means I'm not doing something right, but I've looked over the code over and over, and I think it should work.

Does anyone see an obvious problem?

cv::Mat ConvertToOpenCV(Gdiplus::Bitmap &image) {
    cv::Mat *retval = new cv::Mat(
        image.GetWidth(), image.GetHeight(), CV_8UC3
    );

    Gdiplus::BitmapData source;

    Gdiplus::Rect rect( 0, 0, image.GetWidth(), image.GetHeight() );
    Gdiplus::Status status =
        image.LockBits( &rect, Gdiplus::ImageLockModeRead, PixelFormat24bppRGB, &source );
    if ( status != Gdiplus::Ok ) {
        // Some error condition
        return retval; // No image copied
    }

    BYTE *destination = (BYTE *)retval->data;

    for ( int y = 0; y != source.Height; ++y ) {
        BYTE *src = (BYTE *) source.Scan0 + y * source.Stride;
        BYTE *dst = (BYTE *)(destination + y * retval->step);
        memcpy( dst, src, 3 * source.Width );  // Access Violation happens here
    }

    image.UnlockBits(&source);

    return retval;
}

Upvotes: 0

Views: 9485

Answers (3)

jwezorek
jwezorek

Reputation: 9525

As SSteve notes the Mat constructors go rows then columns, so use height then width. However there is no need to do the actual copy yourself. You can use one of the Mat constructors that will wrap existing data without copying and then force it to copy by calling the clone member function.

The only other trouble is that Gdiplus::Bitmap in theory supports loads of pixel layouts; however, most of these are pretty exotic. You can handle the simple case as follows:

cv::Mat GdiPlusBitmapToOpenCvMat(Gdiplus::Bitmap* bmp)
{
    auto format = bmp->GetPixelFormat();
    if (format != PixelFormat24bppRGB)
        return cv::Mat();

    int wd = bmp->GetWidth();
    int hgt = bmp->GetHeight();
    Gdiplus::Rect rcLock(0, 0, wd, hgt);
    Gdiplus::BitmapData bmpData;

    if (!bmp->LockBits(&rcLock, Gdiplus::ImageLockModeRead, format, &bmpData) == Gdiplus::Ok)
        return cv::Mat();

    cv::Mat mat = cv::Mat(hgt, wd, CV_8UC3, static_cast<unsigned char*>(bmpData.Scan0), bmpData.Stride).clone();

    bmp->UnlockBits(&bmpData);
    return mat;
}

Upvotes: 0

Elmue
Elmue

Reputation: 8138

You can use my ready to use class CGdiPlus that does automatic conversion from cv::Mat to Gdiplus::Bitmap and vice versa:

OpenCV / Tesseract: How to replace libpng, libtiff etc with GDI+ Bitmap (Load into cv::Mat via GDI+)

Upvotes: 0

SSteve
SSteve

Reputation: 10698

Here's one problem:

cv::Mat *retval = new cv::Mat(
    image.GetWidth(), image.GetHeight(), CV_8UC3
);

The Mat constructor's first argument is rows, second is columns. So you should be doing this:

cv::Mat *retval = new cv::Mat(
    image.GetHeight(), image.GetWidth(), CV_8UC3
);

That could cause an access violation.

Edit

Also, OpenCV images are by default BGR, not RGB. So if you get this working and then display the image with imshow, your blue and red values will be backward. You can fix this with the call cv::cvtColor(retval, retval, CV_RGB2BGR) before your return statement.

Upvotes: 2

Related Questions