使用 opencv 从帧创建 divx 编码的 avi
Create a divx-encoded avi from frames using opencv
这个问题与 this one and particularly this one 类似,但我想要的输出不同。我正在尝试使用 opencv 将桌面捕获为视频。首选输出是带有 divx 编码的 avi 文件。一般来说,我是 opencv 和位图编程的新手。
作为第一步,为了确保存在 divx 编解码器,我创建了单帧 (cv::Mat) 纯色(黄色)并将其写入视频文件 100 次,如图所示这里:
int main(int argc, char* argv[])
{
cv::Mat frame(1200, 1920, CV_8UC3, cv::Scalar(0, 50000, 50000));
cv::VideoWriter* videoWriter = new cv::VideoWriter(
"C:/videos/desktop.avi",
CV_FOURCC('D','I','V','3'),
5, cv::Size(1920, 1200), true);
int frameCount = 0;
while (frameCount < 100)
{
videoWriter->write(frame);
::Sleep(100);
frameCount++;
}
delete videoWriter;
return 0;
}
这非常有效 - 视频文件已创建,可以在我的 Win 10 机器上使用 VLC、Windows 媒体播放器或电影和电视应用程序播放。它是 100 帧纯黄色,但它表明视频正在正确创建。
下一步:用一系列桌面屏幕截图替换上面代码中的虚拟 cv::Mat 框架。我使用 GetDesktopWindow(), and then use the function hwnd2mat (taken from this 获得桌面 window 的句柄 SO 问题 - 谢谢!)将从桌面句柄获得的位图转换为我可以写入视频的 cv::Mat。
我逐字复制了 hwnd2mat 函数,除了我不缩放图像 - 桌面位图已经是 1920x1200,而且我创建的 cv::Mat 是 CV_8UC3 而不是 CV_8UC4 (CV_8UC4 导致我的应用程序崩溃)。
这是代码,包括 hwnd2mat 的重印:
int main(int argc, char* argv[])
{
cv::VideoWriter* videoWriter = new cv::VideoWriter(
"C:/videos/desktop.avi",
CV_FOURCC('D','I','V','3'),
5, Size(1920, 1200), true);
int frameCount = 0;
while (frameCount < 100)
{
HWND hDsktopWindow = ::GetDesktopWindow();
cv::Mat frame = hwnd2mat(hDsktopWindow);
videoWriter->write(frame);
::Sleep(100);
frameCount++;
}
delete videoWriter;
return 0;
}
cv::Mat hwnd2mat(HWND hwnd)
{
HDC hwindowDC, hwindowCompatibleDC;
int height, width, srcheight, srcwidth;
HBITMAP hbwindow;
cv::Mat src;
BITMAPINFOHEADER bi;
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;
src.create(height, width, CV_8UC3);
// create a bitmap
hbwindow = CreateCompatibleBitmap(hwindowDC, width, height);
bi.biSize = sizeof(BITMAPINFOHEADER);
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 = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
// 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);
GetDIBits(hwindowCompatibleDC, hbwindow, 0, height, src.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
// avoid memory leak
DeleteObject(hbwindow); DeleteDC(hwindowCompatibleDC); ReleaseDC(hwnd,hwindowDC);
return src;
}
这样做的结果是视频文件已创建并且可以无错误地播放,但它只是纯灰色。桌面位图似乎没有正确复制到 cv::Mat 框架中。我已经尝试了 BITMAPINFOHEADER 中值的无数组合,但没有任何效果,老实说我不知道我在做什么。我知道 opencv 有转换函数,但我什至不知道我要转换什么 to/from.
感谢任何帮助!
想出了一个让它工作的方法 - 我不知道这是否是最好的方法,所以仍然欢迎评论或替代解决方案。
似乎 GetDIBits 可以工作,cv::Mat 必须 是 4 通道,即 CV_8UC4,就像原来的一样我更改之前的 hwnd2mat 代码。如果不是 CV_8UC4,则不会复制任何数据(复制 GetDIBits returns 0 条扫描线),这就是为什么我的 avi 只是灰色的原因。所以第一个更改是将 src cv::Mat 创建为 4 通道:
src.create(height, width, CV_8UC4);
但是对于我尝试创建的 divx 编码的 avi 文件,帧应该是 3 通道的(不要问我为什么)。我在调用GetDIBits()后添加了一个opencv转换函数的调用,如下:
cv::Mat dst;
dst.create(height, width, CV_8UC3);
cv::cvtColor(src, dst, CV_RGBA2RGB);
然后我 return 从 hwnd2mat 而不是 src 进行 dst。对 cvtColor 的调用删除了 alpha 通道(RGBA 中的 A),dst 仅以 R、G、B 通道结束。
您可以从 GetDIBits 获取没有 alpha 通道的位图并将其直接写入 cv::VideoWriter。只需将 biBitCount 更改为 24。将 Mat 格式保留为 CV_8IC3。这对我有用。
src.create(高度, 宽度, CV_8UC3);
bi.biBitCount = 24; // 这是要改变的地方
这个问题与 this one and particularly this one 类似,但我想要的输出不同。我正在尝试使用 opencv 将桌面捕获为视频。首选输出是带有 divx 编码的 avi 文件。一般来说,我是 opencv 和位图编程的新手。
作为第一步,为了确保存在 divx 编解码器,我创建了单帧 (cv::Mat) 纯色(黄色)并将其写入视频文件 100 次,如图所示这里:
int main(int argc, char* argv[])
{
cv::Mat frame(1200, 1920, CV_8UC3, cv::Scalar(0, 50000, 50000));
cv::VideoWriter* videoWriter = new cv::VideoWriter(
"C:/videos/desktop.avi",
CV_FOURCC('D','I','V','3'),
5, cv::Size(1920, 1200), true);
int frameCount = 0;
while (frameCount < 100)
{
videoWriter->write(frame);
::Sleep(100);
frameCount++;
}
delete videoWriter;
return 0;
}
这非常有效 - 视频文件已创建,可以在我的 Win 10 机器上使用 VLC、Windows 媒体播放器或电影和电视应用程序播放。它是 100 帧纯黄色,但它表明视频正在正确创建。
下一步:用一系列桌面屏幕截图替换上面代码中的虚拟 cv::Mat 框架。我使用 GetDesktopWindow(), and then use the function hwnd2mat (taken from this 获得桌面 window 的句柄 SO 问题 - 谢谢!)将从桌面句柄获得的位图转换为我可以写入视频的 cv::Mat。
我逐字复制了 hwnd2mat 函数,除了我不缩放图像 - 桌面位图已经是 1920x1200,而且我创建的 cv::Mat 是 CV_8UC3 而不是 CV_8UC4 (CV_8UC4 导致我的应用程序崩溃)。
这是代码,包括 hwnd2mat 的重印:
int main(int argc, char* argv[])
{
cv::VideoWriter* videoWriter = new cv::VideoWriter(
"C:/videos/desktop.avi",
CV_FOURCC('D','I','V','3'),
5, Size(1920, 1200), true);
int frameCount = 0;
while (frameCount < 100)
{
HWND hDsktopWindow = ::GetDesktopWindow();
cv::Mat frame = hwnd2mat(hDsktopWindow);
videoWriter->write(frame);
::Sleep(100);
frameCount++;
}
delete videoWriter;
return 0;
}
cv::Mat hwnd2mat(HWND hwnd)
{
HDC hwindowDC, hwindowCompatibleDC;
int height, width, srcheight, srcwidth;
HBITMAP hbwindow;
cv::Mat src;
BITMAPINFOHEADER bi;
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;
src.create(height, width, CV_8UC3);
// create a bitmap
hbwindow = CreateCompatibleBitmap(hwindowDC, width, height);
bi.biSize = sizeof(BITMAPINFOHEADER);
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 = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
// 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);
GetDIBits(hwindowCompatibleDC, hbwindow, 0, height, src.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
// avoid memory leak
DeleteObject(hbwindow); DeleteDC(hwindowCompatibleDC); ReleaseDC(hwnd,hwindowDC);
return src;
}
这样做的结果是视频文件已创建并且可以无错误地播放,但它只是纯灰色。桌面位图似乎没有正确复制到 cv::Mat 框架中。我已经尝试了 BITMAPINFOHEADER 中值的无数组合,但没有任何效果,老实说我不知道我在做什么。我知道 opencv 有转换函数,但我什至不知道我要转换什么 to/from.
感谢任何帮助!
想出了一个让它工作的方法 - 我不知道这是否是最好的方法,所以仍然欢迎评论或替代解决方案。
似乎 GetDIBits 可以工作,cv::Mat 必须 是 4 通道,即 CV_8UC4,就像原来的一样我更改之前的 hwnd2mat 代码。如果不是 CV_8UC4,则不会复制任何数据(复制 GetDIBits returns 0 条扫描线),这就是为什么我的 avi 只是灰色的原因。所以第一个更改是将 src cv::Mat 创建为 4 通道:
src.create(height, width, CV_8UC4);
但是对于我尝试创建的 divx 编码的 avi 文件,帧应该是 3 通道的(不要问我为什么)。我在调用GetDIBits()后添加了一个opencv转换函数的调用,如下:
cv::Mat dst;
dst.create(height, width, CV_8UC3);
cv::cvtColor(src, dst, CV_RGBA2RGB);
然后我 return 从 hwnd2mat 而不是 src 进行 dst。对 cvtColor 的调用删除了 alpha 通道(RGBA 中的 A),dst 仅以 R、G、B 通道结束。
您可以从 GetDIBits 获取没有 alpha 通道的位图并将其直接写入 cv::VideoWriter。只需将 biBitCount 更改为 24。将 Mat 格式保留为 CV_8IC3。这对我有用。 src.create(高度, 宽度, CV_8UC3);
bi.biBitCount = 24; // 这是要改变的地方