【问题标题】:Python: pixel manipulation pefrormance. Virtual desktop for embedded devicePython:像素操作性能。嵌入式设备的虚拟桌面
【发布时间】:2022-01-17 09:07:37
【问题描述】:

我正在寻找一种在 python 中进行像素操作的有效方法。 目标是制作一个充当嵌入式系统虚拟桌面的 python 脚本。 我已经有一个可用的版本,但显示单帧需要一秒钟以上(太长)。

每秒刷新显示 5 次会很棒。

它是如何工作的:

  1. 有一个带有微控制器和显示器(128x64px,黑白像素)的电子设备。
  2. 有一台 PC 通过 RS-485 连接到它。
  3. 微控制器中有一个数据缓冲区,代表每个像素。让我们称之为 diplay_buffer。
  4. PC 上的 Python 脚本从微控制器下载 diplay_buffer。
  5. Python 脚本根据来自 diplay_buffer 的数据创建图像。 (我需要优化)

diplay_buffer 是一个 1024 字节的数组。微控制器对其进行准备,然后在真实显示器上显示其内容。我需要使用 python 脚本在 PC 屏幕上显示真实显示的虚拟副本。

如何显示:

diplay_buffer 中的单个位表示单个像素。 显示器有 128x64 像素。 diplay_buffer 中的每个字节代表垂直方向的 8 个像素。前 128 个字节代表第一行像素(字节中有 64px / 8 个像素 = 8 行)。

我使用 python TK 和函数 img.put() 来插入像素。如果位为1,我插入黑色像素,如果位为0,则插入白色。这是非常无效的。 Meybe 有比 PhotoImage 不同的类,具有更好的像素能力?

我附上了带有示例 diplay_buffer 的最小代码。运行脚本时,您将看到帧和执行时间。

Meybe 会有人帮助尝试优化它吗? 请告诉我显示像素的更快方法吗?

丹德代尔

Sample frame downloaded from uC

还有代码(你可以轻松运行)


#this script displays value from uC display buffer in a python screen
from tkinter import Tk, Canvas, PhotoImage, mainloop
from math import sin
import time

WIDTH, HEIGHT = 128, 64
ROWS = 8

#some code from tutorial... check what it does:
window = Tk()
canvas = Canvas(window, width=WIDTH, height=HEIGHT, bg="#ffffff")
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image((WIDTH/2, HEIGHT/2), image=img, state="normal")


#this is sample screen from uC. It is normally periodically read from uC on runtime to refresh screen view. 
diplay_buffer =bytes([16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 130, 254, 130, 0, 0, 254, 32, 16, 8, 254, 0, 254, 144, 144, 144, 128, 0, 124, 130, 130, 130, 124, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 16, 16, 16, 16, 16, 0, 0, 0, 18, 42, 42, 42, 36, 0, 28, 34, 34, 34, 28, 0, 0, 16, 126, 144, 64, 0, 32, 32, 252, 34, 36, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 2, 130, 252, 128, 0, 4, 42, 42, 30, 2, 0, 62, 16, 32, 32, 30, 0, 0, 0, 0, 0, 0, 0, 0, 66, 254, 2, 0, 0, 130, 132, 136, 144, 224, 0, 0, 0, 0, 0, 0, 0, 78, 146, 146, 146, 98, 0, 124, 138, 146, 162, 124, 0, 78, 146, 146, 146, 98, 0, 78, 146, 146, 146, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 254, 16, 16, 16, 254, 0, 28, 42, 42, 42, 24, 0, 0, 130, 254, 2, 0, 0, 0, 130, 254, 2, 0, 0, 28, 34, 34, 34, 28, 0, 0, 0, 0, 0, 0, 0, 254, 144, 144, 144, 128, 0, 62, 16, 32, 32, 16, 0, 0, 34, 190, 2, 0, 0, 28, 42, 42, 42, 24, 0, 62, 16, 32, 32, 30, 0, 28, 34, 34, 20, 254, 0, 0, 0, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 124, 130, 130, 130, 68, 0, 4, 42, 42, 30, 2, 0, 62, 16, 32, 32, 30, 0, 0, 0, 0, 0, 0, 0, 50, 9, 9, 9, 62, 0, 28, 34, 34, 34, 28, 0, 60, 2, 2, 4, 62, 0, 0, 0, 0, 0, 0, 0, 28, 34, 34, 34, 28, 0, 63, 24, 36, 36, 24, 0, 32, 32, 252, 34, 36, 0, 0, 34, 190, 2, 0, 0, 62, 32, 30, 32, 30, 0, 0, 34, 190, 2, 0, 0, 34, 38, 42, 50, 34, 0, 28, 42, 42, 42, 24, 0, 64, 128, 154, 144, 96, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 146, 146, 146, 108, 0, 4, 42, 42, 30, 2, 0, 28, 34, 34, 34, 20, 0, 254, 8, 20, 34, 0, 0, 0, 0])


def get_normalized_bit(value, bit_index):
    return (value >> bit_index) & 1


time_start = time.time()
#first pixels are drawn invisible (some kind of frame in python) so set an offset:
x_offset = 2 
y_offset = 2
x=x_offset
y=y_offset

#display all uC pixels (single screen frame):
byteIndex=0
for j in range(ROWS): #multiple rows
    for i in range(WIDTH): #row
        for n in range(8): #byte
            if get_normalized_bit(diplay_buffer[byteIndex], 7-n):
                img.put("black", (x,y+n))
            else:
                img.put("white", (x,y+n))
        x+=1
        byteIndex+=1
    x=x_offset
    y+=7
time_stop = time.time()
print("Refresh time: ", str(time_stop - time_start), "seconds")    
    
