如何将cv::Mat从cpp端发送到python端

12 篇文章 0 订阅
9 篇文章 0 订阅

如何使用zeromq发送cv::Mat给python端

OpenCV已经成了图像领域不得不学习的库,其源码为cpp编写,但是实际上我们也会经常使用python版本的OpenCV进行快速开发, 但有些场景下,又不得不将cpp代码中的数据传给python端,对于这种需求,
一种方法是使用pybind11之类的第三方库,将cpp代码变成python的一个库,但多进程通信也不失为另一种优雅的方法。下面就介绍下如何将cpp代码中的一个cv::Mat图片发送给python,并在python中进行显示。

框架介绍

先介绍两个库:

  • zeromq

zeromq是一个网络库,它提供的套接字可以在多种协议中传输消息,如线程间、进程间、TCP、广播等。你可以使用套接字构建多对多的连接模式,如发布-订阅、任务分发、请求-应答等。 有了zeromq你就可以从socket琐碎的细节中摆脱出来。
请添加图片描述

  • protobuf

Protocol Buffers是一种开源跨平台的序列化数据结构的协议。它可以方便定义不同语言之间序列化数据的规则。一个简单例子,比如你有个cpp代码,定义了一个struct,这时你需要将这个struct的数据内容分享给其他应用程序,这些应用程序有些是python写的,有些是cpp写的,有些是go写的,有些是大端格式,有些小端,那么如何保证你的这个struct中的数据能够解析成目标语言对应的数据类型呢? struct修改了某些字段后,如何将这些修改很快同步到其他语言中去呢?每个语言都要重写反序列化的代码?protobuf则会cover住这些细节。

代码流程

下面demo展示了cpp端如何将cv::Mat序列化,并通过zeromq发送出去,python端如何解析buff并还原出原始cv::Mat.先创建一个文件夹,新建4个文件:msg.proto, CMakeLists.txt, pub.cpp, sub.py.下面开始往这4个文件中填充内容。

  • 定义proto文件
    下面定义一个protobuf使用的msg.proto文件,里面定义了一个Mat的成员变量,其中mat_data用来存放cv::Mat 的buffer数据。
syntax = "proto3";
package RL;
message OcvMat {
    int32 rows = 1;
    int32 cols = 2;
    int32 channels = 3;
    int32 elt_type = 4;
    int32 elt_size = 5;
    bytes mat_data = 6;
}

有了这个msg.proto文件,我们可以使用protoc生成对应的cpp或者python代码。

protoc --cpp_out . msg.proto
protoc --python_out . msg.proto
  • cmake定义
    cmake脚本会根据proto文件的定义自动生成cpp和python代码,并编译发布端pub文件,定义如下:
cmake_minimum_required(VERSION 3.5.1)
project(cpp-py)
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../cmake")
set (CMAKE_CXX_STANDARD 14)

find_package(OpenCV REQUIRED PATHS /install_x86/)
find_package(ZMQ)
find_package(Protobuf)

include_directories(${ZMQ_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS}  ${Protobuf_INCLUDE_DIR})
include_directories("${PROJECT_SOURCE_DIR}/../cppzmq")
include_directories("${PROJECT_SOURCE_DIR}/")
message("PROJECT_SOURCE_DIR:${PROJECT_SOURCE_DIR}")
# build cpp from proto
PROTOBUF_GENERATE_CPP(PROTO_SRC PROTO_HEADER msg.proto)
ADD_LIBRARY(proto ${PROTO_HEADER} ${PROTO_SRC})
add_executable(pub "pub.cpp")
target_link_libraries(pub proto
                          ${Protobuf_LIBRARIES}
                          ${ZMQ_LIBRARIES}
                          ${OpenCV_LIBS}
                          )

# build python script from proto
PROTOBUF_GENERATE_PYTHON(PROTO_PY msg.proto)
add_custom_target(myTarget ALL DEPENDS ${PROTO_PY})

