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

Ang*_*gus 3 c++ video cimg

有谁知道一种将存储在数组中的像素值序列保存到视频的方法?目前我正在使用 Cimg 来可视化一个简单的 n 体模拟,同时我可以将每次迭代保存到一个图像文件中,这非常慢。任何有关处理视频的类似库的建议将不胜感激。本质上,我只想将我创建的 Cimg 窗口中显示的内容记录到视频文件中。该程序是用 C++ 编写的,在 linux 上,用 g++ 编译。

我可以运行模拟并用屏幕捕获软件记录它的运行这一事实似乎意味着这是可能的,但我想要一个更整洁的解决方案。

干杯,安格斯

Mar*_*ell 5

我今天正在玩这个,我想我会分享我的结果。您可以从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
   }
}
Run Code Online (Sandbox Code Playgroud)

我想我不会去好莱坞,因为视频不是很令人兴奋!

在此处输入图片说明

像这样运行上面的代码来制作视频:

./main | ffmpeg -y -f rawvideo -pixel_format gbrp -video_size 1024x768 -i - -c:v h264 -pix_fmt yuv420p video.mov
Run Code Online (Sandbox Code Playgroud)

注 1

需要意识到的是,CImg将数据存储在平面配置中,这意味着首先是所有红色像素,然后是所有绿色像素,然后是所有蓝色像素 - 没有任何填充或空格。

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

RRRRRRRRRRRRRRRR GGGGGGGGGGGGGGGG BBBBBBBBBBBBBBBB
Run Code Online (Sandbox Code Playgroud)

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

RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB RGB 
Run Code Online (Sandbox Code Playgroud)

因此,您可以将所有数据打乱并重新格式化并传递给ffmpeg as -pixel_fmt rgb24,或者像我一样以CImg的平面格式输出并选择一个匹配-pixel_fmt gbrp(其中的p意思是“平面”)。您只需要以正确的 B、G、R 顺序输出平面。另见注 4


笔记2

我选择了做3个write()S,每种颜色一个平面,用于演示的清楚起见,这将是更有效地使用一个“收集写”writev(),所以这样的:

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);  
Run Code Online (Sandbox Code Playgroud)

会变成这样(未经测试):

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);
Run Code Online (Sandbox Code Playgroud)

注 3

我使用-c:v h264 -pix_fmt yuv420p使视频与Mac 上的Apple QuickTime兼容,但无论如何您都可以轻松更改输出 - 更难的部分是正确设置CImgfmpeg之间的接口。


注 4

如果你想打乱数据并将其写入ffmpeg non-planar ( -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);
Run Code Online (Sandbox Code Playgroud)

理论上,你可以用CImgpermute_axes()方法做到这一点,但我没有成功。