5

I want to convert from ATL::CImage to cv::Mat for image handling in opencv(C++). Could you please help to convert this object?

I got CImage from windows screen shot(Using MFC). Then, I want to handle image in OpenCV Mat object.

I did not know how to convert.

  • C++ Project(VC 2017)
  • MFC
  • OpenCV 3.4.6

CImage image;
int cx;
int cy;
CWnd* pWndDesktop = CWnd::GetDesktopWindow();
CWindowDC srcDC(pWndDesktop);

Rect rcDesktopWindow;
::GetWindowRect(pWndDesktop->m_hWnd, %rcDesktopWindow);

cx = (rcDesktopWindow.right - rcDesktopWindow.left);
cy = (rcDesktopWindow.bottom - rcDesktopWindow.top);

image.create(cx, cy, srcDC.GetDeviceCaps(BITPIXEL));

CDC* pDC = CDC::FromHandle(image.GetDC());
pDC->BitBlt(0, 0, cx, cy, &srcDC, 0, 0, SRCCOPY);

image.ReleaseDC();

cv::Mat mat;

// I want set image to mat!
mat = image???

Can not convert ATL::Image to cv::Mat.

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
Kevin Kim
  • 61
  • 4
  • 2
    You could just eliminate the middleman and have one function to get the screenshot as a Mat. This may help: https://stackoverflow.com/questions/34466993/opencv-desktop-capture or https://stackoverflow.com/questions/14148758/how-to-capture-the-desktop-in-opencv-ie-turn-a-bitmap-into-a-mat – Retired Ninja May 15 '19 at 00:43
  • The simplest (but rather daft) approach would be to save it to file (there seems to be a member function to do that) and then load it back with `imread`. It also seems that you can save into an in-memory stream, so you could then feed the resulting buffer to `imdecode` and avoid using the filesystem. The best approach would be to get a pixel buffer with known layout that's understood by `Mat` (row-major, top to bottom ideally so you don't need to flip it).. then you just need to create a `Mat` header to match the data. Only ever had to go from `Mat` to GDI object, so will need to research. – Dan Mašek May 15 '19 at 02:11

3 Answers3

3

CImage creates a bottom-top bitmap if height is positive. You have to pass a negative height to create top-bottom bitmap for mat

Use CImage::GetBits to retrieve the bits as follows:

HDC hdc = GetDC(0);
RECT rc;
GetClientRect(GetDesktopWindow(), &rc);
int cx = rc.right;
int cy = rc.bottom;

CImage image;
image.Create(cx, -cy, 32);

BitBlt(image.GetDC(), 0, 0, cx, cy, hdc, 0, 0, SRCCOPY);
image.ReleaseDC();
ReleaseDC(0, hdc);

cv::Mat mat;
mat.create(cy, cx, CV_8UC4);
memcpy(mat.data, image.GetBits(), cy * cx * 4);

//or borrow pixel data from CImage 
cv::Mat mat(cy, cx, CV_8UC4, image.GetBits()); 

Or force a deep copy as follows:

cv::Mat mat;
mat = cv::Mat(cy, cx, CV_8UC4, image.GetBits()).clone();

Note, CImage makes its own allocation for pixel data. And Mat needs to make its own allocation, or it has to borrow from CImage which can be tricky.

If you are just taking a screen shot, you can do that with plain Windows API, then write directly to cv::Mat. This way there is a single allocation (a bit faster) and mat does not rely on other objects. Example:

void foo()
{
    HDC hdc = ::GetDC(0);

    RECT rc;
    ::GetClientRect(::GetDesktopWindow(), &rc);
    int cx = rc.right;
    int cy = rc.bottom;
    cv::Mat mat;
    mat.create(cy, cx, CV_8UC4);

    HBITMAP hbitmap = CreateCompatibleBitmap(hdc, cx, cy);
    HDC memdc = CreateCompatibleDC(hdc);
    HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
    BitBlt(memdc, 0, 0, cx, cy, hdc, 0, 0, SRCCOPY);

    BITMAPINFOHEADER bi = { sizeof(bi), cx, -cy, 1, 32, BI_RGB };
    GetDIBits(hdc, hbitmap, 0, cy, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);

    //GDI cleanup:
    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);
    DeleteObject(hbitmap);
    ::ReleaseDC(0, hdc);
}


