当前位置: 首页 > news >正文

C++ opencv RTSP小工具 RTSP流播放、每一帧保存

目录

效果

项目

代码

下载


效果

项目

代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <string>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <sys/stat.h> // 用于文件夹操作 (C++14兼容)
#include <direct.h>   // Windows下的mkdir

// 线程安全的帧队列
class FrameQueue {
private:
    std::queue<std::pair<cv::Mat, std::string>> queue;
    std::mutex mtx;
    std::condition_variable cond;
    bool stop_flag = false;

public:

    void push(const cv::Mat& frame, const std::string& filename) {
        std::unique_lock<std::mutex> lock(mtx);
        // 深拷贝帧,因为原帧可能会被修改或释放
        queue.push(std::make_pair(frame.clone(), filename));
        cond.notify_one();
    }

    bool pop(std::pair<cv::Mat, std::string>& item) {
        std::unique_lock<std::mutex> lock(mtx);
        cond.wait(lock, [this]() { return !queue.empty() || stop_flag; });

        if (stop_flag && queue.empty()) {
            return false;
        }

        item = queue.front();
        queue.pop();
        return true;
    }

    void stop() {
        std::unique_lock<std::mutex> lock(mtx);
        stop_flag = true;
        cond.notify_all();
    }

    bool empty() {
        std::unique_lock<std::mutex> lock(mtx);
        return queue.empty();
    }
};

// 全局变量
FrameQueue frameQueue;
std::atomic<long long> total_save_time{ 0 };
std::atomic<int> saved_frames_count{ 0 };
std::string imgFolder = "img"; // 图片保存文件夹

