用C++从像素值数组创建视频

4
有人知道一种将存储在数组中的像素值序列保存为视频的方法吗?目前我正在使用Cimg可视化一个简单的n体模拟,虽然我可以将每次迭代保存到图像文件中,但这非常慢。如果有类似于处理视频的库的建议将不胜感激。基本上,我只想记录创建的Cimg窗口中显示的内容到视频文件中。该程序是用C++编写的,在Linux上使用g++进行编译。
事实上,我可以通过屏幕捕捉软件运行模拟并记录它的运行情况,这表明可能是可以的,但我想要一个更整洁的解决方案。
2个回答

10
我今天试着做了一些事情,想和大家分享我的结果。你可以从CImg输出原始RGB视频,然后使用ffmpeg将其编码成视频,就像这样:
#include <iostream>
#include "CImg.h"

using namespace std;
using namespace cimg_library;

int main()
{
   const unsigned int width=1024;
   const unsigned int height=768;

   // Basic frame we will draw in
   CImg<unsigned char> image(width,height,1,3);

   unsigned char magenta[] = {255,0,255};

   // We are going to output 300 frames of 1024x768 RGB raw video
   // ... making a 10s long video at 30fps
   int radius=100;
   int cx=100;
   int cy=100;
   for(int frame=0;frame<300;frame++){
      // Start with black - it shows fewer stains ;-)
      image.fill(0);
      image.draw_circle(cx,cy,radius,magenta);

      // Move and re-colour circle
      cx+=2; cy++; if(magenta[1]!=255){magenta[1]++;}

      // Output to ffmpeg to make video, in planar GBR format
      // i.e. run program like this
      // ./main | ffmpeg -y -f rawvideo -pixel_format gbrp -video_size 1024x768 -i - -c:v h264 -pix_fmt yuv420p video.mov
      char* s=reinterpret_cast<char*>(image.data()+(width*height));   // Get start of G plane
      std::cout.write(s,width*height);                                // Output it
      s=reinterpret_cast<char*>(image.data()+2*(width*height));       // Get start of B plane
      std::cout.write(s,width*height);                                // Output it
      s=reinterpret_cast<char*>(image.data());                        // Get start of R plane
      std::cout.write(s,width*height);                                // Output it
   }
}

我猜我不会进入好莱坞,因为这个视频不是很令人兴奋!

enter image description here

按照以下方式运行上述代码以生成视频:

./main | ffmpeg -y -f rawvideo -pixel_format gbrp -video_size 1024x768 -i - -c:v h264 -pix_fmt yuv420p video.mov

注1

需要明白的是,CImg以平面配置存储数据,这意味着所有红色像素首先保存,然后直接保存所有绿色像素,最后紧随其后保存所有蓝色像素-全部没有填充或间隔。

想象一下在CImg中有一个4x4的图像(共16个像素):

RRRRRRRRRRRRRRRR GGGGGGGGGGGGGGGG BBBBBBBBBBBBBBBB

与常规的RGB数据不同,它将存储与以下图像相同的图像:

RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB 

因此,您可以将所有数据混合并重新格式化,并将其作为-pixel_fmt rgb24传递给ffmpeg,或者像我一样以CImg的平面格式输出并选择匹配的-pixel_fmt gbrp(其中p表示"平面")。您只需要按正确的B、G、R顺序输出平面即可。另请参见Note 4

注2

为了更清晰地展示,我选择使用3个write(),分别写入每个色彩平面,但实际上使用 writev()"gathered write" 会更高效。

char* s=reinterpret_cast<char*>(image.data()+(width*height));   // Get start of G plane
std::cout.write(s,width*height);                                // Output it
s=reinterpret_cast<char*>(image.data()+2*(width*height));       // Get start of B plane
std::cout.write(s,width*height);                                // Output it
s=reinterpret_cast<char*>(image.data());                        // Get start of R plane
std::cout.write(s,width*height);  

会成为类似这样的东西:

struct iovec iov[3];
ssize_t nwritten;

iov[0].iov_base = reinterpret_cast<char*>(image.data()+(width*height))
iov[0].iov_len  = width*height;
iov[1].iov_base = reinterpret_cast<char*>(image.data()+2*(width*height));
iov[1].iov_len  = width*height;
iov[2].iov_base = reinterpret_cast<char*>(image.data());  
iov[2].iov_len  = width*height;

nwritten = writev(STDOUT_FILENO,iov,3);

注3

我使用了-c:v h264 -pix_fmt yuv420p来使视频与我的Mac上的苹果公司的QuickTime兼容,但您可以轻松更改输出 - 更难的部分是正确地处理CImgfmpeg之间的接口。


注4

如果你想要重新排列数据并将其写入ffmpeg非平面格式(-pixel_fmt rgb),我最初的代码如下:

// Outside main loop
unsigned char* BIP = new unsigned char[width*height*3];
unsigned char *d,*r,*g,*b;

...
...

// Now output it...
// ... remember CImg is band-interleaved by plane  RRRRRR GGGGGG BBBBBB
// ... not band-interleaved by pixel RGB RGB RGB RGB
r=image.data();       // Start of R plane in CImg image
g=r+(width*height);   // Start of G plane in CImg image
b=g+(width*height);   // Start of B plane in CImg image
d=BIP;                // Destination buffer in RGB order
for(int i=0;i<width*height;i++){
   *d++=*r++;
   *d++=*g++;
   *d++=*b++;
}
// Output to ffmpeg to make video, i.e. run program like this
// ./main | ffmpeg -y -f rawvideo -pixel_format rgb24 -video_size 1024x768 -i - -c:v h264 -pix_fmt yuv420p video.mov
std::cout.write(reinterpret_cast<char*>(BIP),width*height*3);

理论上,您可以使用CImgpermute_axes()方法来实现此操作,但我没有成功。


2
如果保存图像文件很慢,那么你可能做错了什么。保存图像文件的缺点是占用硬盘空间。
要制作视频有很多选择:
- 生成图像序列,并使用第三方外部工具(如ffmpeg)进行编码。 - 使用视频库,例如ffmpeg、libx264、gstreamer、DirectShow等。
在Linux上,我强烈推荐使用libx264或ffmpeg,它们还提供了从位图序列保存视频文件的示例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接