首页 > 解决方案 > 如何将屏幕截图位图转换为 cv::Mat

问题描述

我目前正在尝试截取屏幕截图,然后将其转换为 OpenCV 可编辑的格式。我使用的代码来自微软网站,https://docs.microsoft.com/en-gb/windows/win32/gdi/capturing-an-image。该代码使用“Windows.h”库。最简单的方法显然是将位图保存为 .bmp,然后使用 opencv 打开它。但是,我希望它比这更有效,但我不知道该怎么做。当我使用代码时,它输出了一个 char 指针,我不知道如何将其转换为 cv::Mat。代码如下:

cv::Mat * Capture::GetMat()
{
    cv::Mat * mat1;

    MemoryHandle = NULL;
    BitmapHandle = NULL;

    // Find the handle for the device context of the entire screen, and the specific window specified.
    ScreenHandle = GetDC(NULL);
    WindowHandle = GetDC(hwnd);

    //Make the compatible DC (Device Context) for storing the data in memory.
    MemoryHandle = CreateCompatibleDC(WindowHandle);

    //Make a compatible DC for the bitmap to be stored in.
    BitmapHandle = CreateCompatibleBitmap(WindowHandle, width, height);

    //Select the correct bitmap, and put it into memory using the memory handle.
    SelectObject(MemoryHandle, BitmapHandle);

    //Transfer the actual bitmap into the compatible memory DC.
    BitBlt(MemoryHandle, 0, 0, 1920, 1080, WindowHandle, 0, 0, SRCCOPY);

    //Get the bitmap from the handle, and ready it to be filed.
    GetObject(BitmapHandle, sizeof(BITMAP), &Bitmap);

    //Cofinguring INFO details.
    bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfoHeader.biWidth = Bitmap.bmWidth;
    bmpInfoHeader.biHeight = Bitmap.bmHeight;
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biBitCount = 32;
    bmpInfoHeader.biCompression = BI_RGB;
    bmpInfoHeader.biSizeImage = 0;
    bmpInfoHeader.biXPelsPerMeter = 0;
    bmpInfoHeader.biYPelsPerMeter = 0;
    bmpInfoHeader.biClrUsed = 0;
    bmpInfoHeader.biClrImportant = 0;

    bmpSize = ((Bitmap.bmWidth * bmpInfoHeader.biBitCount + 31) / 32) * 4 * Bitmap.bmHeight;

    memhnd = GlobalAlloc(GHND, bmpSize);
    mat1 = (cv::Mat *)GlobalLock(memhnd);
    std::cout << GetLastError() << std::endl;

    return mat1;
}
int Capture::save_mat(cv::Mat * mat)
{
    std::string FileName("P:/Photos/capture");
    FileName += std::to_string(image_count_mat);
    FileName += (const char*)(".jpg");
    cv::Mat mat2 = *mat;
    cv::imwrite(FileName.c_str(), mat2);
    image_count_mat++;
    return 0;
}

该类具有以下属性:

private:
        HWND hwnd;
        HDC hdc;
        int image_count_bitmap = 0;
        int image_count_mat = 0;
        int height;
        int width;

        HDC ScreenHandle;
        HDC WindowHandle;
        HDC MemoryHandle = NULL;
        HBITMAP BitmapHandle = NULL;
        BITMAP Bitmap;

        BITMAPFILEHEADER bmpFileHeader;
        BITMAPINFOHEADER bmpInfoHeader;

        DWORD bmpSize;
        HANDLE memhnd;

GetMat() 函数工作正常并且不输出错误,尽管我不知道如何检查输出的 cv::Mat 是否正确。但是,当我运行 save_mat() 函数时,程序崩溃了。

标签: c++windowsopencv

解决方案


不建议存储设备上下文句柄。例如,一旦你完成了句柄,就GetDC应该调用 to。ReleaseDC您可以存储位图句柄和内存 dc,但在大多数情况下没有必要。

将图像复制到位图中后,使用GetDIBits将位复制到cv::Mat,如下例所示。

请注意,您的应用程序需要 DPI 兼容性,例如SetProcessDPIAware找到正确的桌面大小。

此示例使用 32 位位图CV_8UC4,但这些 GDI 函数是 24 位的。您还可以将 24 位位图与CV_8UC3.

void screenshot()
{
    auto w = GetSystemMetrics(SM_CXFULLSCREEN);
    auto h = GetSystemMetrics(SM_CYFULLSCREEN); 
    auto hdc = GetDC(HWND_DESKTOP);
    auto hbitmap = CreateCompatibleBitmap(hdc, w, h);
    auto memdc = CreateCompatibleDC(hdc);
    auto oldbmp = SelectObject(memdc, hbitmap);
    BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, SRCCOPY);

    cv::Mat mat(h, w, CV_8UC4);
    BITMAPINFOHEADER bi = { sizeof(bi), w, -h, 1, 32, BI_RGB };
    GetDIBits(hdc, hbitmap, 0, h, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
    cv::imwrite("screenshot.png", mat);

    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);
    DeleteObject(hbitmap);
    ReleaseDC(HWND_DESKTOP, hdc);
}

推荐阅读