pythonimagesubprocessframebufferfbi

Display NumPy array as an image using fbi on Ubuntu


I would like to display a NumPy array as an image on the screen from a Python script. I need to do this without starting X, so I can't use OpenCV, PIL, etc.

Is it possible to do this using fbi in a subprocess started in the Python script?

Below is what I have tried to do. I expected to see the images in the 'images' folder displayed with a 1-second delay between them, but instead I get the error BrokenPipeError: [Errno 32] Broken Pipe

import os
import time
import numpy as np
from PIL import Image
from subprocess import Popen, PIPE

# Define the folder containing the images
folder_path = 'images'

# Loop through each file in the folder
for filename in os.listdir(folder_path):
    # Check if the file is an image
    if filename.endswith('.jpg') or filename.endswith('.jpeg') or filename.endswith('.png'):
        # Load the image into a NumPy array using PIL
        image = np.array(Image.open(os.path.join(folder_path, filename)))
        
        # Edit the image as desired 
        edited_image = image
        
        # Build the fbi command to display the edited image in full-screen mode
        command = 'sudo fbi -T 1 -noverbose -a -t 1 -u -blend 500 - '
        
        # Create a new process for fbi, and pass the edited image to its standard input
        with Popen(command.split(), stdin=PIPE) as process:
            process.stdin.write(Image.fromarray(edited_image).tobytes())
            process.communicate()  # Wait for fbi to complete
        
        # Wait for 1 second before displaying the next image
        time.sleep(1)

Thanks in advance!


Solution

  • I am still confused by your question, but now I think you are generating Numpy arrays rather than images and want to send them to the frame buffer from Python - maybe on a Raspberry Pi?

    Here is an example of that:

    #!/usr/bin/env python3
    
    import numpy as np
    
    def RGB888toRGB565(rgb888):
        """Convert RGB888 input array to RGB565 output array"""
        # Create 16-bit output image, same height and width as input
        rgb565 = np.zeros(rgb888.shape[:2], np.uint16)
        rgb565[:]  = (rgb888[:,:,0] & 0x1f).astype(np.uint16) << 11 
        rgb565[:] |= (rgb888[:,:,1] & 0x3f).astype(np.uint16) << 5
        rgb565[:] |= (rgb888[:,:,2] & 0x1f).astype(np.uint16)
        return rgb565
    
    # Get the framebuffer height, width and pixel format (e.g. RGB565) by running "fbset" command
    # $ fbset
    # mode "1024x600"
    #    geometry 1024 600 1024 600 16
    #    timings 0 0 0 0 0 0 0
    #    accel true
    #    rgba 5/11,6/5,5/0,0/0
    # endmode
    #
    w, h = 1024, 600
    
    # Create a pure black RGB888 image same size as framebuffer
    # then a red, green and blue one
    blk = RGB888toRGB565( np.zeros((h,w,3), np.uint8) )
    red = RGB888toRGB565( np.full((h,w,3), [255,0,0], np.uint8) )
    grn = RGB888toRGB565( np.full((h,w,3), [0,255,0], np.uint8) )
    blu = RGB888toRGB565( np.full((h,w,3), [0,0,255], np.uint8) )
    
    # Open the framebuffer
    with open('/dev/fb0', 'wb') as fb:
        fb.seek(0)
        fb.write(blk.tobytes())  # send black frame
        time.sleep(0.5)
        fb.seek(0)
        fb.write(red.tobytes())  # send red frame
        time.sleep(0.5)
        fb.seek(0)
        fb.write(grn.tobytes())  # send green frame
        time.sleep(0.5)
        fb.seek(0)
        fb.write(blu.tobytes())  # send blue frame
        time.sleep(0.5)
    

    Obviously the code can be refactored to be tidier, but I wanted to just demonstrate the techniques rather than confuse anyone with super-compact, efficient, non-repetitive tricky code.