mainloop()
 

【问题讨论】:

    标签: python image performance bit-manipulation pixel


    【解决方案1】:

    我并没有真正使用 Tkinter,但我读到使用 put() 将单个像素写入图像非常慢。因此,我修改了您的代码,将像素放入 Numpy 数组中,然后使用 PIL 将其转换为 PhotoImage

    在我的 Mac 上,将字节缓冲区转换为 PhotoImage 大约需要 1 毫秒。如果将三个 for 循环封装到 Numba-jitted 函数中,它可能会快 10-100 倍,但它似乎不值得,因为它可能已经足够快了。

    #!/usr/bin/env python3
    
    import numpy as np
    from tkinter import *
    from PIL import Image, ImageTk
    
    # INSERT YOUR variable display_buffer here <<<
    
    # Make a Numpy array of uint8, that will become
    # ... our PIL Image that will become... 
    # ... a PhotoImage
    WIDTH, HEIGHT, ROWS = 128, 64, 8
    na = np.zeros((HEIGHT,WIDTH), np.uint8)
    
    idx = 0
    x = y = 0
    for j in range(ROWS):
       for i in range(WIDTH):
          b = display_buffer[idx]
          for n in range(8):
             na[y+n, x] = (1 - ((b >> (7-n)) & 1)) * 255
          idx += 1
          x   += 1
       x  = 0
       y += 7
    
    # Make Numpy array into PIL Image
    PILImage = Image.fromarray(na)
    
    border = 10
    root = Tk()  
    canvas = Canvas(root, width = 2*border + WIDTH, height = 2*border + HEIGHT)  
    canvas.pack()  
    # Make PIL Image into PhotoImage
    img = ImageTk.PhotoImage(PILImage)
    canvas.create_image(border, border, anchor=NW, image=img) 
    root.mainloop() 
    

    另外,我不知道你的串行线有多快,但传输 1024 字节可能需要一些时间,所以你可以考虑启动第二个线程从你的串行重复读取 1024 字节并将它们填充到 @ 987654326@为主进程到get()他们从。


    此外,您可以完全避免使用 Tkinter,而只需像这样使用 OpenCV imshow()

    #!/usr/bin/env python3
    
    import numpy as np
    import cv2
    
    # INSERT YOUR display_buffer here <<<
    
    # Make a Numpy array of uint8, that will be displayed
    WIDTH, HEIGHT, ROWS = 128, 64, 8
    na = np.zeros((HEIGHT,WIDTH), np.uint8)
    
    idx = 0
    x = y = 0
    for j in range(ROWS):
       for i in range(WIDTH):
          b = display_buffer[idx]
          for n in range(8):
             na[y+n, x] = (1 - ((b >> (7-n)) & 1)) * 255
          idx += 1
          x   += 1
       x  = 0
       y += 7
    
    
    while True:
      # Display image
      cv2.imshow("Virtual Console", na)
    
      # Wait for user to press "q" to quit
      if cv2.waitKey(1) & 0xFF == ord('q'):
         break
    

    我决定尝试使用Numba,提取 128x64 帧的时间降至 68 微秒。请注意,Python 必须第一次编译,所以我进行了一次热身运行以包含编译,然后测量了第二次运行:

    #!/usr/bin/env python3
    
    import numba as nb
    import numpy as np
    from tkinter import *
    from PIL import Image, ImageTk
    import time
    
    # Make a Numpy array of uint8, that will become
    # ... our PIL Image that will become... 
    # ... a PhotoImage
    WIDTH, HEIGHT, ROWS = 128, 64, 8
    na = np.zeros((HEIGHT,WIDTH), np.uint8)
    
    @nb.njit()
    def extract(na,display_buffer):
       idx = 0
       x = y = 0
       for j in range(ROWS):
          for i in range(WIDTH):
             b = display_buffer[idx]
             for n in range(8):
                na[y+n, x] = (1 - ((b >> (7-n)) & 1)) * 255
             idx += 1
             x   += 1
          x  = 0
          y += 7
       return na
    
    # Following is first run which includes compilation time
    warmup = extract(na, display_buffer)
    
    # Only time the second run
    start = time.time()
    na = extract(na, display_buffer)
    # Make Numpy array into PIL Image
    PILImage = Image.fromarray(na)
    elapsed = (time.time()-start)*1000
    print(f'Total time: {elapsed} ms')      # Reports 0.068 ms
    
    border = 10
    root = Tk()  
    canvas = Canvas(root, width = 2*border + WIDTH, height = 2*border + HEIGHT)  
    canvas.pack()  
    # Make PIL Image into PhotoImage
    img = ImageTk.PhotoImage(PILImage)
    canvas.create_image(border, border, anchor=NW, image=img) 
    root.mainloop() 
    

    【讨论】:

    • 你好马克!非常感谢您的广泛回答。我用 numpy 的第一个版本。我是 python 新手,所以我想继续使用 tkinter,因为有很多教程。它就像一个魅力。我已经将它实现到程序的其余部分(使用 gui、线程、串行通信)并且几乎可以使用我需要的工具。你的贡献很有帮助。 Numba 是我以前没有听说过的东西,但总会有时间使用(因为它在运行时编译为 C - 听起来很棒)。再一次,非常感谢你:) 最好的祝福
    • 酷 - 祝你的项目好运!请记住,问题和答案是免费的,所以如果您遇到困难,请回来再问另一个问题。
    猜你喜欢
    • 2016-01-17
    • 1970-01-01
    • 1970-01-01
    • 2011-01-08
    • 1970-01-01
    • 2015-11-09
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    相关资源
    最近更新 更多