// 检查文件夹是否存在 (C++14兼容方法)
bool folderExists(const std::string& folderPath) {
    struct stat info;
    return stat(folderPath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
}

// 创建文件夹 (C++14兼容方法)
bool createFolder(const std::string& folderPath) {
#ifdef _WIN32
    return _mkdir(folderPath.c_str()) == 0;
#else
    return mkdir(folderPath.c_str(), 0733) == 0;
#endif
}

// 检查并创建图片保存文件夹
bool ensureImageFolderExists() {
    // 检查文件夹是否存在
    if (folderExists(imgFolder)) {
        std::cout << "使用图片保存文件夹: " << imgFolder << std::endl;
        return true;
    }

    // 如果不存在,尝试创建文件夹
    if (createFolder(imgFolder)) {
        std::cout << "创建图片保存文件夹: " << imgFolder << std::endl;
        return true;
    }

    std::cerr << "错误: 无法创建图片保存文件夹" << std::endl;
    return false;
}

// 保存图像的线程函数
void saveFrameThread() {
    std::vector<int> compression_params;
    compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
    compression_params.push_back(100);

    while (true) {
        std::pair<cv::Mat, std::string> item;
        if (!frameQueue.pop(item)) {
            break;
        }

        auto start_time = std::chrono::high_resolution_clock::now();

        bool saveResult = cv::imwrite(item.second, item.first);
        //bool saveResult = cv::imwrite(item.second, item.first, compression_params);

        auto end_time = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

        if (!saveResult) {
            std::cerr << "错误: 无法保存帧: " << item.second << std::endl;
        }
        else {
            long long save_time = duration.count();
            total_save_time += save_time;
            saved_frames_count++;

            std::cout << "帧已保存: " << item.second<< " (保存时间: " << save_time << "ms)" << std::endl;
        }
    }
}

// 保存图像的线程函数
void saveFrameThread2() {
    std::vector<int> compression_params;
    compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
    compression_params.push_back(100);  // 质量

    while (true) {
        std::pair<cv::Mat, std::string> item;
        if (!frameQueue.pop(item)) {
            break;
        }

        auto start_time = std::chrono::high_resolution_clock::now();

        // 编码图像
        std::vector<uchar> buffer;
        auto encode_start = std::chrono::high_resolution_clock::now();
        cv::imencode(".jpg", item.first, buffer, compression_params);
        auto encode_end = std::chrono::high_resolution_clock::now();

        // 写入文件
        auto write_start = std::chrono::high_resolution_clock::now();
        std::ofstream ofs(item.second, std::ios::binary);
        ofs.write(reinterpret_cast<char*>(buffer.data()), buffer.size());
        ofs.close();
        auto write_end = std::chrono::high_resolution_clock::now();

        auto end_time = std::chrono::high_resolution_clock::now();
        auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
        auto encode_duration = std::chrono::duration_cast<std::chrono::milliseconds>(encode_end - encode_start);
        auto write_duration = std::chrono::duration_cast<std::chrono::milliseconds>(write_end - write_start);

        if (!ofs) {
            std::cerr << "错误: 无法保存帧: " << item.second << std::endl;
        }
        else {
            long long total_time = total_duration.count();
            long long encode_time = encode_duration.count();
            long long write_time = write_duration.count();
            total_save_time += total_time;
            saved_frames_count++;

            std::cout << "帧已保存: " << item.second
                << " (总时间: " << total_time << "ms, 编码: " << encode_time << "ms, 写入: " << write_time << "ms)" << std::endl;
        }
    }
}

// 显示使用说明
void printUsage(const std::string& programName) {
    std::cout << "使用方法: " << programName << " <RTSP_URL>" << std::endl;
    std::cout << "示例: " << programName << " rtsp://username:password@192.168.1.100:554/stream" << std::endl;
    std::cout << "注意: 如果URL中包含特殊字符(如&),请将整个URL用双引号括起来" << std::endl;
    std::cout << std::endl;
    std::cout << "控制命令:" << std::endl;
    std::cout << "  q/Q      - 退出程序" << std::endl;
    std::cout << "  空格键   - 暂停/继续播放" << std::endl;
    std::cout << "  s/S      - 显示保存统计信息" << std::endl;
    std::cout << "  c/C      - 清零统计信息" << std::endl;
}

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

    // 检查命令行参数
    if (argc != 2) {
        std::cerr << "错误: 需要提供RTSP URL作为参数" << std::endl;
        printUsage(argv[0]);
        return -1;
    }

    // 从命令行参数获取RTSP URL
    std::string rtspUrl = argv[1];
    std::cout << "使用RTSP URL: " << rtspUrl << std::endl;

    
    // 检查并创建图片保存文件夹
    if (!ensureImageFolderExists()) {
        std::cerr << "错误: 无法创建或访问图片保存文件夹,程序将退出" << std::endl;
        return -1;
    }

    // 打开视频流
    cv::VideoCapture cap(rtspUrl, cv::CAP_FFMPEG);

    if (!cap.isOpened()) {
        std::cerr << "错误: 无法打开RTSP流" << std::endl;
        return -1;
    }

    // 获取视频流的基本信息
    double fps = cap.get(cv::CAP_PROP_FPS);
    int frameWidth = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
    int frameHeight = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
    std::cout << "RTSP流信息: " << frameWidth << "x" << frameHeight << " at " << fps << " FPS" << std::endl;

    // 创建保存线程
    std::thread saveThread(saveFrameThread);

    // 创建窗口
    cv::namedWindow("RTSP Stream", cv::WINDOW_NORMAL);
    cv::resizeWindow("RTSP Stream", frameWidth / 4, frameHeight / 4);

    // 主循环
    cv::Mat frame;
    while (true) {
        // 读取一帧
        if (!cap.read(frame)) {
            std::cerr << "错误: 读取帧失败" << std::endl;
            break;
        }

        // 显示帧
        cv::imshow("RTSP Stream", frame);

        // 获取当前时间(精确到毫秒)
        auto now = std::chrono::system_clock::now();
        auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
        auto epoch = now_ms.time_since_epoch();
        auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
        long long milliseconds = value.count();

        // 将毫秒时间戳转换为可读格式
        std::time_t time_t_now = std::chrono::system_clock::to_time_t(now);
        std::tm tm_struct;
        localtime_s(&tm_struct, &time_t_now);
        std::ostringstream oss;
        oss << std::put_time(&tm_struct, "%Y%m%d_%H%M%S_") << std::setfill('0') << std::setw(3) << (milliseconds % 1000);
        std::string timestampStr = oss.str();

        // 构建保存图像的文件名(包含img文件夹路径)
        std::string filename = imgFolder + "/frame_" + timestampStr + ".bmp";

        // 将帧和文件名添加到队列(由保存线程处理)
        frameQueue.push(frame, filename);

        // 处理键盘输入
        int key = cv::waitKey(1) & 0xFF;
        if (key == 'q' || key == 'Q') {
            break;
        }
        else if (key == ' ') {
            while (true) {
                int innerKey = cv::waitKey(0) & 0xFF;
                if (innerKey == ' ') {
                    break;
                }
                else if (innerKey == 'q' || innerKey == 'Q') {
                    cv::destroyAllWindows();
                    cap.release();
                    frameQueue.stop();
                    saveThread.join();
                    return 0;
                }
            }
        }
        else if (key == 's' || key == 'S') {
            // 显示统计信息
            if (saved_frames_count > 0) {
                double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;
                std::cout << "保存统计: " << saved_frames_count << " 帧, 平均保存时间: "<< avg_save_time << "ms" << std::endl;
            }
        }
        else if (key == 'c' || key == 'C') {
            // 清空统计信息
            total_save_time = 0;
            saved_frames_count = 0;
            std::cout << "统计信息已清零" << std::endl;
        }
    }

    // 释放资源
    cap.release();
    cv::destroyAllWindows();

    // 停止保存线程并等待结束
    frameQueue.stop();
    saveThread.join();

    // 显示最终统计信息
    if (saved_frames_count > 0) {
        double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;
        std::cout << "最终统计: " << saved_frames_count << " 帧已保存, 平均保存时间: "<< avg_save_time << "ms" << std::endl;
    }

    return 0;
}

