zmq远程画面显示

写在前面

2022/11/16更新:增加两种语言(python、c++)利用zmq实现图传

前言

目前有一个需求,PC端通过SSH远程访问Jetson nano,而Jetson nano进行视频流推理。现在PC端需要实时获取其推理后的画面。

有人可能有所疑问,直接在jetson nano上外接个显示屏不就行了吗?jetson nano上又不是没有显示屏的接口?为什么要大费周章的在PC端显示呢?

博主不太喜欢在nano上加上太多的东西,键盘、鼠标、显示器…,故时常需要远程开发。感兴趣的可以看下SSH和VScode远程开发这篇文章,基于嵌入式设备进行远程开发,而远程开发过程中无法获取画面,在深度学习模型推理过程中又经常需要进行可视化显示,所以这个需求自然而然的也就有了。

这只是在嵌入式设备上去进行模型部署,当然你可以选择外接显示器获取图像画面,当你在服务器上去进行模型部署,需要进行视频流推理时,这个需求就更加强烈了!

1.What、Why and How

1.1 what

什么是ZMQ?

ZeroMQ指的是类似于Socket的一系列接口,它跟Socket的区别是:普通的socket是端到端即1:1的关系, 而ZMQ却是可以N:M的关系,人们对BSD套接字了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而ZMQ屏蔽了这些细节,让你的网络编程更为简单。ZMQ用于node与node间的通信,node可以是主机或者是进程。

引用维基百科的说法:“ØMQ(也拼写作ZeroMQ0MQZMQ)是一个为可伸缩的分布式或并发应用程序设计的高性能异步消息库。它提供一个消息队列, 但是与面向消息的中间件不同,ZeroMQ的运行不需要专门的消息代理message broker)。该库设计成常见的套接字风格的API。”

引用官方的说法:“ZMQ(ØMQ、ZeroMQ, 0MQ)看起来像是一套嵌入式的网络链接库,但工作起来更像是一个并发式的框架。它提供的套接字可以在多种协议中传输消息,如线程间、进程间、TCP、广播等。你可以使用套接字构建多对多的连接模式,如扇出、发布-订阅、任务分发、请求-应答等。ZMQ的快速足以胜任集群应用产品。它的异步I/O机制让你能够构建多核应用程序,完成异步消息处理任务。ZMQ有着多语言支持,并能在几乎所有的操作系统上运行。ZMQ是iMatix公司的产品,以LGPL开源协议发布。”

1.2 why

为什么要学习ZMQ?

为了实现我们的需求即PC端能够实时获取嵌入式设备视频流的推理效果

1.3 How

如何去使用ZMQ?

tensorRT_Pro项目中帮助我们提供了对应的案例,我们拿过来直接使用即可

2.ZMQ的应用

嵌入式设备:Jetson nano

部署的模型:yolov7-tiny.pt

项目仓库:tensorRT_Pro

大家如果对Jetson nano嵌入式模型部署有疑问的,可以翻看前几篇文章,细节就不再赘述了,我们重点关注zmq实现远程显示画面。

tensorRT_Pro提供的案例在tensorRT_Pro/tools/show.py中,其最初目的是为了利用zmq remote实现远程显示服务器画面的效果。

2.1 yolov7模型部署

这里以上篇博客Jetson nano部署YOLOv7为基础,默认大家已经阅读过,细节就不再强调。本次以官方yolov7-tiny.pt模型为主,博主给出权重及其ONNX文件的下载链接Baidu Drive[password:yolo],关于ONNX导出的细节请参考上篇博客,这里不再赘述。

yolo模型的推理代码在src/application/app_yolo.cpp中,需要推理的图片放在workspace/inference文件夹中,需要推理的视频放在workspace/exp文件夹中,将上述修改后导出的ONNX文件放在workspace文件夹下。源码修改较简单,只需要修改一处:将app_yolo.cpp 177行"yolov7"改成"yolov7-tiny",构建yolov7-tiny.pt模型

2.1.1 编译

编译生成可执行文件pro,保存在workspace文件夹下,指令如下:

$ cd tensorRT_Pro-main
$ mkdir build && cd build
$ cmake .. && make -j8

编译图解如下所示

在这里插入图片描述

2.1.2 模型构建和推理

编译完成后的可执行文件pro存放在workspace文件夹下,故进入workspace文件夹下执行pro会生成yolov7-tiny.FP32.trtmodel引擎文件用于模型推理,会生成yolov7-tiny_Yolov7_FP32_result文件夹,该文件夹下保存了推理的图片

模型构建图解如下所示

在这里插入图片描述

模型推理效果如下图所示

在这里插入图片描述

2.2 zmq远程

zmq远程需要分别在Jetson nano和PC端写相应的代码

2.2.1 Jetson nano

jetson nano这边主要在src/application/app_yolo.cpp中添加一个新的函数用于视频流推理,代码主要修改以下几处:

  • 1.app_yolo.cpp添加zmq头文件
  • 2.app_yolo.cpp新增zmq_remote_demo()函数,具体内容参考下面
  • 3.app_yolo.cpp 176行新增调用zmq_remote_demo()函数代码,具体内容参考下面
  • 4.app_yolo.cpp 177行注释

具体修改如下

#include "tools/zmq_remote_show.hpp"		// 修改1 添加zmq头文件


static void zmq_remote_demo(){				// 修改2 新增zmq_remote_demo()函数
    INFO("===================== test yolov7 FP32 ==================================");
    
    auto engine_file = "yolov7-tiny.FP32.trtmodel";

    auto yolo = Yolo::create_infer(engine_file, Yolo::Type::V7, 00.25f, 0.45f);

    cv::Mat frame;
    cv::VideoCapture cap("exp/test.mp4");
    // cv::VideoCapture cap(0);
    // 1280*960
    // cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
    // cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);

    INFO("Video fps=%d, Width=%d, Height=%d",
        (int)cap.get(cv::CAP_PROP_FPS),
        (int)cap.get(cv::CAP_PROP_FRAME_WIDTH),
        (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));

    auto remote_show = create_zmq_remote_show();
    INFO("Use tools/show.py to remote show");

    while (cap.read(frame)){
        
        auto t0 = iLogger::timestamp_now_float();
        time_t now = time(0);
        auto boxes = yolo->commit(frame).get();
        for (auto &obj : boxes){
            uint8_t b, g, r;
            tie(r, g, b) = iLogger::random_color(obj.class_label);
            cv::rectangle(frame, cv::Point(obj.left, obj.top), cv::Point(obj.right, obj.bottom), cv::Scalar(b, g, r), 5);

            auto name = cocolabels[obj.class_label];
            auto caption = iLogger::format("%s %.2f", name, obj.confidence);

            int width = cv::getTextSize(caption, 0, 1, 2, nullptr).width + 10;
            cv::rectangle(frame, cv::Point(obj.left - 3, obj.top - 33), cv::Point(obj.left + width, obj.top), cv::Scalar(b, g, r), -1);
            cv::putText(frame, caption, cv::Point(obj.left, obj.top - 5), 0, 1, cv::Scalar::all(0), 2, 16);
        }
        remote_show->post(frame);
        // imshow("frame", frame);
        auto fee = iLogger::timestamp_now_float() - t0;
        INFO("fee %.2f ms, fps = %.2f", fee, 1 / fee * 1000);
        int key = cv::waitKey(1);
        if (key == 27)
            break;
    }
    cap.release();
    cv::destroyAllWindows();
    INFO("Done");
    yolo.reset();
    return;
}

int app_yolo(){
    zmq_remote_demo();						// 修改3 新增zmq_remote_demo()函数
    // test(Yolo::Type::V7, TRT::Mode::FP32, "yolov7");		// 修改4 注释该行
}

进入build文件夹下重新编译,然后进入workspace文件夹下运行即可调用摄像头检测,指令如下

$ cd build
$ make -j
$ cd ../workspace
$ ./pro yolo

图解如下所示

在这里插入图片描述

程序会一直等待PC端请求,至此,Jetson nano端的zmq远程已经准备完毕,下面来关注PC端

