首页 > 解决方案 > 如何在 Python 中通过 ZeroMQ PUB/SUB 从 Raspberry Pi 接收图像?

问题描述

我想将 Picamera 在 Raspberry Pi 上拍摄的图像发送到我的 windows 计算机。

我写了一些代码,如下(它们在这里被简化了),但是它卡在
frame = footage_socket.recv_string()client.py.

我没有收到任何错误,但它总是卡在代码中,就像它冻结一样,无法转到下一行。工作正常并连续server.py打印。'test'如果您查看jpg_as_text,您可以看到编码文本。

server.py

import picamera
import socket
import threading
import zmq
import cv2
import base64
from picamera.array import PiRGBArray

if __name__ == "__main__":

    addr = 'ip_address'

    camera = picamera.PiCamera()                 # Camera initialization
    camera.resolution = (640, 480)
    camera.framerate = 7
    rawCapture = PiRGBArray(camera, size=(640, 480))

    # FPV initialization
    context = zmq.Context()
    footage_socket = context.socket(zmq.PUB)
    footage_socket.connect('tcp://%s:5555'%addr)
    print(addr)

    font = cv2.FONT_HERSHEY_SIMPLEX
    for frame in camera.capture_continuous( rawCapture,
                                            format         = "bgr",
                                            use_video_port = True ):
        image = frame.array
        print('test')
        image = cv2.resize(image, (640, 480))    # resize the frame
        encoded, buffer = cv2.imencode('.jpg', image)
        jpg_as_text = base64.b64encode(buffer)
        footage_socket.send(jpg_as_text)
        rawCapture.truncate(0)

client.py

from socket import *
import sys
import time
import threading as thread
import tkinter as tk
import math
import os
import cv2
import zmq
import base64
import numpy as np


if __name__ == "__main__":

    context = zmq.Context()
    footage_socket = context.socket(zmq.SUB)
    footage_socket.bind('tcp://*:5555')
    footage_socket.setsockopt_string(zmq.SUBSCRIBE, np.unicode(''))

    font = cv2.FONT_HERSHEY_SIMPLEX

    while 100:
        try:
            frame = footage_socket.recv_string() # This line of code is the problem.

            print('next successfuly connected')
            img = base64.b64decode(frame)
            npimg = np.frombuffer(img, dtype=np.uint8)
            source = cv2.imdecode(npimg, 1)
            cv2.imshow("Stream", source)
            cv2.waitKey(1)

        except KeyboardInterrupt:
            break
        except:
            pass
    

标签: pythonsocketsraspberry-pipyzmqpicamera

解决方案


:如何在 Python 中通过 ZeroMQ 从树莓派接收图像?PUB/SUB

如果您从未使用过 ZeroMQ,您可以在这里先看看“ ZeroMQ原则在不到五秒的时间内”,然后再深入了解更多细节

观察:

没有错误。

您应该使用其他数据采集策略+设置一些自卫参数。

.recv_string()-方法以阻塞模式调用(它确实并且将永远阻塞代码执行,直到任何合理的符合规则成为可交付

使用zmq.NOBLOCKflag 可以避免这种阻塞模式 + 使用.poll()-method 可以帮助您设计私有事件驱动循环的逻辑,.recv( zmq.NOBLOCK )以防万一,确实有一些东西可以交付。

SUB-side 将一无所获,除非正确订阅以接收某些内容,默认状态 - 就像报纸一样 - 是一无所获,除非明确订阅 - 无论是朝日新闻还是其他标题 -。根据 API 记录的策略,订阅任何内容的最安全模式是使用 - 方法订阅零长度字符串.setsockopt( zmq.SUBSCRIBE, "" )

最后但并非最不重要的一点是,如果愿意进行 RPi-Win 流式传输,可能会有一个虎钳策略,因为除了最新的,入队/发布/传输/接收/出队没有任何价值frame,为此.setsockopt( zmq.CONFLATE, 1 )准备好了.

您可能需要对资源进行更多调整,以提高.Context( nIOthreads )-instance 性能、保留队列深度、L3 堆栈参数和许多进一步可能的增强功能。

始终.setsockopt( zmq.LINGER, 0 )为您设置永远不知道哪些版本将连接以及可能发生哪些默认设置,在这里,有机会让您崩溃的套接字实例永远挂起(通常直到操作系统重新启动),这似乎有点疯狂,未处理任何生产级软件的风险因素,不是吗?


解决方案提示:

  • 避免错过 unicode 约定的风险,这些约定在 Linux 端发起者和 Windows O/S 端之间是不同的,彼此不匹配。

+
由于 unicode 对象具有广泛的表示形式,因此它们不会根据其编码存储为字节,而是以一种称为 UCS(一种较旧的固定宽度 Unicode 格式)的格式存储。在某些平台(OS X、Windows)上,存储是 UCS-2,即每个字符 2 个字节。在大多数ix 系统上,它是 UCS-4,即每个字符 4 个字节。unicode 对象的缓冲区内容不依赖于编码(总是 UCS-2 或 UCS-4),但它们依赖于平台
...
+
这里的效率问题来自于简单的 ascii 字符串在内存中的大小是它们需要的 4 倍(在大多数 Linux 上,在其他平台上是 2 倍)。此外,转换为/从与 char 一起使用的 C 代码
,您总是必须复制数据并对字节进行编码/解码。从内存的角度来看,这确实是非常低效的。本质上,在内存效率对您很重要的地方,您永远不应该使用字符串;使用字节。问题是用户几乎总是使用str,并且在 2.x 中它们是有效的,但在 3.x 中它们不是。我们希望确保我们不会帮助用户犯这个错误,因此我们确保zmq方法不会试图隐藏字符串的真实含义。

  • 阅读有关 ZeroMQ 中延迟避免的更多信息,如果尝试流式传输常量和先验已知图像( 640 x 480 x <colordepth> )- 转换成本很高,将小规模、低分辨率、低 FPS RGB / IR 图片转换为 JPEG 文件格式仅用于传输是如果在 RPi 和 Win-device 之间使用本地 LAN 或专用 WLAN 网段,则毫无意义。以延迟为动机的设计可能会测试并可能避免任何类型的数据压缩,方法是使用cPickle.dumps()dill.dumps()在二进制块 BLOB 中尽可能紧凑地发送数据,通常足以使用aNumpyObject.data实用程序直接发送或使用/<read-write buffer for 0x7fa3cbe3f8a0, size 307200, offset 0 at 0x7f632bb2cc30>进行一些二进制处理-methods,如果需要超越可用的-access 技巧。所有鉴于struct.pack().unpack()numpy.data.setsockopt( zmq.CONFLATE, 1 )两边都被激活,以避免缓冲直播数据的深度过大。

  • 出于性能和延迟的原因,您可以避免使用这PUB/SUB对原型,因为 ZeroMQ API v3.+ 已将 TOPIC 过滤的工作负载转移到 - 侧PUB,这是您较弱的节点(而 RPi 有多个内核,您可以增强.Context( nIOthreads )类固醇上的 -instance,为 I/O 提供更多功率,但与 Windows 端的 localhost 相比,RPi 的频率仅为 GHz 的一小部分,并且机器人紧密的控制回路可能已经吃掉了大部分用于控制)。对于 1 对 1 拓扑,使用PUSH/PULL方式非常相似,而且由于在 RPi 端避免了处理,因此处理和 E2E 延迟开销也更少。

对于.poll()基于不同优先级的事件处理程序,以及关于 Margaret HAMILTON 夫人和她的 MIT 团队的开创性工作的一些评论,可能喜欢阅读this & this


推荐阅读