通过套接字

时间:2018-04-11 21:12:29

标签: python opencv raspberry-pi

Adrian's guide和其他一些人的第6步之后,我设法从我的覆盆子pi到我的笔记本电脑以10 fps的速度和0.1秒的延迟流式传输320x240帧。问题是,当我在我的实验室(配备古董路由器)中测试这个系统时,它只能以1-1.5秒的延迟流式传输1-2 fps,这对于我打算用这些来做的事情是完全不可接受的。帧。

现在,我的方法很简单直接:覆盆子pi上的服务器捕获一个帧并将其存储为320x240x3矩阵,如上面提到的指南,然后腌制该矩阵并继续将其泵送到TCP套接字上。笔记本电脑上的客户端继续接收这些帧,对它们进行一些处理,最后用imshow显示结果。我的代码相当长(约200行),所以如果可以,我宁愿避免显示它。

有没有办法减少每帧数据的大小(腌制的320x240x3矩阵,其长度为230 kB)还是有更好的方法来传输数据?

修改

好的人,精确呈现的酸洗阵列长度为230563字节,有效载荷数据应至少为230400字节,因此开销不应超过总包大小的0.07%。我认为这将问题缩小到无线连接质量和将数据编码为字节的方法(酸洗似乎很慢)。无线问题可以通过创建ad-hoc网络来解决(听起来很有趣,但我还没有尝试过)或者只是购买更好的路由器,编码问题可以通过Aaron的解决方案来解决。希望这将有助于未来的读者:)

3 个答案:

答案 0 :(得分:3)

tl; dr struct实际上很慢..而不是pickle使用np.ndarray.tobytes()结合np.frombuffer()来消除开销。

我不熟悉opencv,这可能是最好的答案,但加速转移的简单方法可能是使用struct来打包和解包数据通过网络而不是pickle发送。

以下是使用numpy

在套接字上发送struct已知维度数组的示例
import numpy as np
import socket
import struct

#----- server ------
conn = socket.socket()
#connect socket somewhere
arr = np.random.randint(0,256,(320,240,3), dtype="B") # unsigned bytes "B": camera likely returns 0-255 pixel values
conn.write(struct.pack('230400B', *arr.flat)) #230400 unsigned bytes

#----- client ------
conn = socket.socket()
#connect socket somewhere
data = conn.read(230400) #read 230400 bytes
arr = np.array(struct.unpack('230400B', data), dtype='B').reshape((320,240,3),)

修改

有点挖掘表明numpy有一个tobytes函数,它将数据的内存视图公开为bytes对象。这基本上完成了struct的工作,而不需要在函数调用中解压缩编码。这促使我也看看我们是否也可以打开包装,只要你能在裤子的座位上飞一点(中断或错误不会被优雅地抓住),我们就可以打包以几乎零开销解压缩数据,这是网络的唯一限制因素。

测试脚本:

arr = np.random.randint(0,256,(320,240,3), dtype="B") # unsigned bytes "B": camera likely returns 0-255 pixel values

t = time()
for _ in range(100):
    arr2 = pickle.loads(pickle.dumps(arr))
print(f'pickle pack, pickle unpack: {time()-t} sec')

t = time()
for _ in range(100):
    arr2 = np.array(struct.unpack('230400B', struct.pack('230400B', *arr.flat)), dtype='B').reshape((320,240,3),)
print(f'struct pack, struct unpack: {time()-t} sec')

t = time()
for _ in range(100):
    arr2 = np.array(struct.unpack('230400B', arr.tobytes()), dtype='B').reshape((320,240,3),)
print(f'numpy pack, struct unpack: {time()-t} sec')

t = time()
for _ in range(100):
    arr2 = np.frombuffer(arr.tobytes(), dtype="B").reshape((320,240,3),)
print(f'numpy pack, numpy unpack: {time()-t} sec')

打印:

pickle pack, pickle unpack: 0.005013704299926758 sec
struct pack, struct unpack: 3.558577299118042 sec
numpy pack, struct unpack: 1.2988512516021729 sec
numpy pack, numpy unpack: 0.0010025501251220703 sec