2.2.2 PC端

PC端发送请求即可,只需要运行一个代码,其具体内容在tensorRT_Pro/tools/show.py中,只需要修改一处:将show.py中的第11行修改为jetson nano的静态IP地址,博主在这里是通过网线设置静态IP,在jetson nano上可使用ifconfig查看对应的静态IP地址。对静态IP和远程控制不了解的可以查看SSH和VScode远程开发这篇文章。PC端的show.py代码博主放在pycharm中运行,别忘了安装zmq库,可使用pip install zmq指令安装。

静态IP地址查看如下图所示

在这里插入图片描述

PC端show.py代码如下所示

# 配合/data/sxai/tensorRT/src/application/app_fall_recognize.cpp中的zmq remote实现远程显示服务器画面的效果
# pip install zmq
import zmq
import sys
import numpy as np
import cv2

context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://192.168.101.66:15556")	# 修改为对应的静态IP地址

while True:
    socket.send(b"a")
    message = socket.recv()
    if len(message) == 1 and message == b'x':
    	break

    image = np.frombuffer(message, dtype=np.uint8)
    image = cv2.imdecode(image, 1)
    
    cv2.imshow("image", image)
    key = cv2.waitKey(1) & 0xFF

    if key == ord('q'):
    	break

至此,PC端的zmq远程已经准备完毕,可以进行zmq远程显示画面了。

2.2.3 运行

jetson nano端启动程序,然后点击PC端运行show.py即可看到对应的推理画面,这是博主在PC端看到的效果,无法展示视频流,只能通过图片来进行部分展示🤣,大家自行测试即可。

在这里插入图片描述

2.3 拓展

