如何在ZMQ中使用序列化发送图像和数据字符串?

时间:2017-07-11 17:27:53

标签: python zeromq

我的目标是将图像和数据字符串从RPi(服务器)发送到客户端。我使用send_json(data),其中数据是dict {'img': img_ls, 'telemetry':'0.01, 320, -10'}img_ls是转换为列表的图像。问题是我得到了len( img_ls ) = 57556,而原始图片的大小是:320 x 240 = 76800.我不明白为什么会出现差异。这是代码:

SERVER侧

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://0.0.0.0:5557")


def outputs():
    stream = io.BytesIO()
    while True:
        yield stream
        stream.seek(0)
        sensors = '0.01, 320, -10'
        img_ls = np.fromstring(stream.getvalue(), dtype=np.uint8).tolist()
        data = {'telemetry': sensors, 'img': img_ls}
        socket.send_json(data)
        stream.seek(0)
        stream.truncate()

with picamera.PiCamera() as camera:
        camera.resolution = (320, 240)
        camera.framerate = 80
        time.sleep(2)
        camera.capture_sequence(outputs(), 'jpeg', use_video_port=True)

客户端侧

ip_server = "192.168.42.1"
context = zmq.Context()
zmq_socket = context.socket(zmq.SUB)
zmq_socket.setsockopt(zmq.SUBSCRIBE, b'')
zmq_socket.setsockopt(zmq.CONFLATE, 1)
zmq_socket.connect("tcp://{}:5557".format(ip_server))

try:
    img_nbr = 1
    while True:
        start = time.time()
        frames = zmq_socket.recv_json()
        img_ls = frames['img']
        telemetry = frames['telemetry']
        #convert img list to array
        img_arr = np.asarray(img_ls)
        #reshape gives error because 320*240 != len(img_ls)
        image = np.reshape(img_ls, (320, 240))
        #save image file locally 
        image = Image.fromarray(image)
        #timestamp in ms
        timestamp = int(time.time() * 1000 )
        image.save('img_'+str(timestamp)+'.jpg')
        print('Frame number: ', str(img_nbr))
        img_nbr += 1
finally:
    pass

最后注意事项:这是我尝试将图像和传感器数据从RPi同步传输到客户端。我担心阵列和列表转换(在RPi端完成)可能会减慢流的速度。如果有更好的方法(仍然)使用zmq,请告诉我。

2 个答案:

答案 0 :(得分:2)

图像处理是CPU昂贵的。所以,性能第一:

ZeroMQ应该允许人们享受零拷贝操作方式,因此可以防止任何不利操作,这会破坏它。

仅使用通用的OpenCV相机,而不是RPi / PiCamera,我总是更喜欢在受控事件循环下在采集端采用单独的相机帧(不是序列)。

