Search code examples
c++opencvmfcatl

How to convert ATL::CImage to cv::Mat?


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.


Solution

  • 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)