答案 1 :(得分:1)

由于良好的通信链路通信速度和延迟良好,您可能已经受到Raspberry在帧速率方面的性能的限制。通过在单独的线程中运行图像采集和通信,您可能会获得一点(如果您还没有这样做)(在Python中,可能需要单独的进程来避免GIL)。

当然,避免任何不必要的开销,例如Aaron在他的回答中描述的内容,特别是当它很容易做到时,是值得的。

我甚至考虑使用一些轻量级压缩。 Python提供了zlib,您可以根据CPU的使用情况调整交易压缩率。还有bzip2以及Python 3.3 lzma,尽管这些更加占用CPU。您还可以获得lz4snappy等与频谱相反的内容的绑定。

另一种方法是使用cv2.imencode(和另一端的cv2.imdecode)使用一些压缩格式。由于您将处理图像,因此无损编解码器似乎是合适的,因此PNG可能是一个不错的选择(它基本上是zlib并带有一些简单的预测器)。

一如往常,只要性能至关重要,请对各种方法进行基准测试,以便找出最适合您的方法。

如果您确实拥有320x240分辨率的相机,我建议传输原始的拜耳CFA数据(通常彩色相机是带有特殊滤色镜的单色阵列)。这意味着与RGB格式相比,传输1/3的数据(并且您在另一端进行去马赛克)。然而,由于这是500万像素相机已经缩小的图像,因此在这里不会有所帮助。

然而,关于你提到的问题的关键问题是:

  • 连接是通过WiFi

  • 周围还有10个其他WiFi网络
  • 您使用的WiFi路由器很古老(很可能只有2.4 GHz)

即使以最佳速度,您每秒传输的速度也只有3兆字节。那不是那么多,所以我会考虑以某种方式改善连接。有些工具可以让您扫描所有相邻网络。使用其中一个来找到最不拥挤的通道(请记住重叠)。也许你只是转换到一个更好的渠道将改善这种情况。

如果可能的话,你可能想要使用5GHz,因为这往往更不拥挤。然而古代路由器可能会阻止这种情况您可以考虑在两台计算机之间创建一个ad-hoc网络,这基本上是一个不涉及任何中间路由器的直接连接。

否则,请考虑投资更好的路由器,并确保获得可靠的通信链接。如果链接是您控件循环的一部分,我认为没有其他选择。

编辑:哦,也许是固定机器上的定向天线?

答案 2 :(得分:1)

我一直在研究通过无线网络将图像从RaspberryPi传输到Mac的所有选项 - 到Mac,但接收器可能是任何东西。

我决定以YUV420p格式传输数据,因为RGB数据所需的网络带宽最少 - HALF 。我尝试禁用降噪增加ISO和我想象的一切,让它更快,并用我从亚马逊以12英镑(15美元)和sigrok逻辑分析仪得到的非常酷的小逻辑分析仪分析整个事情软件

以下是我如何获得每秒36帧的320x240像素:

#!/usr/bin/python3
import io
import socket
import struct
import time
import picamera

import RPi.GPIO as GPIO

# Enable GPIO pins as output for Logic Analyser probe-based debugging
GPIO.setmode(GPIO.BCM)
GPIO.setup(20, GPIO.OUT)
GPIO.setup(21, GPIO.OUT)
GPIO.setup(22, GPIO.OUT)
GPIO.setup(23, GPIO.OUT)

WIDTH=320
HEIGHT=240
BUFFERSIZE=WIDTH*HEIGHT*3//2
PORT=8000
SERVER='192.168.0.8'
client_socket = socket.socket()
client_socket.connect((SERVER, PORT))
connection = client_socket.makefile('wb')
try:
    with picamera.PiCamera() as camera:
        camera.resolution = (WIDTH, HEIGHT)
        camera.framerate = 60
        time.sleep(2)
        start = time.time()
        count = 0
        stream = io.BytesIO()
        # Use the video-port for captures...
        while True:
            GPIO.output(20,GPIO.HIGH)
            frame=next(camera.capture_continuous(stream, format="yuv", use_video_port=True))
            GPIO.output(20,GPIO.LOW)
            stream.seek(0)
            GPIO.output(22,GPIO.HIGH)
            connection.write(stream.read())
            GPIO.output(22,GPIO.LOW)
            count += 1
            if time.time() - start > 15:
                break
            stream.seek(0)
            stream.truncate()
    #connection.write(struct.pack('<L', 0))