相机获得一个已知的固定几何图片(在OpenCV中numpy.ndarray 3D结构[X,Y,[B,G,R]]),因此最快和最直接的序列化使用了发送方struct.pack( CONST_FRAME_STRUCT_MASK, aFrame )和接收方struct.unpack( CONST_FRAME_STRUCT_MASK, aMessage )。{/ p>

是的,struct.pack()是迄今为止最快的方式,即使文档提供了其他方法(灵活性需要额外的费用,这是不合理的):

import numpy

def send_array( socket, A, flags = 0, copy = True, track = False ):
    """send a numpy array with metadata"""
    md = dict( dtype = str( A.dtype ),
               shape =      A.shape,
               )
    pass;  socket.send_json( md, flags | zmq.SNDMORE )
    return socket.send(      A,  flags, copy = copy, track = track )

def recv_array( socket, flags = 0, copy = True, track = False ):
    """recv a numpy array"""
    md = socket.recv_json( flags = flags )
    msg = socket.recv(     flags = flags, copy = copy, track = track )
    buf = buffer( msg )
    pass;  A = numpy.frombuffer( buf, dtype = md['dtype'] )
    return A.reshape(                         md['shape'] )

任何颜色转换和类似的源侧转换都可能消耗+150~180 [ms],因此请尽量避免任何和所有不必要的色彩空间或重塑或类似的非核心转换,因为这些会不利地增加积累的管道延迟包络。

使用struct.pack()也可以避免任何类型的大小不匹配,因此加载到二进制有效负载着陆区的内容正是您在接收方侧收到的内容。

如果确实希望在消息核心数据周围也有一个与JSON相关的开销,那么就设置一个双插槽范例,它们都有ZMQ_CONFLATE == 1,第一个移动struct - 有效载荷和第二次JSON装饰的遥测。

如果RPi允许,zmq.Context( nIOthreads )可以通过nIOthreads >= 2进一步提高双方的数据抽取吞吐量,并且额外的JSON_socket.setsockopt( ZMQ_AFFINITY, 1 ); VIDEO_socket.setsockopt( ZMQ_AFFINITY, 0 )映射可以分离/分配工作负载不同的,单独的IOthread

答案 1 :(得分:0)

查看下面的代码。我使用了Nlohmann json和一些细微的调整(来自各种来源)来发送图像,修饰符,字符串等。

客户代码

#include <zmq.hpp> 
#include <string>
#include <iostream>
#include <sstream>

#include <nlohmann/json.hpp> 
#include <opencv2/opencv.hpp>
#include "opencv2/imgproc/imgproc_c.h"
#include "opencv2/imgproc/imgproc.hpp"
#include <typeinfo>
using json = nlohmann::json;


class image_test
{
  public:
    void client1(){
      zmq::context_t context (1);
      zmq::socket_t socket (context, ZMQ_REQ);
      socket.connect ("tcp://localhost:5555");

      while (true){
        // create an empty structure (null)
        json j;
        std::string data;
        float f = 3.12;
        cv::Mat mat = cv::imread("cat.jpg",CV_LOAD_IMAGE_COLOR);

        // std::cout<<Imgdata;

        std::vector<uchar> array;
        if (mat.isContinuous()) 
          {
            array.assign(mat.datastart, mat.dataend);
          } 

        else 
          {
            for (int i = 0; i < mat.rows; ++i) 
              {
                  array.insert(array.end(), mat.ptr<uchar>(i), mat.ptr<uchar>(i)+mat.cols);
               }
          }

        std::vector<uint> v = {1,5,9};

        j["Type"] = f;
        j["vec"] = v;
        j["Image"]["rows"] = mat.rows;
        j["Image"]["cols"] = mat.cols;
        j["Image"]["channels"] = mat.channels();
        j["Image"]["data"] = array;

        // add a Boolean that is stored as bool
        j["Parameter"] = "Frequency";

        // add a string that is stored as std::string
        j["Value"] = "5.17e9";


        // explicit conversion to string
        std::string s = j.dump();  


        zmq::message_t request (s.size());
        memcpy (request.data (), (s.c_str()), (s.size()));
        socket.send(request);

        zmq::message_t reply;
        socket.recv (&reply);
        std::string rpl = std::string(static_cast<char*>(reply.data()), reply.size());

        json second = json::parse(rpl);

        std::cout << second["num"] << std::endl;


      }
    }         
};


int main (void)
{

  image_test caller;
  caller.client1();
}

服务器代码

import zmq
import json
import numpy as np
import matplotlib.pyplot as plt
import cv2

context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")

while True:

    json_str = socket.recv()

    data_print = json.loads(json_str)

    img = np.array(data_print["Image"]["data"])
    img = img.reshape(data_print["Image"]["rows"],data_print["Image"]["cols"], data_print["Image"]["channels"])

    b,g,r = cv2.split(img)
    img = cv2.merge((r,g,b))

    print(img.shape)
    # plt.imshow(img)
    # plt.show()

    Type = data_print['Type']
    Parameter = data_print['Parameter']
    Value = data_print['Value']

    a = {"info": "hello", "num":1}
socket.send(json.dumps(a))

应该包含来自nlohmann git的include软件包。或者,您可以直接从https://github.com/zsfVishnu/zmq.git下载源代码和链接。 此外,如果您使用的是g ++或其他任何不包含nlohmann的include文件夹的编译器,只需在CLI上进行指定即可,即添加-I / path-to-the-include-folder /