You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:单步骤实现图片转视频的同时实时播放(OpenCV/gStreamer等环境)

实现思路与代码示例

核心逻辑

单进程内复用读取到的图片帧:每读取一张图片,同时执行两个操作——1. 将帧写入视频文件;2. 在窗口中实时显示该帧。关键是保证帧率同步,同时处理图片读取的顺序问题(必须按文件名排序避免乱序)。


方案一:OpenCV 原生实现(无需额外依赖)

利用OpenCV自带的VideoWriter完成视频保存,imshow实现实时播放,单线程即可完成需求。

代码示例

#include <opencv2/opencv.hpp>
#include <vector>
#include <string>
#include <algorithm>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    // 配置参数
    const std::string img_dir = "./images";
    const std::string output_video = "./output.mp4";
    const double fps = 24.0;
    const std::string img_ext = ".jpg";

    // 收集并排序图片文件
    std::vector<fs::path> img_paths;
    for (const auto& entry : fs::directory_iterator(img_dir)) {
        if (entry.path().extension() == img_ext) {
            img_paths.push_back(entry.path());
        }
    }
    std::sort(img_paths.begin(), img_paths.end());

    if (img_paths.empty()) {
        std::cerr << "No images found in target directory!" << std::endl;
        return -1;
    }

    // 获取帧分辨率并初始化VideoWriter
    cv::Mat first_frame = cv::imread(img_paths[0].string());
    if (first_frame.empty()) {
        std::cerr << "Failed to load first image!" << std::endl;
        return -1;
    }
    const cv::Size frame_size(first_frame.cols, first_frame.rows);
    const int fourcc = cv::VideoWriter::fourcc('a', 'v', 'c', '1'); // H264编码
    cv::VideoWriter video_writer(output_video, fourcc, fps, frame_size);

    if (!video_writer.isOpened()) {
        std::cerr << "Failed to initialize VideoWriter (check codec support)!" << std::endl;
        return -1;
    }

    // 创建播放窗口
    cv::namedWindow("Image Playback", cv::WINDOW_NORMAL);

    // 循环处理每帧
    for (const auto& img_path : img_paths) {
        cv::Mat frame = cv::imread(img_path.string());
        if (frame.empty()) {
            std::cerr << "Skipping invalid image: " << img_path << std::endl;
            continue;
        }

        // 同时执行保存与播放
        video_writer.write(frame);
        cv::imshow("Image Playback", frame);

        // 控制帧率,按ESC可提前退出
        const int key = cv::waitKey(static_cast<int>(1000.0 / fps));
        if (key == 27) break;
    }

    // 释放资源
    video_writer.release();
    cv::destroyAllWindows();
    return 0;
}

方案二:gStreamer 分支管道(灵活编码/播放)

通过gStreamer构建分支管道,将单帧同时路由到文件写入和视频显示模块,适合需要更复杂编码或播放逻辑的场景。

代码示例

#include <gst/gst.h>
#include <opencv2/opencv.hpp>
#include <vector>
#include <algorithm>
#include <filesystem>

namespace fs = std::filesystem;

// 向gStreamer管道推送帧的回调函数
static GstFlowReturn push_frame(GstAppSrc* src, gpointer user_data) {
    cv::Mat* frame = static_cast<cv::Mat*>(user_data);
    if (frame->empty()) return GST_FLOW_EOS;

    // 将OpenCV Mat转换为GstBuffer
    GstBuffer* buffer = gst_buffer_new_allocate(nullptr, frame->total() * frame->elemSize(), nullptr);
    GstMapInfo map;
    gst_buffer_map(buffer, &map, GST_MAP_WRITE);
    memcpy(map.data, frame->data, frame->total() * frame->elemSize());
    gst_buffer_unmap(buffer, &map);

    // 设置时间戳保证帧率同步
    const GstClockTime timestamp = gst_util_uint64_scale(
        gst_clock_get_time(gst_system_clock_obtain()),
        GST_SECOND, static_cast<guint64>(24 * GST_SECOND)
    );
    GST_BUFFER_PTS(buffer) = timestamp;
    GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale_int(1, GST_SECOND, 24);

    return gst_app_src_push_buffer(src, buffer);
}

int main(int argc, char* argv[]) {
    gst_init(&argc, &argv);

    // 收集并排序图片
    const std::string img_dir = "./images";
    std::vector<fs::path> img_paths;
    for (const auto& entry : fs::directory_iterator(img_dir)) {
        if (entry.path().extension() == ".jpg") img_paths.push_back(entry.path());
    }
    std::sort(img_paths.begin(), img_paths.end());

    if (img_paths.empty()) {
        g_printerr("No images found!\n");
        return -1;
    }

    // 构建分支管道:一路存文件,一路播放
    const std::string pipeline_str = 
        "appsrc name=src ! video/x-raw,format=BGR,width=640,height=480,framerate=24/1 ! videoconvert ! tee name=t "
        "t. ! queue ! x264enc ! mp4mux ! filesink location=output.mp4 "
        "t. ! queue ! autovideosink";
    GstElement* pipeline = gst_parse_launch(pipeline_str.c_str(), nullptr);
    GstElement* app_src = gst_bin_get_by_name(GST_BIN(pipeline), "src");

    // 配置appsrc为手动推送模式
    g_object_set(app_src, "stream-type", 0, "format", GST_FORMAT_TIME, nullptr);
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    // 循环处理图片
    for (const auto& img_path : img_paths) {
        cv::Mat frame = cv::imread(img_path.string());
        if (frame.empty()) {
            g_printerr("Skipping invalid image: %s\n", img_path.c_str());
            continue;
        }
        cv::resize(frame, frame, cv::Size(640, 480)); // 匹配管道分辨率

        push_frame(GST_APP_SRC(app_src), &frame);
        cv::waitKey(static_cast<int>(1000.0 / 24)); // 控制帧率
    }

    // 发送结束信号并清理资源
    gst_app_src_end_of_stream(GST_APP_SRC(app_src));
    GstBus* bus = gst_element_get_bus(pipeline);
    gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_EOS);

    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
    return 0;
}

关键注意事项

  • 图片排序:必须对文件夹内的图片按文件名排序,否则视频和播放会出现乱序。
  • 帧率同步:通过waitKey(OpenCV)或时间戳(gStreamer)控制每帧停留时间,保证保存和播放的帧率一致。
  • 编码兼容性:OpenCV的VideoWriter依赖系统编码库,若H264无法使用,可替换为MJPG编码(fourcc('M','J','P','G'))生成AVI文件。
  • 性能优化:图片量极大时,可新增线程负责读取图片,主线程处理保存和播放,避免IO阻塞影响帧率。

内容的提问来源于stack exchange,提问作者Mohamed Adel El-Badry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 20:30:32