window 在 OpenCV C++ 上的视频录制

Video recording of the window on OpenCV C++

我正在尝试编写一个用于记录 windows 的程序,但由于某种原因,在程序完成后,我得到一个损坏的 .avi 文件。 我不明白问题出在哪里。 hwnd2mat()windowNames() 功能正常,错误显然不在其中。代码看起来很庞大,但实际上,大部分代码都被图像从HWND到Mat的翻译占用了。还应该注意的是,录制后生成的图像始终具有相同的大小(与录制时间无关)。

#include <iostream>
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <opencv2/videoio.hpp>
#include <Windows.h>

BOOL CALLBACK windowNames(HWND hwnd, LPARAM lParam) {
    const DWORD TITLE_SIZE = 1024;
    WCHAR windowTitle[TITLE_SIZE];

    GetWindowTextW(hwnd, windowTitle, TITLE_SIZE);

    int length = ::GetWindowTextLength(hwnd);
    std::wstring title(&windowTitle[0]);
    if (!IsWindowVisible(hwnd) || length == 0 || title == L"Program Manager") {
        return TRUE;
    }

    // Retrieve the pointer passed into this callback, and re-'type' it.
    // The only way for a C API to pass arbitrary data is by means of a void*.
    std::vector<std::wstring>& titles = *reinterpret_cast<std::vector<std::wstring>*>(lParam);
    titles.push_back(title);

    return TRUE;
}

cv::Mat hwnd2mat(HWND hwnd)
{
    HDC hwindowDC, hwindowCompatibleDC;

    int height, width, srcheight, srcwidth;
    HBITMAP hbwindow;
    cv::Mat src;
    BITMAPINFOHEADER  bi;
    HBITMAP bi2;
    hwindowDC = GetDC(hwnd);
    hwindowCompatibleDC = CreateCompatibleDC(hwindowDC);
    SetStretchBltMode(hwindowCompatibleDC, COLORONCOLOR);

    RECT windowsize;    // get the height and width of the screen
    GetClientRect(hwnd, &windowsize);

    srcheight = windowsize.bottom;
    srcwidth = windowsize.right;
    height = windowsize.bottom / 1;  //change this to whatever size you want to resize to
    width = windowsize.right / 1;

    // create a bitmap
    hbwindow = CreateCompatibleBitmap(hwindowDC, width, height);
    bi.biSize = sizeof(BITMAPINFOHEADER);    //http://msdn.microsoft.com/en-us/library/windows/window/dd183402%28v=vs.85%29.aspx
    bi.biWidth = width;
    bi.biHeight = -height;  //this is the line that makes it draw upside down or not
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 1;
    bi.biYPelsPerMeter = 2;
    bi.biClrUsed = 3;
    bi.biClrImportant = 4;

    // use the previously created device context with the bitmap
    SelectObject(hwindowCompatibleDC, hbwindow);
    // copy from the window device context to the bitmap device context
    StretchBlt(hwindowCompatibleDC, 0, 0, width, height, hwindowDC, 0, 0, srcwidth, srcheight, SRCCOPY); //change SRCCOPY to NOTSRCCOPY for wacky colors !

    src.create(height, width, CV_8UC4);
    GetDIBits(hwindowCompatibleDC, hbwindow, 0, height, src.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);  //copy from hwindowCompatibleDC to hbwindow

    // avoid memory leak
    DeleteObject(hbwindow);
    DeleteDC(hwindowCompatibleDC);
    ReleaseDC(hwnd, hwindowDC);

    return src;
}

int main(int argc, char** argv)
{
    std::vector<std::wstring> titles; // we use std::wstring in place of std::string. This is necessary so that the entire character set can be represented.
    EnumWindows(windowNames, reinterpret_cast<LPARAM>(&titles));
    HWND hwndDesktop = GetDesktopWindow();
    size_t number = 0;
    int i = 0;
    for (const auto& title : titles)
        std::wcout << L"Title: " << i++ << title << std::endl;
    std::cin >> number;
    HWND hwndWindow = FindWindow(NULL, titles[number].c_str());
    cv::namedWindow("output", cv::WINDOW_NORMAL);
    cv::Mat src = hwnd2mat(/*hwndDesktop*/hwndWindow);
    cv::VideoWriter outputVideo("output.avi", cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 1, cv::Size(src.cols, src.rows));
    outputVideo.write(src);
    int key = 0;
    while (key != 27)
    {
         src = hwnd2mat(hwndWindow);
         outputVideo.write(src);
        cv::imshow("output", src);
        key = cv::waitKey(60); //press ESC to end
    }
    return 0;
}

VideoWriter 个实例需要使用 release() 方法关闭。完成视频容器文件。

OpenCV 不支持现代屏幕捕获 AFAIK。您需要使用特定于平台的方法来执行此操作(可能封装在某些库中)。这里的问题是屏幕数据已经在 GPU 中,并且通过使用 OpenCV,您强制将其复制到主内存并以相对较慢的速度进行处理 CPU。它不会表现良好。相反,特定于平台的方法将在 GPU 上处理数据,使用它来提取 window 的帧并对它们进行编码。它在速度和能耗方面都非常高效(在捕获 运行 时,您将大大延长电池寿命,并且会防止风扇在笔记本电脑上烦人)。

问题在于我们需要在将帧传输到VideoWriter对象的过程中将Mat从BGRA传输到BGR。 为了正确操作,需要写成

Mat bgrImg; cvtColor(src, bgrImg, COLOR_BGRA2BGR);

在发送帧前的范围内,bgrImg作为帧发送。