LED-11. Make a very big size 384 X 512 RGB Matrix #4

 A network must be used to exchange images between the LED server and the player. A method for transmitting an image over a network is possible in several ways. In most cases, in order to reduce bandwidth, data is compressed and transmitted, and the receiver undergoes a process of decoding again. However, since we will be sending images on a local LAN, we will prioritize speed over bandwidth. Therefore, in this article, we will send and receive images without decompression. For image transmission using a network, refer to the next article.

 

 Network display

 First, let's test the network image transfer using a simple Python program.

Test

 The following is a server-side Python program. We will read the image and send it to the player.

#!/usr/bin/python3
import argparse
import numpy as np
import cv2
import time
from PIL import Image, ImageSequence
import netsender2 as netsender
import socket

canvas_w = 384
canvas_h = 512
parser = argparse.ArgumentParser(description='gif run')
parser.add_argument('--file', type=str, required=True, help='image file name')
args = parser.parse_args()

netsender.set_recv_pi([("192.168.11.11", 4321), ("192.168.11.12", 4321), ("192.168.11.13", 4321), ("192.168.11.14", 4321)])

img = cv2.imread(args.file, cv2.IMREAD_UNCHANGED)
img = cv2.resize(img, (canvas_w, canvas_h))
h, w, c = img.shape
if c == 4:
    img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)

sleep_sec = 0.05
count = 0
total_sec = 0.0
while True:
    try:
        s = time.time()
        netsender.net_send(img)
        time.sleep(sleep_sec)
        count += 1
        total_sec += (time.time() - s)
    except KeyboardInterrupt:
        break
    except socket.timeout:
        continue

FPS = count / (total_sec)        
print('FPS:%4.2f'%(FPS))

 <server side image sender program : test_image2.py>


And this is a program on the player side. The same program will run on 4 players.

#!/usr/bin/python3
import numpy as np
import cv2
import socket
import struct
import time
import argparse
from threading import Thread

from rgbmatrix import RGBMatrix, RGBMatrixOptions
from PIL import Image, ImageDraw, ImageFont

CHUNK_SIZE = 8192 + 32
CHUNK_SIZE = 52000

H = 128
W = 384
C = 3

VSwap = False


parser = argparse.ArgumentParser(description='net run')
parser.add_argument('--port', type=int, default=4321, help='port number')
parser.add_argument('--cols', type=int, default=128, help='cols')
parser.add_argument('--rows', type=int, default=64, help='rows')
parser.add_argument('--chain_length', type=int, default=2, help='chain_length')
parser.add_argument('--parallel', type=int, default=3, help='parallel')
args = parser.parse_args()

VMapper = True


options = RGBMatrixOptions()
options.cols = args.cols
options.rows = args.rows
options.chain_length = args.chain_length
options.parallel = args.parallel
options.gpio_slowdown = 4
options.show_refresh_rate = 0
options.hardware_mapping = 'regular'  # I'm using Electrodragon HAT

#options.pixel_mapper_config = "Rotate:180"
options.pwm_bits = 7
options.pwm_dither_bits = 1
if VMapper == True:
    options.pixel_mapper_config = "V-mapper"


matrix = RGBMatrix(options=options)
double_buffer = matrix.CreateFrameCanvas()


sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", args.port))
bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
if CHUNK_SIZE < bufsize:
    CHUNK_SIZE = bufsize

print('Rcv buf size:%d' % (CHUNK_SIZE))
sock.settimeout(60)
total = 0
buf = []
packet_cnt = 0


def reset():
    global buf, total, packet_cnt
    total = 0
    buf = []
    packet_cnt = 0
    time.sleep(0.001)

while(True):
    try:
        data, addr = sock.recvfrom(CHUNK_SIZE)
        total += len(data)
        key = int.from_bytes(data[:4], byteorder="big")
        seq = int.from_bytes(data[4:8], byteorder="big")
        cnt = int.from_bytes(data[8:12], byteorder="big")
        buf += data[12:]
        packet_cnt += 1

        if key == 1:  # last
            # if(packet_cnt != cnt):
            #     print('Total rcv cnt:%d total chunk:%d'%(packet_cnt, cnt))
            #     reset()
            #     continue
            try:
                img = np.asarray(buf, dtype=np.uint8)
                img = img.reshape(H, W, C)
                final = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
                h, w, c = final.shape
                im_pil = Image.fromarray(final)
                #print('Reshape success')

                double_buffer.SetImage(im_pil)
                VSwap = True 
                double_buffer = matrix.SwapOnVSync(double_buffer)

                reset()
            except ValueError:
                reset()
                print('Reshape Error')

    except KeyboardInterrupt:
        break
    except socket.timeout:
        reset()
        continue

 <player side image receiver program : net_player2.py>

 

 First run the player programs.