#include <opencv2/opencv.hpp>
#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <string>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <sys/stat.h> // 用于文件夹操作 (C++14兼容)
#include <direct.h>   // Windows下的mkdir// 线程安全的帧队列
class FrameQueue {
private:std::queue<std::pair<cv::Mat, std::string>> queue;std::mutex mtx;std::condition_variable cond;bool stop_flag = false;public:void push(const cv::Mat& frame, const std::string& filename) {std::unique_lock<std::mutex> lock(mtx);// 深拷贝帧,因为原帧可能会被修改或释放queue.push(std::make_pair(frame.clone(), filename));cond.notify_one();}bool pop(std::pair<cv::Mat, std::string>& item) {std::unique_lock<std::mutex> lock(mtx);cond.wait(lock, [this]() { return !queue.empty() || stop_flag; });if (stop_flag && queue.empty()) {return false;}item = queue.front();queue.pop();return true;}void stop() {std::unique_lock<std::mutex> lock(mtx);stop_flag = true;cond.notify_all();}bool empty() {std::unique_lock<std::mutex> lock(mtx);return queue.empty();}
};// 全局变量
FrameQueue frameQueue;
std::atomic<long long> total_save_time{ 0 };
std::atomic<int> saved_frames_count{ 0 };
std::string imgFolder = "img"; // 图片保存文件夹// 检查文件夹是否存在 (C++14兼容方法)
bool folderExists(const std::string& folderPath) {struct stat info;return stat(folderPath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
}// 创建文件夹 (C++14兼容方法)
bool createFolder(const std::string& folderPath) {
#ifdef _WIN32return _mkdir(folderPath.c_str()) == 0;
#elsereturn mkdir(folderPath.c_str(), 0733) == 0;
#endif
}// 检查并创建图片保存文件夹
bool ensureImageFolderExists() {// 检查文件夹是否存在if (folderExists(imgFolder)) {std::cout << "使用图片保存文件夹: " << imgFolder << std::endl;return true;}// 如果不存在,尝试创建文件夹if (createFolder(imgFolder)) {std::cout << "创建图片保存文件夹: " << imgFolder << std::endl;return true;}std::cerr << "错误: 无法创建图片保存文件夹" << std::endl;return false;
}// 保存图像的线程函数
void saveFrameThread() {std::vector<int> compression_params;compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);compression_params.push_back(100);while (true) {std::pair<cv::Mat, std::string> item;if (!frameQueue.pop(item)) {break;}auto start_time = std::chrono::high_resolution_clock::now();bool saveResult = cv::imwrite(item.second, item.first);//bool saveResult = cv::imwrite(item.second, item.first, compression_params);auto end_time = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);if (!saveResult) {std::cerr << "错误: 无法保存帧: " << item.second << std::endl;}else {long long save_time = duration.count();total_save_time += save_time;saved_frames_count++;std::cout << "帧已保存: " << item.second<< " (保存时间: " << save_time << "ms)" << std::endl;}}
}// 保存图像的线程函数
void saveFrameThread2() {std::vector<int> compression_params;compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);compression_params.push_back(100);  // 质量while (true) {std::pair<cv::Mat, std::string> item;if (!frameQueue.pop(item)) {break;}auto start_time = std::chrono::high_resolution_clock::now();// 编码图像std::vector<uchar> buffer;auto encode_start = std::chrono::high_resolution_clock::now();cv::imencode(".jpg", item.first, buffer, compression_params);auto encode_end = std::chrono::high_resolution_clock::now();// 写入文件auto write_start = std::chrono::high_resolution_clock::now();std::ofstream ofs(item.second, std::ios::binary);ofs.write(reinterpret_cast<char*>(buffer.data()), buffer.size());ofs.close();auto write_end = std::chrono::high_resolution_clock::now();auto end_time = std::chrono::high_resolution_clock::now();auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);auto encode_duration = std::chrono::duration_cast<std::chrono::milliseconds>(encode_end - encode_start);auto write_duration = std::chrono::duration_cast<std::chrono::milliseconds>(write_end - write_start);if (!ofs) {std::cerr << "错误: 无法保存帧: " << item.second << std::endl;}else {long long total_time = total_duration.count();long long encode_time = encode_duration.count();long long write_time = write_duration.count();total_save_time += total_time;saved_frames_count++;std::cout << "帧已保存: " << item.second<< " (总时间: " << total_time << "ms, 编码: " << encode_time << "ms, 写入: " << write_time << "ms)" << std::endl;}}
}// 显示使用说明
void printUsage(const std::string& programName) {std::cout << "使用方法: " << programName << " <RTSP_URL>" << std::endl;std::cout << "示例: " << programName << " rtsp://username:password@192.168.1.100:554/stream" << std::endl;std::cout << "注意: 如果URL中包含特殊字符(如&),请将整个URL用双引号括起来" << std::endl;std::cout << std::endl;std::cout << "控制命令:" << std::endl;std::cout << "  q/Q      - 退出程序" << std::endl;std::cout << "  空格键   - 暂停/继续播放" << std::endl;std::cout << "  s/S      - 显示保存统计信息" << std::endl;std::cout << "  c/C      - 清零统计信息" << std::endl;
}int main(int argc, char* argv[]) {// 检查命令行参数if (argc != 2) {std::cerr << "错误: 需要提供RTSP URL作为参数" << std::endl;printUsage(argv[0]);return -1;}// 从命令行参数获取RTSP URLstd::string rtspUrl = argv[1];std::cout << "使用RTSP URL: " << rtspUrl << std::endl;// 检查并创建图片保存文件夹if (!ensureImageFolderExists()) {std::cerr << "错误: 无法创建或访问图片保存文件夹,程序将退出" << std::endl;return -1;}// 打开视频流cv::VideoCapture cap(rtspUrl, cv::CAP_FFMPEG);if (!cap.isOpened()) {std::cerr << "错误: 无法打开RTSP流" << std::endl;return -1;}// 获取视频流的基本信息double fps = cap.get(cv::CAP_PROP_FPS);int frameWidth = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));int frameHeight = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));std::cout << "RTSP流信息: " << frameWidth << "x" << frameHeight << " at " << fps << " FPS" << std::endl;// 创建保存线程std::thread saveThread(saveFrameThread);// 创建窗口cv::namedWindow("RTSP Stream", cv::WINDOW_NORMAL);cv::resizeWindow("RTSP Stream", frameWidth / 4, frameHeight / 4);// 主循环cv::Mat frame;while (true) {// 读取一帧if (!cap.read(frame)) {std::cerr << "错误: 读取帧失败" << std::endl;break;}// 显示帧cv::imshow("RTSP Stream", frame);// 获取当前时间(精确到毫秒)auto now = std::chrono::system_clock::now();auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);auto epoch = now_ms.time_since_epoch();auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);long long milliseconds = value.count();// 将毫秒时间戳转换为可读格式std::time_t time_t_now = std::chrono::system_clock::to_time_t(now);std::tm tm_struct;localtime_s(&tm_struct, &time_t_now);std::ostringstream oss;oss << std::put_time(&tm_struct, "%Y%m%d_%H%M%S_") << std::setfill('0') << std::setw(3) << (milliseconds % 1000);std::string timestampStr = oss.str();// 构建保存图像的文件名(包含img文件夹路径)std::string filename = imgFolder + "/frame_" + timestampStr + ".bmp";// 将帧和文件名添加到队列(由保存线程处理)frameQueue.push(frame, filename);// 处理键盘输入int key = cv::waitKey(1) & 0xFF;if (key == 'q' || key == 'Q') {break;}else if (key == ' ') {while (true) {int innerKey = cv::waitKey(0) & 0xFF;if (innerKey == ' ') {break;}else if (innerKey == 'q' || innerKey == 'Q') {cv::destroyAllWindows();cap.release();frameQueue.stop();saveThread.join();return 0;}}}else if (key == 's' || key == 'S') {// 显示统计信息if (saved_frames_count > 0) {double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;std::cout << "保存统计: " << saved_frames_count << " 帧, 平均保存时间: "<< avg_save_time << "ms" << std::endl;}}else if (key == 'c' || key == 'C') {// 清空统计信息total_save_time = 0;saved_frames_count = 0;std::cout << "统计信息已清零" << std::endl;}}// 释放资源cap.release();cv::destroyAllWindows();// 停止保存线程并等待结束frameQueue.stop();saveThread.join();// 显示最终统计信息if (saved_frames_count > 0) {double avg_save_time = static_cast<double>(total_save_time) / saved_frames_count;std::cout << "最终统计: " << saved_frames_count << " 帧已保存, 平均保存时间: "<< avg_save_time << "ms" << std::endl;}return 0;
}

下载

源码下载


文章转载自:

http://UwXF2XwE.qrmyd.cn
http://5SxDuR5K.qrmyd.cn
http://Veq7Cq1e.qrmyd.cn
http://lIY8iRZC.qrmyd.cn
http://Zd1gxSvO.qrmyd.cn
http://GobiPR2D.qrmyd.cn
http://gzA8I1kT.qrmyd.cn
http://ecQpRztH.qrmyd.cn
http://IaToetpP.qrmyd.cn
http://FtU6Oepr.qrmyd.cn
http://UlHeP31v.qrmyd.cn
http://o4Ecl2rn.qrmyd.cn
http://BtvIeoQL.qrmyd.cn
http://gXqfWozb.qrmyd.cn
http://M9H43Oe9.qrmyd.cn
http://iHE9oTQX.qrmyd.cn
http://LqB2YIiR.qrmyd.cn
http://sfFMMYy3.qrmyd.cn
http://C81Dc20a.qrmyd.cn
http://mySZgoZw.qrmyd.cn
http://lyaF5n7z.qrmyd.cn
http://c27LLogX.qrmyd.cn
http://esWoWNN8.qrmyd.cn
http://rNLPBHM6.qrmyd.cn
http://VC5idwjP.qrmyd.cn
http://iKnfBDjX.qrmyd.cn
http://VTg9E7h4.qrmyd.cn
http://q5RQ9kZS.qrmyd.cn
http://cGXkIXfl.qrmyd.cn
http://WrmvFPNu.qrmyd.cn
http://www.dtcms.com/a/368685.html

相关文章:

  • android View详解—动画
  • 2024年9月GESPC++三级真题解析(含视频)
  • ASP.NET Core文件分片上传
  • OCA、OCP、OCM傻傻分不清?Oracle认证就看这篇
  • 面试了一个外包公司,面试不到5分钟就出来,这问题问得有点变态。。。。。。
  • Matlab使用小技巧合集(系列四):Table类型高效用法与数据处理实战
  • 25高教社杯数模国赛【C题超高质量思路+可运行代码】第十弹
  • WinForms 项目里生成时选择“首选目标平台 32 位导致有些电脑在获取office word对象时获取不到
  • ANSYS 热力耦合计算
  • UE4 Mac构建编译报错 no member named “disjunction” in namespace “std”
  • 深度相机详解
  • vue 经常写的echarts图表模块结构抽取
  • 蚂蚁 S21e XP Hyd 3U 860T矿机性能分析与技术特点
  • Python迭代协议完全指南:从基础到高并发系统实现
  • CT影像寻找皮肤轮廓预处理
  • 7种流行Prompt设计模式详解:适用场景与最佳实践
  • uni-app 项目 iOS 上架踩坑经验总结 从证书到审核的避坑指南
  • 3.3_第一行之hard_local_irq_disable
  • 汽车 信息娱乐系统 概览
  • 将已有 Vue 项目通过 Electron 打包为桌面客户端的完整步骤
  • Nginx 配置片段主要用于实现​​正向代理​​,可以用来转发 HTTP 和 HTTPS 请求
  • 有鹿机器人的365天奇幻日记:我在景区当扫地僧
  • C++算法专题学习——分治
  • 智能工单路由系统(Java)
  • 生成模型实战 | 深度分层变分自编码器(Nouveau VAE,NVAE)
  • Windows多开文件夹太乱?Q-Dir四窗口同屏,拖拽文件快一倍
  • 测试驱动开发 (TDD) 与 Claude Code 的协作实践详解
  • Bug 排查日记:打造高效问题定位与解决的技术秘籍
  • MySQL InnoDB索引机制
  • Nextcloud 实战:打造属于你的私有云与在线协作平台