finally:
    connection.close()
    client_socket.close()
    finish = time.time()
print('Sent %d images in %d seconds at %.2ffps' % (
    count, finish-start, count / (finish-start)))
GPIO.cleanup() 

在Mac上,作为服务器运行并显示:

#!/usr/local/bin/python3
import io
import socket
import struct
import numpy as np
import cv2

WIDTH=320
HEIGHT=240
PORT=8000
BUFFERSIZE=WIDTH*HEIGHT*3//2
print("Running on port {}: width={}, height={}".format(PORT,WIDTH,HEIGHT))

# Start a socket listening for connections on all interfaces
server_socket = socket.socket()
server_socket.bind(('0.0.0.0', PORT))
server_socket.listen(0)

frame=0

# Accept a single connection and make a file-like object out of it
connection = server_socket.accept()[0].makefile('rb')
try:
    while True:
        # Construct a stream and fill with image data from network
        image_stream = io.BytesIO()
        image_stream.write(connection.read(BUFFERSIZE))
        # Rewind the stream, and process
        image_stream.seek(0)
        img_str=image_stream.read()
        nparr=np.fromstring(img_str,np.uint8)
        # Data layout is here https://en.wikipedia.org/wiki/YUV#/media/File:Yuv420.svg
        Y=nparr[0:WIDTH*HEIGHT].reshape(HEIGHT,WIDTH)
        # ssU = sub-sampled U channel, ssV = sub-sampled V channel
        ssU=nparr[WIDTH*HEIGHT:WIDTH*HEIGHT*5//4].reshape(HEIGHT//2,WIDTH//2)
        ssV=nparr[WIDTH*HEIGHT*5//4:].reshape(HEIGHT//2,WIDTH//2)
        # Up-sample the U and V channels to full size
        U=cv2.resize(ssU,(WIDTH,HEIGHT),cv2.INTER_LINEAR)
        V=cv2.resize(ssV,(WIDTH,HEIGHT),cv2.INTER_LINEAR)
        # Combine the YUV into single image and convert to BGR
        oc_YUV=cv2.merge((Y,U,V))
        oc_BGR=cv2.cvtColor(oc_YUV,cv2.COLOR_YUV2BGR)
        # That's actually it, but I upscale for better viewing on screen
        tmp=cv2.resize(oc_BGR,(WIDTH*2,HEIGHT*2),cv2.INTER_LINEAR)
        cv2.imshow('image',tmp)
        cv2.waitKey(1)
        frame=frame+1
finally:
    connection.close()
    server_socket.close()

这是一个关于它运行的小视频。它的响应速度非常快,而且只是因为我将屏幕捕获重新采样到2fps以使其低于StackOverflow 2MB限制这一事实。[/ p>

您可以安全地忽略代码中所有与GPIO相关的东西 - 只需在逻辑分析仪上摆动高低引脚,这样我就可以准确地计时。

enter image description here

YUV数据的布局如下:

enter image description here

  

由Xburge03的原始位图版本,Qef的SVG版本。 - http://en.wikipedia.org/wiki/Image:Yuv420.png的矢量化版本 - 使用谈话页面上提供的程序创建。,Public Domain,https://en.wikipedia.org/w/index.php?curid=18105371

鉴于您的问题似乎与网络带宽有关,我的方法基于发送YUV数据,如上所述。我还看了Dan关于压缩数据的建议,虽然带宽减少显然取决于你的照明和你的主题,但YUV数据确实有些lz4压缩。我看到网络带宽进一步减少了25%左右,延迟没有明显增加。代码如下所示:

import lz4.frame
...
compressed=lz4.frame.compress(data,compression_level=lz4.frame.COMPRESSIONLEVEL_MINHC))