如果要进行摄像头实时推理将zmq_remote_demo()函数中的cv::VideoCapture cap("exp/test.mp4)"修改为cv::VideoCapture cap(0),然后重新编译运行即可。有时候需要进行视频流推理,有时需要进行USB摄像头推理、有时又需要网络摄像头推理,频繁改动程序重新编译运行过程繁琐,这时候可以用到上篇文章中提到的利用JSON配置文件解决这个问题,大家如果对JSON有疑问的可以查看Json及Jsoncpp开源库的应用这篇文章。

现在来构建一个JSON数据格式的配置文件,用来修改zmq_remote远程时读取的视频流(来自本地文件还是摄像头),方便后续调试。首先在workspace文件夹下创建一个JSON文件,命名为config.json,其内容如下:

{
    "conf_thresh" : 0.25,
    "nms_thresh"  : 0.45,
    "is_usb" : true,
    "camera" : 0
}

JSON文件中主要包含以下几个内容:

  • conf_thresh:float类型,置信度阈值
  • nms_thresh:float类型,nms阈值
  • is_usb:bool类型,判断此时读取的视频流是否来自USB摄像头
  • camera:VideoCapture捕获的视频流,可以从文件或者摄像设备中读取视频

然后修改源码,位于src/application/app_yolo.cpp,修改较简单主要有以下几点:

  • 1.app_yolo.cpp添加json头文件和对应命名空间
  • 2.app_yolo.cpp中zmq_remote_demo()函数中,添加json文件解析代码

具体修改如下

#include <common/json.hpp>			// 修改1 json头文件
#include <fstream>
using namespace Json;


static void zmq_remote_demo(){
    INFO("===================== test yolov7 FP32 ==================================");

    // JSON配置文件解析
    ifstream ifs("config.json");	// 修改2 json解析
    Value config;
    Reader r;
    r.parse(ifs, config);
    float conf_thresh = config["conf_thresh"].asFloat();
    float nms_thresh  = config["nms_thresh"].asFloat();
    INFO("conf_thresh = %.2f", conf_thresh);
    INFO("nms_thresh  = %.2f", nms_thresh);
    cv::VideoCapture cap;
    if(config["is_usb"].asBool()){
        auto camera = config["camera"].asInt();
        cap.open(camera);
        cap.set(CAP_PROP_FRAME_WIDTH, 640);
        cap.set(CAP_PROP_FRAME_HEIGHT, 480);
    }else{
        auto camera = config["camera"].asString();
        cap.open(camera);
    }

    INFO("Video fps=%d, Width=%d, Height=%d",
        (int)cap.get(cv::CAP_PROP_FPS),
        (int)cap.get(cv::CAP_PROP_FRAME_WIDTH),
        (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));
    
    auto engine_file = "yolov7-tiny.FP32.trtmodel";
    auto yolo = Yolo::create_infer(engine_file, Yolo::Type::V7, 0, conf_thresh, nms_thresh);

    cv::Mat frame;

    auto remote_show = create_zmq_remote_show();
    INFO("Use tools/show.py to remote show");

    while (cap.read(frame)){
        
        auto t0 = iLogger::timestamp_now_float();
        time_t now = time(0);
        auto boxes = yolo->commit(frame).get();
        for (auto &obj : boxes){
            uint8_t b, g, r;
            tie(r, g, b) = iLogger::random_color(obj.class_label);
            cv::rectangle(frame, cv::Point(obj.left, obj.top), cv::Point(obj.right, obj.bottom), cv::Scalar(b, g, r), 5);

            auto name = cocolabels[obj.class_label];
            auto caption = iLogger::format("%s %.2f", name, obj.confidence);

            int width = cv::getTextSize(caption, 0, 1, 2, nullptr).width + 10;
            cv::rectangle(frame, cv::Point(obj.left - 3, obj.top - 33), cv::Point(obj.left + width, obj.top), cv::Scalar(b, g, r), -1);
            cv::putText(frame, caption, cv::Point(obj.left, obj.top - 5), 0, 1, cv::Scalar::all(0), 2, 16);
        }
        remote_show->post(frame);
        // imshow("frame", frame);
        auto fee = iLogger::timestamp_now_float() - t0;
        INFO("fee %.2f ms, fps = %.2f", fee, 1 / fee * 1000);
        int key = cv::waitKey(1);
        if (key == 27)
            break;
    }
    cap.release();
    cv::destroyAllWindows();
    INFO("Done");
    yolo.reset();
    return;
}

进入build文件夹重新编译即可,编译图解如下所示,编译完成后可以修改config.json配置文件中置信度阈值、nms阈值、视频流设备等来观察变化。

在这里插入图片描述

试验1.设置的config.json如下所示,依旧是jetson nano端启动程序,PC端接收远程画面显示

{
    "conf_thresh" : 0.80,
    "nms_thresh"  : 0.45,
    "is_usb" : false,
    "camera" : "exp/test.mp4"
}

远程画面显示如下

在这里插入图片描述

分析

从远程画面可以看到,此时使用exp/test.mp4本地文件进行模型推理,可以看到置信度低于0.80的物体并没有显示

试验2.设置的config.json如下所示

{
    "conf_thresh" : 0.25,
    "nms_thresh"  : 0.45,
    "is_usb" : true,
    "camera" : 0
}

远程画面显示如下

在这里插入图片描述

分析

从远程画面可以看到,此时使用USB摄像头进行模型推理,可以看到置信度高于0.25的物体均被预测到

3.ZMQ图传拓展

利用zmq实现图传(C++、Python),采用zmq中请求-应答模式,共包含四种情况:

  • python(服务端)->python(客户端)
  • c++(服务端)->c++(客户端)
  • python(服务端)->c++(客户端)
  • c++(服务端)->python(客户端)

3.1 Python➡Python

3.1.1 服务端代码
import cv2
import sys
import zmq
import numpy as np

if __name__ == "__main__":
    
    context = zmq.Context()
    socket  = context.socket(zmq.REP)
    socket.bind("tcp://*:15556")

    cap = cv2.VideoCapture(0)
    ret, frame = cap.read()
    width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    print("width = {}, height = {}".format(width, height))
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

    while True:
        message = socket.recv()
        ret, image = cap.read()
        _, image_encode = cv2.imencode(".png", image)
        image_encode = np.array(image_encode)
        img_data = image_encode.tostring()
        socket.send(image_encode)
        key = cv2.waitKey(1)
        if key == 27:
            break
    cap.release()
    cv2.destroyAllWindows()
3.1.2 客户端代码
import zmq
import sys
import numpy as np
import cv2

context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://192.168.101.66:15556")

while True:
    socket.send(b"a")
    message = socket.recv()
    if len(message) == 1 and message == b'x':
        break

    image = np.frombuffer(message, dtype=np.uint8)
    image = cv2.imdecode(image, 1)

    cv2.imshow("image", image)
    key = cv2.waitKey(1) & 0xFF

    if key == ord('q'):
        break

问题:zmq.error.ZMQError: Operation cannot be accomplished in current state
原因:zmq存在三种工作模式,请求应答模式下必须遵守recv()和send()配对使用的编程模式
解决:不论在服务端还是客户端都需要recv()和send()数据
参考详解报错[zmq.error.ZMQError: Operation cannot be accomplished in current state]详解Python 实现 ZeroMQ 的三种基本工作模式

3.2 C++➡C++

3.2.1 服务端代码

服务端总体结构如下:

.
├── build
├── CMakeLists.txt
├── src
│   ├── common
│   │   ├── ilogger.cpp
│   │   └── ilogger.hpp
│   ├── main.cpp
│   └── tools
│       ├── zmq_remote_show.cpp
│       ├── zmq_remote_show.hpp
│       ├── zmq_u.cpp
│       └── zmq_u.hpp
└── workspace
  • build文件夹存放编译过程中的中间文件
  • workspace文件夹存放编译完成后的可执行文件
  • src文件夹存放源文件
    • src/common文件夹下存放日志打印文件,ilogger.hpp和ilogger.cpp参考ilogger.hpp和ilogger.cpp参考here
    • src/tools文件夹下存放zmq源文件,zmq_u.hpp、zmq_u.cpp、zmq_remote_show.hpp、zmq_remote_show.cpp参考here

main.cpp和CMakeLists.txt内容如下:

// main.cpp
#include <iostream>
#include "tools/zmq_remote_show.hpp"

using namespace std;
using namespace cv;


int main()
{
    auto remote_show = create_zmq_remote_show();

    Mat frame;
    VideoCapture cap(0);    // 1280*960
    cap.set(CAP_PROP_FRAME_WIDTH, 640);
    cap.set(CAP_PROP_FRAME_HEIGHT, 480);

    while (cap.read(frame)){
        remote_show->post(frame);
        int key = waitKey(1);
        if (key == 27)
            break;
    }

    return 0;
}
cmake_minimum_required(VERSION 3.9)
project(pro)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)

