ZeroMQ通过Unix域套接字传送视频帧,OpenCV实时显示

wangchunlin 2023-9-6 152 9/6

进程间通信有几种常用方式:TCP/IP、Unix Socket、SHM(Share Memory)
以下代码示例是通过:ZeroMQ(zmq库)来通过Unix域套接字传送图像,OpenCV的imshow函数展示图像
两个重点:1. ZeroMQ的基本用法;2. 控制imshow()函数实现同步播放的方法;

receiver.cpp:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <zmq.hpp>
#include <chrono>
#include <thread>
#include <sys/time.h>

int main() {
    // 创建ZeroMQ上下文
    zmq::context_t context(1);

    // 创建套接字并绑定到接收方
    zmq::socket_t socket(context, zmq::socket_type::pull);
    socket.bind("ipc:///tmp/video_socket");

    // 创建窗口用于显示视频
    cv::namedWindow("Received Video", cv::WINDOW_NORMAL);

    // 统计变量
    int frameCount = 0;
    double totalTime = 0.0;
    double avgFps = 0.0;
    double avgInterval = 0.0;
    std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
    struct timeval tv1, tv2;
    gettimeofday(&tv1, NULL);
    int lastFrameCount = 0;
    double lastTotalTime = 0.0;
    float tspan = 0.0;

    // 接收并实时播放视频帧
    while (true) {
        // 接收图像数据
        zmq::message_t message;
        socket.recv(&message);

        // 解析接收到的图像数据
        int width, height, type;
        memcpy(&width, message.data(), sizeof(int));
        memcpy(&height, static_cast<char*>(message.data()) + sizeof(int), sizeof(int));
        memcpy(&type, static_cast<char*>(message.data()) + sizeof(int) * 2, sizeof(int));

        // 计算图像数据的大小
        size_t imageDataSize = message.size() - sizeof(int) * 3 - sizeof(double);

        // 创建图像矩阵
        cv::Mat frame(height, width, type, static_cast<char*>(message.data()) + sizeof(int) * 3);

        // 获取帧率信息
        double fps;
        memcpy(&fps, static_cast<char*>(message.data()) + sizeof(int) * 3 + imageDataSize, sizeof(double));

        // 为了保证周期的准确,一下代码应该写到一起,关键点在于:算当前时间、等待剩余时间、重置时间三者的关系必须是依次紧挨这,中间不要加入额外操作,重置时间以后可以加入其他操作,不用着急imshow
        {
            // 获取当前时间点
            std::chrono::steady_clock::time_point currentTime = std::chrono::steady_clock::now();

            // 计算已经过去的时间
            std::chrono::duration<double> elapsedSeconds = currentTime - startTime;

            // 计算应该等待的时间间隔
            double expectedInterval = 1000 / fps;

            // 计算实际需要等待的时间间隔
            double actualInterval = expectedInterval - elapsedSeconds.count() * 1000;

            // 等待剩余时间,以实现与原视频完全一致的帧率
            if (actualInterval > 0) {
                std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(std::round(actualInterval))));
            } else {
                printf("Error! Accept and process the image timeout, if it is the first frame please ignore!\n");
            }

            // 更新起始时间点和计时器
            startTime = std::chrono::steady_clock::now();

            // 打印帧率和时间间隔信息
            // std::cout << "Received FPS: " << fps << std::endl;
            // std::cout << "Expected Interval: " << expectedInterval << " ms" << std::endl;
            // std::cout << "Elapsed Seconds: " << elapsedSeconds.count() * 1000 << " ms" << std::endl;
            // std::cout << "Actual Interval: " << actualInterval << " ms" << std::endl;
        }

        // 调试,在imshow前引入time.h的时间来统计帧率,看是否已经实时播放
        {
            gettimeofday(&tv2, NULL);
            tspan = (tv2.tv_sec - tv1.tv_sec) * 1000.0 + (tv2.tv_usec - tv1.tv_usec) / 1000.0;
            //printf("****************************the tspan=%f ms****************************\n", tspan);
            gettimeofday(&tv1, NULL);
        }

        // 显示图像
        cv::imshow("Received Video", frame);

        // 按下 ESC 键退出循环
        if (cv::waitKey(1) == 27)
            break;

        // 更新统计变量
        frameCount++;
        totalTime += tspan/1000;

        // 每秒钟打印一次统计结果
        if (totalTime - lastTotalTime >= 1.0) {
            int currentFrameCount = frameCount - lastFrameCount;
            double currentTotalTime = totalTime - lastTotalTime;
            avgFps = currentFrameCount / currentTotalTime;
            avgInterval = currentTotalTime * 1000 / currentFrameCount;
            std::cout << "Average FPS (last 1 second): " << avgFps << std::endl;
            std::cout << "Average Interval (last 1 second): " << avgInterval << " ms" << std::endl;

            // 更新上次统计的帧数和时间
            lastFrameCount = frameCount;
            lastTotalTime = totalTime;
        }  
    }

    // 关闭套接字
    socket.close();

    // 关闭 ZeroMQ 上下文
    context.close();

    return 0;
}

sender.cpp:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <zmq.hpp>

int main() {
    // 创建ZeroMQ上下文
    zmq::context_t context(1);

    // 创建套接字并连接到接收方
    zmq::socket_t socket(context, zmq::socket_type::push);
    socket.connect("ipc:///tmp/video_socket");

    // 打开视频文件
    cv::VideoCapture cap("1.mp4");
    if (!cap.isOpened()) {
        std::cerr << "Failed to open video file" << std::endl;
        return 1;
    }

    // 获取视频帧率
    double fps = cap.get(cv::CAP_PROP_FPS);
    std::cout << "Original FPS: " << fps << std::endl;

    // 逐帧读取并发送
    cv::Mat frame;
    while (cap.read(frame)) {
        // 获取图像尺寸
        int width = frame.cols;
        int height = frame.rows;
        int type = frame.type();

        // 创建消息
        zmq::message_t message(sizeof(int) * 3 + frame.total() * frame.elemSize() + sizeof(double));

        // 将图像尺寸复制到消息
        char* dataPtr = static_cast<char*>(message.data());
        memcpy(dataPtr, &width, sizeof(int));
        dataPtr += sizeof(int);
        memcpy(dataPtr, &height, sizeof(int));
        dataPtr += sizeof(int);
        memcpy(dataPtr, &type, sizeof(int));
        dataPtr += sizeof(int);

        // 将图像数据复制到消息
        memcpy(dataPtr, frame.data, frame.total() * frame.elemSize());
        dataPtr += frame.total() * frame.elemSize();

        // 将帧率信息复制到消息
        memcpy(dataPtr, &fps, sizeof(double));

        // 发送消息
        socket.send(message);

        // 打印帧率
        std::cout << "Sent FPS: " << fps << std::endl;
    }

    // 关闭套接字
    socket.close();

    // 关闭 ZeroMQ 上下文
    context.close();

    return 0;
}

编译指令:

sudo apt-get install libzmq3-dev
g++ receiver.cpp -o receiver `pkg-config --cflags --libs opencv` -lzmq
g++ sender.cpp -o sender `pkg-config --cflags --libs opencv` -lzmq
- THE END -

wangchunlin

9月06日14:29

最后修改:2023年9月6日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论