Edit:
Changed mat.data = (unsigned char*)image.GetBits(); to
memcpy(mat.data, image.GetBits(), cy * cx * 4);

Changed ReleaseDC(0, hdc) to ::ReleaseDC(0, hdc) to avoid conflict with CWnd::ReleaseDC(dc)

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • 1
    By resetting the `data` member, you're potentially leaking the buffer (or at the least asking for trouble -- don't really want to dig through the implementation to find any possible pitfalls due to messing with its exposed internal state). Just use the [appropriate constructor](https://docs.opencv.org/3.4.3/d3/d63/classcv_1_1Mat.html#a51615ebf17a64c968df0bf49b4de6a3a) to create a header wrapping that buffer correctly. Fix that, and i'll revise my vote. – Dan Mašek May 15 '19 at 02:58
  • 3
    Might be also useful to mention that the `Mat` is only good as long as `image` is in scope. – Dan Mašek May 15 '19 at 03:06
  • 1
    @DanMašek You don't have to dig through the source code. You can read the docs for `data` which is "exposed", and you can check for leaks. Leaks don't occur in this case because `Mat` keeps track of its own allocation. Your suggestion is certainly more efficient and safer, but the question is for `Mat mat;` that's up to the asker to rewrite it... – Barmak Shemirani May 15 '19 at 07:04
  • Yeah, I see that `UMatData` member object will handle the originally allocated memory. But you're definitely breaking the internal state right now, since `datastart`, `dataend` and `datalimit` still point to the internally allocated memory, while `data` points to completely different block. So things like `locateROI` and maybe `adjustROI` probably won't be very happy at best. Not to mention you're allocating a useless chunk of memory. I really don't see a reason to do this, especially having pointed out a cleaner approach. I'm sorry, but I'd never pass code like this through a review. – Dan Mašek May 16 '19 at 21:33
  • "up to the asker to rewrite it" -- based on my observations on SO, I expect a lot of beginners will take this code as the right thing to do, without giving it a second thought. Hence, to me, it seems a bit irresponsible to write code like this in an answer. – Dan Mašek May 16 '19 at 21:35
  • 1
    @DanMašek, Yes, the extra allocation is not efficient (in fairness it's better than saving the image to disk and reading it again) I updated the answer to avoid the extra allocation and fix possible problems with the pointer. – Barmak Shemirani May 17 '19 at 04:11
  • 1
    "better than saving" -- absolutely, that was just the minimum effort approach that came to mind with minimum research. Your approach (other than the bit I complained about) would be what I'd look for (and the pure WinAPI solution is even better). Thanks for the update. – Dan Mašek May 17 '19 at 05:05
0
#include <opencv2\opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <atlimage.h>

using namespace cv;

Mat CImage2Mat(CImage cimg)
{
    BITMAP bmp;
    ::GetObject(cimg.Detach(), sizeof(BITMAP), &bmp);

    int nChannels = bmp.bmBitsPixel == 1 ? 1 : bmp.bmBitsPixel / 8;
    int depth = bmp.bmBitsPixel == 1 ? IPL_DEPTH_1U : IPL_DEPTH_8U;

    IplImage* img = cvCreateImageHeader(cvSize(bmp.bmWidth, bmp.bmHeight), depth, nChannels);

    img->imageData = (char*)malloc(bmp.bmHeight * bmp.bmWidth * nChannels * sizeof(char));
    memcpy(img->imageData, (char*)(bmp.bmBits), bmp.bmHeight * bmp.bmWidth * nChannels);
    cvFlip(img, img, 0);
    return cvarrToMat(static_cast<IplImage*>(img));
}

Usage:

CImage cimg;
cimg.Load(L"resim.png");

Mat img = CImage2Mat(cimg);
Kitiara
  • 343
  • 6
  • 21
0

Usually, this scenario occurs when working with image processing in MFC windows application development. I was developing an MFC based app that captures the images from Canon DSLR using the EDSDK (canon SDK). I used this solution (time saving), the problem I faced was degradation of the image resolution!

Solution here! 100% working. https://github.com/wlrl/convertType