set(OpenCV_DIR "/usr/include/opencv4/")

find_package(OpenCV)

include_directories(
    ${PROJECT_SOURCE_DIR}/src
    ${PROJECT_BINARY_DIR}/src/tools
    ${PROJECT_BINARY_DIR}/src/common
    ${OpenCV_DIR}    
)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O0 -Wfatal-errors -pthread -w -g")
file(GLOB_RECURSE cpp_srcs ${PROJECT_SOURCE_DIR}/src/*.cpp)

add_executable(pro ${cpp_srcs})

target_link_libraries(pro pthread)
target_link_libraries(pro ${OpenCV_LIBS})

# reference:https://blog.csdn.net/luolaihua2018/article/details/117018285
3.2.2 客户端代码

客户端代码总体结构如下:

.
├── build
├── CMakeLists.txt
├── src
│   ├── main.cpp
│   └── tools
│       ├── zmq_u.cpp
│       └── zmq_u.hpp
└── workspace
  • build文件夹存放编译过程中的中间文件
  • workspace文件夹存放编译完成后的可执行文件
  • src文件夹存放源文件
  • src/tools文件夹下存放zmq源文件,zmq_u.hpp和zmq_u.cpp参考here

main.cpp和CMakeLists.txt内容如下:

// main.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include "tools/zmq_u.hpp"

using namespace std;
using namespace cv;

int main()
{
    zmq::context_t context(1);
    zmq::socket_t socket(context, ZMQ_REQ); // Request-Reply模式
    socket.connect("tcp://192.168.101.66:15556");
    string str = "a";       // 发送的内容
    zmq::message_t msg(str.size());
    memcpy(msg.data(), str.data(), str.size());
    zmq::message_t reply;   
    vector<uchar> buffer;
    Mat image;
    while(true){
        socket.send(msg);
        socket.recv(&reply);
        buffer.resize(reply.size());
        memcpy(buffer.data(), reply.data(), reply.size());
        image = cv::imdecode(buffer, cv::IMREAD_COLOR);
        cv::imshow("image", image);
        int key = waitKey(1);
        if (key==27)
            break;
    } 
}
cmake_minimum_required(VERSION 3.9)
project(pro)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)

set(OpenCV_DIR "/usr/include/opencv2")	# 修改为自己的opencv路径

find_package(OpenCV)

include_directories(
    ${PROJECT_SOURCE_DIR}/src
    ${PROJECT_BINARY_DIR}/src/tools
    ${OpenCV_DIR}    
)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O0 -Wfatal-errors -pthread -w -g")
file(GLOB_RECURSE cpp_srcs ${PROJECT_SOURCE_DIR}/src/*.cpp)

add_executable(pro ${cpp_srcs})

target_link_libraries(pro pthread)
target_link_libraries(pro ${OpenCV_LIBS})

# reference:https://blog.csdn.net/luolaihua2018/article/details/117018285

3.3 Python➡C++

3.3.1 服务端代码
import cv2
import sys
import zmq
import numpy as np

if __name__ == "__main__":
    
    context = zmq.Context()
    socket  = context.socket(zmq.REP)
    socket.bind("tcp://*:15556")

    cap = cv2.VideoCapture(0)
    ret, frame = cap.read()
    width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    print("width = {}, height = {}".format(width, height))
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

    while True:
        message = socket.recv()
        ret, image = cap.read()
        _, image_encode = cv2.imencode(".png", image)
        image_encode = np.array(image_encode)
        img_data = image_encode.tostring()
        socket.send(img_data)
        key = cv2.waitKey(1)
        if key == 27:
            break
    cap.release()
    cv2.destroyAllWindows()
3.3.2 客户端代码

客户端代码总体结构如下:

.
├── build
├── CMakeLists.txt
├── src
│   ├── main.cpp
│   └── tools
│       ├── zmq_u.cpp
│       └── zmq_u.hpp
└── workspace
  • build文件夹存放编译过程中的中间文件
  • workspace文件夹存放编译完成后的可执行文件
  • src文件夹存放源文件
  • src/tools文件夹下存放zmq源文件,zmq_u.hpp和zmq_u.cpp参考here

main.cpp和CMakeLists.txt内容如下:

// main.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include "tools/zmq_u.hpp"

using namespace std;
using namespace cv;

int main()
{
    zmq::context_t context(1);
    zmq::socket_t socket(context, ZMQ_REQ); // Request-Reply模式
    socket.connect("tcp://192.168.101.66:15556");
    string str = "a";       // 发送的内容
    zmq::message_t msg(str.size());
    memcpy(msg.data(), str.data(), str.size());
    zmq::message_t reply;   
    vector<uchar> buffer;
    Mat image;
    while(true){
        socket.send(msg);
        socket.recv(&reply);
        buffer.resize(reply.size());
        memcpy(buffer.data(), reply.data(), reply.size());
        image = cv::imdecode(buffer, cv::IMREAD_COLOR);
        cv::imshow("image", image);
        int key = waitKey(1);
        if (key==27)
            break;
    } 
}
cmake_minimum_required(VERSION 3.9)
project(pro)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)

set(OpenCV_DIR "/usr/include/opencv2")	# 修改为自己的opencv路径

find_package(OpenCV)

include_directories(
    ${PROJECT_SOURCE_DIR}/src
    ${PROJECT_BINARY_DIR}/src/tools
    ${OpenCV_DIR}    
)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O0 -Wfatal-errors -pthread -w -g")
file(GLOB_RECURSE cpp_srcs ${PROJECT_SOURCE_DIR}/src/*.cpp)

add_executable(pro ${cpp_srcs})

target_link_libraries(pro pthread)
target_link_libraries(pro ${OpenCV_LIBS})

# reference:https://blog.csdn.net/luolaihua2018/article/details/117018285

问题:传输的bytes没有问题,但是图像解码存在问题,解码后buffer中的数值始终为空

原因:buffer中的数值并不为空,而是std::cout输出unsigned char类型变量时不显示(眼见不一定为真)

解决:将unsigned char类型变量加上int强转后即可使用std::cout得到输出结果

参考opencv imdecode和imencode用法查BUG笔记-std::cout打印unsigned char类型变量不显示C++之ZeroMQ的使用vscode替换字符

3.4 C++➡Python

3.4.1 服务端代码

服务端总体结构如下:

.
├── build
├── CMakeLists.txt
├── src
│   ├── common
│   │   ├── ilogger.cpp
│   │   └── ilogger.hpp
│   ├── main.cpp
│   └── tools
│       ├── zmq_remote_show.cpp
│       ├── zmq_remote_show.hpp
│       ├── zmq_u.cpp
│       └── zmq_u.hpp
└── workspace
  • build文件夹存放编译过程中的中间文件
  • workspace文件夹存放编译完成后的可执行文件
  • src文件夹存放源文件
    • src/common文件夹下存放日志打印文件,ilogger.hpp和ilogger.cpp参考ilogger.hpp和ilogger.cpp参考here
    • src/tools文件夹下存放zmq源文件,zmq_u.hpp、zmq_u.cpp、zmq_remote_show.hpp、zmq_remote_show.cpp参考here

main.cpp和CMakeLists.txt内容如下:

// main.cpp
#include <iostream>
#include "tools/zmq_remote_show.hpp"

using namespace std;
using namespace cv;

int main()
{
    auto remote_show = create_zmq_remote_show();

    Mat frame;
    VideoCapture cap(0);    // 1280*960
    cap.set(CAP_PROP_FRAME_WIDTH, 640);
    cap.set(CAP_PROP_FRAME_HEIGHT, 480);

    while (cap.read(frame)){
        remote_show->post(frame);
        int key = waitKey(1);
        if (key == 27)
            break;
    }

    return 0;
}
cmake_minimum_required(VERSION 3.9)
project(pro)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/workspace)

set(OpenCV_DIR "/usr/include/opencv4/")

find_package(OpenCV)

include_directories(
    ${PROJECT_SOURCE_DIR}/src
    ${PROJECT_BINARY_DIR}/src/tools
    ${PROJECT_BINARY_DIR}/src/common
    ${OpenCV_DIR}    
)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O0 -Wfatal-errors -pthread -w -g")
file(GLOB_RECURSE cpp_srcs ${PROJECT_SOURCE_DIR}/src/*.cpp)

add_executable(pro ${cpp_srcs})

target_link_libraries(pro pthread)
target_link_libraries(pro ${OpenCV_LIBS})

# reference:https://blog.csdn.net/luolaihua2018/article/details/117018285
3.4.2 客户端代码
import zmq
import sys
import numpy as np
import cv2

context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://192.168.101.66:15556")

while True:
    socket.send(b"a")
    message = socket.recv()
    if len(message) == 1 and message == b'x':
        break

    image = np.frombuffer(message, dtype=np.uint8)
    image = cv2.imdecode(image, 1)

    cv2.imshow("image", image)
    key = cv2.waitKey(1) & 0xFF

    if key == ord('q'):
        break

结语

本篇博客介绍了ZMQ在嵌入式远程开发时的应用。关于ZMQ框架博主也没有深入了解,目前仅会简单应用来解决问题,大家感兴趣可以查看高速并发消息通信框架-ZeroMQ详解进行相关阅读。在这里博主利用ZMQ实现远程画面显示,当进行嵌入式或服务器远程开发时可以作为一个小工具使用,关于ZMQ更详细的内容和应用需要各位自己去挖掘啦。感谢各位看到最后,创作不易,读后有收获的看官帮忙点个👍。

下载链接

参考

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱听歌的周童鞋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值