求助:单步骤实现图片转视频的同时实时播放(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
相关产品推荐
相关产品推荐