root@LEDOne:/usr/local/src/test# python3 net_player2.py 
Rcv buf size:5242880

 

root@LEDTwo:/usr/local/src/test# python3 net_player2.py 
Rcv buf size:5242880

 

root@LEDThree:/usr/local/src/test# python3 net_player2.py 
Rcv buf size:5242880

 

root@LEDFour:/usr/local/src/test# python3 net_player2.py 
Rcv buf size:5242880

 

 Now run the server program with sleep_sec = 0.05.

root@LEDSrv:/usr/local/src/test# python3 test_image2.py --file=totoro.jpg 
Snd buf size:5242880
network image receiver set to:[('192.168.11.11', 4321), ('192.168.11.12', 4321), ('192.168.11.13', 4321), ('192.168.11.14', 4321)]
^CFPS:15.76

 After running the program for a while, press Ctrl+C to check the FPS value. At sleep_sec = 0.05, about 15.40 FPS was obtained. 


 In this setting environment, it was displayed without any problem.

 

 This time, I will lower the sleep_sec value to 0.02 to increase the FPS. However, there are some problems on the player side.


 After running the program for a while, press Ctrl+C to check the FPS value. At sleep_sec = 0.02, about 30.56 FPS was obtained. 

The "Reshape Error" message sometimes appears in the player output window.

This message appears when image packets sent from the server side are not processed normally.

The reshape error doesn't happen for every image frame, but you can monitor it for occasional occurrences. The reshape error is that the image frame received through udp communication is not normal. 

The player program uses the SwapOnVSync function to update the LED display image after combining packets sent from the server. 

This series of tasks must end faster than the time interval at which images are sent from the server. If the player's processing time slows down, packet loss occurs, and image frames cannot be made normally. In this case, a 'Reshape error' occurs.

It was almost impossible to reliably implement 30 FPS in a player using Python.

The biggest reason of "Reshape error" is that this program is made of Python. Originally, the rpi-rgb-led-matrix library was created for C/C++. Python bindings were then added. Thus, if the program is re-created using the rpi-rgb-led-matrix library for C/C++, the processing speed can be much higher.



Tunning

 Several ways are available to speed up the player's processing and operate reliably.


Raspberry Pi Overclocking and reserver CPU core

By disabling one of the four CPUs in the Raspberry Pi by the Linux kernel, LED applications can use more CPU resources. If raspberry pi is used exclusively for LED players, this is a very good method. Henner Zeller also highlights this approach on the https://github.com/hzeller/rpi-rgb-led-matrix page.

Add the following text to the end of the /boot/cmdline.txt file. Note that the line must not be changed.

isolcpus=3

 And Henner Zeller explains several options on this page to improve performance. I definitely recommend reading it.

 

 You can overclock the player using the dietpi-config command.

 



Porting Python Code to C/C++

 The C++ codes are not released in this article due to the large amount. But you can download them from my Github. The operation of the C++ program is exactly the same as the Python above.


Reduce electrical noise

Both the LED matrix and the Raspberry Pi use 5V power. Thus, a single 5V power rail can be used.

But don't organize it like this. The reason is that the LED matrix uses a large amount of power, which makes the power of the raspberry pi very unstable if the same power source is used by the LED and raspberry pi. When a voltage drop occurs suddenly, the raspberry pi becomes momentarily unstable. In severe cases, system downtime may occur.

Therefore, a separate 5V power source is prepared for raspberry pi.

And to reduce electrical noise, it is also good to twist +,- cables. And where there is a lot of electrical noise, I add electromagnetic shielding devices.


Wrapping up

In this article, I tested whether it works using 1 LED server and 4 players. The program required for testing is implemented using Python. However, there was a performance issue that did not work properly at 30FPS. Although it is possible to increase the hardware performance to some extent by using methods such as Raspberry Pi CPU control and overclocking, it cannot be a fundamental solution. The way to dramatically improve performance is to port a player implemented in Python to C/C++. When implemented in C/C++, performance improvement can be expected from several times to several tens of times.

The LED server will not be ported to C/C++. The biggest reason is that the LED server is a program that does not directly control the LED, so it is not necessary to use the Raspberry Pi. You can also use a high-performance x86 system.

 In the next article, we will look at porting the LED player program to C/C++.

 

 

 

 

댓글

이 블로그의 인기 게시물

LED-12. Displaying HDMI Contents

LED - 5. Raspberry Pi 4 + DietPi Buster + Electrodragon HAT - Part 1

LED-11. Make a very big size 384 X 512 RGB Matrix #6(final)