1.引言
当您希望将多个视频按照不同的方式拼接在一起并同时显示时,OpenCV库提供了强大的功能来实现这个目标。在本篇博客中,介绍如何使用OpenCV来创建一个多视频拼接程序。通过命令行交互获取视频信息,并根据用户的选择,将视频按照水平、垂直或2x2网格的方式进行拼接,并最终保存视频。
2.工作环境
OpenCV4.5.4、vs2022
3.代码实现
#include<opencv2/opencv.hpp>
#include<iostream>
int main()
{
int n = 0;
std::cout << "多视频【拼接】显示:" << std::endl;
std::cout << "请输入你要拼接的视频数量(2-4):";
std::cin >> n;
if (n < 2 || n>4)
{
std::cout << "拼接视频数量超过限制,请重新输入......" << std::endl;
return -1;
}
std::vector<std::string> video_paths;
for (int i = 0; i < n; i++)
{
std::cout << "输入第" << i + 1 << "个视频路径:";
std::string video_path;
std::cin >> video_path;
video_paths.push_back(video_path);
}
std::cout << "请选择拼接模式:(0)水平方向拼接;(1)垂直方向拼接;(2)2*2网格拼接(仅限输入视频数量为4)" << std::endl;
int concate_mode;
while (true)
{
std::cin >> concate_mode;
if (concate_mode != 0 && concate_mode != 1 && concate_mode != 2)
{
std::cout << "拼接模式选择错误请重新选择:" << std::endl;
continue;
}
if (concate_mode == 2 && n != 4)
{
std::cout << "拼接模式选择错误请重新选择:" << std::endl;
continue;
}
else
{
break;
}
}
std::vector<cv::VideoCapture>caps(n);
for (int i = 0; i < n; i++)
{
if (!caps[i].open(video_paths[i])) {
std::cerr << "错误:打开视频" << i << "失败!" << std::endl;
return -1;
}
}
// 创建VideoWriter写入结果
cv::VideoWriter writer;
int codec = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
double fps = caps[0].get(cv::CAP_PROP_FPS);
int height = (int)caps[0].get(cv::CAP_PROP_FRAME_HEIGHT);
int width = (int)caps[0].get(cv::CAP_PROP_FRAME_WIDTH);
cv::Size outputFrame_Size;
std::vector<cv::Mat>frames(n);
switch (concate_mode)
{
case 0:
{
outputFrame_Size = cv::Size(width * n, height);
writer.open("output.avi", codec, fps, outputFrame_Size);
if (!writer.isOpened()) {
std::cerr << "无法打开输出视频文件!" << std::endl;
for (int i = 0; i < n; ++i) {
caps[i].release(); // 释放资源
}
return -1;
}
while (true)
{
// 读取每段视频的当前帧
bool allFramesRead = true;
for (int i = 0; i < n; ++i)
{
caps[i] >> frames[i];
// 检查是否结束
if (frames[i].empty()) {
allFramesRead = false;
break;
}
}
if (!allFramesRead) {
break;
}
cv::Mat outputImg(outputFrame_Size, CV_8UC3);
int offset = 0;
for (const auto& img : frames) {
img.copyTo(outputImg(cv::Rect(offset, 0, img.cols, img.rows)));
offset += img.cols;
}
writer.write(outputImg);
}
writer.release();
for (int i = 0; i < n; ++i) {
caps[i].release(); // 释放资源
}
break;
}
case 1:
{
outputFrame_Size = cv::Size(width, height * n);
writer.open("output.avi", codec, fps, outputFrame_Size);
if (!writer.isOpened()) {
std::cerr << "无法打开输出视频文件!" << std::endl;
for (int i = 0; i < n; ++i) {
caps[i].release(); // 释放资源
}
return -1;
}
while (true)
{
// 读取每段视频的当前帧
bool allFramesRead = true;
for (int i = 0; i < n; ++i)
{
caps[i] >> frames[i];
// 检查是否结束
if (frames[i].empty()) {
allFramesRead = false;
break;
}
}
if (!allFramesRead) {
break;
}
cv::Mat outputImg(outputFrame_Size, CV_8UC3);
int offset = 0;
for (auto& img : frames) {
img.copyTo(outputImg(cv::Rect(0, offset, img.cols, img.rows)));
offset += img.rows;
}
writer.write(outputImg);
}
writer.release();
for (int i = 0; i < n; ++i) {
caps[i].release(); // 释放资源
}
break;
}
case 2:
{
cv::Size outputFrame_Size = cv::Size(width * 2, height * 2);
writer.open("output.avi", codec, fps, outputFrame_Size);
if (!writer.isOpened()) {
std::cerr << "无法打开输出视频文件!" << std::endl;
for (int i = 0; i < n; ++i) {
caps[i].release(); // 释放资源
}
return -1;
}
cv::Mat outputImg(outputFrame_Size, CV_8UC3);
while (true)
{
bool allFramesRead = true;
for (int i = 0; i < n; ++i)
{
caps[i] >> frames[i];
if (frames[i].empty()) {
allFramesRead = false;
break;
}
}
if (!allFramesRead) {
break;
}
// 拼接视频帧成2x2网格
frames[0].copyTo(outputImg(cv::Rect(0, 0, width, height)));
frames[1].copyTo(outputImg(cv::Rect(width, 0, width, height)));
frames[2].copyTo(outputImg(cv::Rect(0, height, width, height)));
frames[3].copyTo(outputImg(cv::Rect(width, height, width, height)));
writer.write(outputImg);
}
writer.release();
for (int i = 0; i < n; ++i) {
caps[i].release();
}
break;
}
default:
break;
return 0;
}
}
注意:所拼接的视频需要注意长、宽统一,当多个视频的时长不一致时,以时间最短的为准。