写在前面
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(也拼写作ZeroMQ,0MQ或ZMQ)是一个为可伸缩的分布式或并发应用程序设计的高性能异步消息库。它提供一个消息队列, 但是与面向消息的中间件不同,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, 0,0.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文件夹存放源文件
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文件夹存放源文件
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更详细的内容和应用需要各位自己去挖掘啦。感谢各位看到最后,创作不易,读后有收获的看官帮忙点个👍。
下载链接
参考
- SSH和VScode远程开发
- tensorRT_Pro
- Jetson nano部署YOLOv7
- Json及Jsoncpp开源库的应用
- 高速并发消息通信框架-ZeroMQ详解
- ZeroMQ官方文档
- ZeroMQ官方指南
- ZeroMQ中文指南(旧版本)
- ZeroMQ官方Github
- 详解报错[zmq.error.ZMQError: Operation cannot be accomplished in current state]
- 详解Python 实现 ZeroMQ 的三种基本工作模式
- opencv imdecode和imencode用法
- 查BUG笔记-std::cout打印unsigned char类型变量不显示
- C++之ZeroMQ的使用
- vscode替换字符