【问题标题】:How to send image and data string using serialization in ZMQ?如何在 ZMQ 中使用序列化发送图像和数据字符串?
【发布时间】:2017-07-11 17:27:52
【问题描述】:

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

服务器端

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,请告诉我。

【问题讨论】:

    标签: python zeromq


    【解决方案1】:

    图像处理消耗大量 CPU。所以,性能优先:

    ZeroMQ 应该允许人们享受零拷贝的作案手法,所以要防止任何破坏它的不利操作。

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

    相机获取已知的固定几何图形(在 OpenCV 中为 numpy.ndarray 3D-structure [X,Y,[B,G,R]]),因此最快和最直接的序列化是使用 @987654322 @ 在发送方,struct.unpack( CONST_FRAME_STRUCT_MASK, aMessage ) 在接收方。

    是的,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-payloads 和第二个 JSON-装饰遥测。

    如果 RPi 允许,zmq.Context( nIOthreads ) 可以使用nIOthreads >= 2 进一步增加双方的数据泵吞吐量,并且额外的JSON_socket.setsockopt( ZMQ_AFFINITY, 1 ); VIDEO_socket.setsockopt( ZMQ_AFFINITY, 0 ) 映射可以分离/分配工作负载以将每个工作负载骑在不同的、单独的IOthread 上.

    【讨论】:

    • 感谢您的回答。我不知道如何使用struct_pack (struct_unpack) 实现序列化,它在代码中。我修改了“服务器端”的初始代码以生成 np 数组 - 将 stream = io.BytesIO() 替换为 stream = PiRGBArray(camera, size=resolution)def output()def outputs(camera, resolution);和camera.capture_sequence(outputs(), 'jpeg', use_video_port=True) by camera.capture_sequence(outputs(camera, resolution), 'rgb', use_video_port=True). BTW, I use 'capture_sequence" rather than (single shot)capture`,因为fps更高。
    【解决方案2】:

    查看下面的代码。我使用了 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 的包含包。或者直接从https://github.com/zsfVishnu/zmq.git下载源代码和链接。 另外,如果您使用 g++ 或任何其他不包含来自 nlohmann 的包含文件夹的编译器,只需在 CLI 中指定,即添加 -I/path-to-the-include-folder/

    【讨论】:

      猜你喜欢
      • 2019-04-02
      • 1970-01-01
      • 2016-08-22
      • 2020-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-12
      • 1970-01-01
      相关资源
      最近更新 更多