其中PROTOBUF_GENERATE_CPP会从proto文件生成pub.cpp使用头文件(msg.pb.h)和cc文件(msg.pb.cc),接着会编译到pub文件中。PROTOBUF_GENERATE_PYTHON会从proto文件生成sub.py使用的python库。

  • cpp端(pub.cpp):
    pub.cpp引用了msg.pb.h文件,该头文件定义了cv::Mat的序列化方式。
#include <iostream>
#include <chrono>
#include <thread>
#include <opencv2/opencv.hpp>
#include <build/msg.pb.h>

#include <zmq.hpp>

int main(int argc, char *argv[])
{
    zmq::context_t context (1);
    zmq::socket_t publisher (context, ZMQ_PUB);
    publisher.set(zmq::sockopt::sndhwm, 2); // in case use too much memory, only cache 2 frame

    publisher.bind("tcp://127.0.0.1:5557");
    int i_frame (0);
    cv::Mat img_original = cv::imread("../cat.jpg");
    while(true) {
        char buff[255];
        sprintf(buff, "frame idx:%d", i_frame);
        cv::Mat img = img_original.clone();
        cv::putText(img, buff, cv::Point(0, 50), cv::FONT_HERSHEY_SCRIPT_COMPLEX, 1.0, cv::Scalar(255, 0, 0));

        RL::OcvMat serializableMat;
        serializableMat.set_rows(img.rows);
        serializableMat.set_cols(img.cols);
        serializableMat.set_channels(img.channels());
        serializableMat.set_elt_type(img.type());
        serializableMat.set_elt_size((int)img.elemSize()); //3
        size_t dataSize = img.rows * img.cols * img.elemSize();
        serializableMat.set_mat_data(img.data, dataSize);

        std::string encoded_msg;
        serializableMat.SerializeToString(&encoded_msg);
        //serializableMat.SerializeToOstream

        zmq::message_t zmq_msg(encoded_msg.size());
        memcpy ((void *) zmq_msg.data(), encoded_msg.c_str(), encoded_msg.size());
        publisher.send(zmq_msg);

        ++i_frame;
    }

    std::cout << "pub done" << std::endl;
    return 0;
}

先定义一个publisher,在zmq中,publisher类似一个生产者,源源不断的制造数据,并将数据放在缓存队列中,等待订阅者(subscriber)从队列中取数据。由于队列中存储的是图片,占用内存比较大,如果订阅者处理太慢可能会导致资源耗尽,所以这里设置了sndhwm参数为2,就是说我们只保存2帧数据在队列中,多余的会被抛弃。

zmq::context_t context (1);
zmq::socket_t publisher (context, ZMQ_PUB);
publisher.set(zmq::sockopt::sndhwm, 2); // in case use too much memory, only cache 2 frame
publisher.bind("tcp://127.0.0.1:5557");

RL::OcvMat是前面proto生成的cpp结构体,和cv::Mat中的成员变量一一对应。

  • subscribe端(sub.py)
    subscribe端会监听5557端口,并将获取的buff转换为一张图片并显示。
import zmq
import sys
import os
import time
import numpy as np
import cv2
sys.path.append(os.getcwd() + '/build')

from msg_pb2 import *

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:5557")
socket.setsockopt_string(zmq.SUBSCRIBE, "")
time.sleep(1)

ocv_mat = OcvMat()
while True:
    t_start = time.time()
    raw_msg = socket.recv()
    ocv_mat.ParseFromString(raw_msg)
    img = np.frombuffer(ocv_mat.mat_data, dtype = np.uint8)
    img = img.reshape(ocv_mat.rows, ocv_mat.cols, ocv_mat.channels)
    fps = 1/(time.time() - t_start) 
    print(f'fps:{fps}')
    cv2.imshow('img', img)
    cv2.waitKey(1)

结果类似如下:
请添加图片描述

图片分辨率为1082*1016, 实测本机传输帧率约有200fps,还是非常快的。如果想要更快的速度,可以考虑使用cv::imencode方法对原始图片mat进行jpeg编码,将编码后的数据发送出去,python端再用cv2.imdecode进行解码,估计fps又上一层楼。

所有源代码参考链接

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值