【问题标题】:Creating MJEPG video from multiple JPEG encoded images without using cv::imdecode()不使用 cv::imdecode() 从多个 JPEG 编码图像创建 MJEPG 视频
【发布时间】:2020-07-23 00:07:23
【问题描述】:

我需要在内存中存储多个编码帧。
我正在使用cv::imencode(".jpg", ...) 将编码图像压缩并存储到std::list<std::vector<u_char>> compressed_images - 压缩图像列表。

我想从compressed_images创建一个视频,但我必须使用cv::imdecode()将所有图像解码为cv::Mat,然后使用cv::VideoWriter将图像保存为MJPEG视频。

我可以跳过cv::imdecode(),或者使用其他解决方案来避免编码两次吗?

【问题讨论】:

  • 我编辑了你的帖子,希望你不要介意。

标签: c++ opencv video jpeg mjpeg


【解决方案1】:

您可以将编码的图像通过管道传输到 FFmpeg。

根据以下post,您可以“简单地将JEPG图像混合制作视频”。

如果帧在内存中,您可以将编码图像写入 FFmpeg 的输入 PIPE。

使用-f image2pipe 格式标志代替-f image2。

用 C++ 实现解决方案对我来说太难了。
我已经实现了一个 Python 代码示例。

代码示例:

  • 构建一个包含 100 个编码帧的列表(绿色帧计数器)。
  • 将编码帧通过管道传输到ffmpeg 子进程。
    编码后的图像写入子进程的stdin输入流。

这里是 Python 代码示例:

import numpy as np
import cv2
import subprocess as sp

# Generate 100 synthetic JPEG encoded images in memory:
###############################################################################
# List of JPEG encoded frames.
jpeg_frames = []

width, height, n_frames = 640, 480, 100  # 100 frames, resolution 640x480

for i in range(n_frames):
    img = np.full((height, width, 3), 60, np.uint8)
    cv2.putText(img, str(i+1), (width//2-100*len(str(i+1)), height//2+100), cv2.FONT_HERSHEY_DUPLEX, 10, (30, 255, 30), 20)  # Green number

    # JPEG Encode img into jpeg_img
    _, jpeg_img = cv2.imencode('.JPEG', img)

    # Append encoded image to list.
    jpeg_frames.append(jpeg_img)
###############################################################################

#FFmpeg input PIPE: JPEG encoded images
#FFmpeg output AVI file encoded with MJPEG codec.
# https://video.stackexchange.com/questions/7903/how-to-losslessly-encode-a-jpg-image-sequence-to-a-video-in-ffmpeg
process = sp.Popen('ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi', stdin=sp.PIPE)

# Iterate list of encoded frames and write the encoded frames to process.stdin
for jpeg_img in jpeg_frames:
    process.stdin.write(jpeg_img)

# Close and flush stdin
process.stdin.close()

# Wait one more second and terminate the sub-process
try:
    process.wait(1)
except (sp.TimeoutExpired):
    process.kill()

更新:C++ 实现:

#include "opencv2/opencv.hpp"
#include "opencv2/highgui.hpp"

#ifdef _MSC_VER
#include <Windows.h>    //For Sleep(1000)
#endif

#include <stdio.h>

int main()
{
    int width = 640;
    int height = 480;
    int n_frames = 100;

    //Generate 100 synthetic JPEG encoded images in memory:
    //////////////////////////////////////////////////////////////////////////
    std::list<std::vector<uchar>> jpeg_frames;

    for (int i = 0; i < n_frames; i++)
    {
        cv::Mat img = cv::Mat(height, width, CV_8UC3);
        img = cv::Scalar(60, 60, 60);

        cv::putText(img, std::to_string(i + 1), cv::Point(width / 2 - 100 * (int)(std::to_string(i + 1).length()), height / 2 + 100), cv::FONT_HERSHEY_DUPLEX, 10, cv::Scalar(30, 255, 30), 20);  // Green number

        //cv::imshow("img", img);cv::waitKey(1);

        std::vector<uchar> jpeg_img;

        cv::imencode(".JPEG", img, jpeg_img);

        jpeg_frames.push_back(jpeg_img);
    }
    //////////////////////////////////////////////////////////////////////////

    //In Windows (using Visual Studio) we need to use _popen and in Linux popen
#ifdef _MSC_VER
    //ffmpeg.exe must be in the system path (or in the working directory)
    FILE *pipeout = _popen("ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi", "wb"); //For Windows use "wb"
#else
    //https://batchloaf.wordpress.com/2017/02/12/a-simple-way-to-read-and-write-audio-and-video-files-in-c-using-ffmpeg-part-2-video/
    FILE *pipeout = popen("ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi", "w"); //For Linux use "w"

    //In case ffmpeg is not in the execution path, you may use full path:
    //popen("/usr/bin/ffmpeg -y -f image2pipe -r 10 -i pipe: -codec copy out.avi", "w");
#endif

    std::list<std::vector<uchar>>::iterator it;

    //Iterate list of encoded frames and write the encoded frames to pipeout
    for (it = jpeg_frames.begin(); it != jpeg_frames.end(); ++it) 
    {
        std::vector<uchar> jpeg_img = *it;

        // Write this frame to the output pipe
        fwrite(jpeg_img.data(), 1, jpeg_img.size(), pipeout);
    }

    // Flush and close input and output pipes
    fflush(pipeout);

#ifdef _MSC_VER
    _pclose(pipeout);
#else
    pclose(pipeout);
#endif

    //It looks like we need to wait one more second at the end.
    Sleep(1000);

    return 0;
}

【讨论】:

  • 你是要我练习我的 C++ 吗?我发布了一个 C++ 实现。
  • "-i pipe:" 与 link 中的 "-i -" 不同。有类似的工作吗?
  • 是一样的。
  • 尝试按原样构建和执行我的代码。让我知道它是否有效。
  • 我必须使用 ffmpeg 的绝对路径和 "w" 标志(如果不是,pipeout 为 NULL) popen("/usr/bin/ffmpeg -y -f image2pipe -r 10 -i pipe: -编解码器复制 out.avi","w");
猜你喜欢
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
  • 2011-06-02
  • 2012-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多