如何用Raspberry Pi与SPI显示屏制作Vector机器人流畅眼睛动画?
树莓派SPI LCD流畅动画优化方案(适配Vector机器人眼睛效果)
问题背景
手头有Raspberry Pi 4(4GB)和2寸Waveshare SPI LCD显示屏,想实现类似Vector机器人的动态眼睛表情,但用Waveshare Python库结合PIL开发时帧率极低——测试代码中移动矩形50次耗时近7秒,无法满足流畅动画需求。
原测试代码:
import os import sys import time import logging import spidev as SPI from lib import LCD_2inch from PIL import Image, ImageDraw, ImageFont class CoreDisplay: # Raspberry pi pin configuration: RST = 27 DC = 25 BL = 18 bus = 0 device = 0 def __init__(self): # Display with hardware SPI # DON'T CRERATE MULTIPLE DISPLAYER OBJECTS!!! self.disp = LCD_2inch.LCD_2inch() # initialize library self.disp.Init() # Clear display self.disp.clear() def testmotion(self): # test to move a rectangle on screen x, y = 20, 20 old_shape = [(x, y), (x + 40, y + 40)] for i in range(50): # Cover the old shape in black image = Image.new("RGB", (self.disp.height, self.disp.width ), "BLACK") draw = ImageDraw.Draw(image) draw.rectangle(old_shape, fill ="Black") image = image.rotate(180) self.disp.ShowImage(image) # draw new shape shape = [(x, y), (x + 40, y + 40)] image = Image.new("RGB", (self.disp.height, self.disp.width ), "BLACK") draw = ImageDraw.Draw(image) draw.rectangle(shape, fill ="Cyan") image = image.rotate(180) self.disp.ShowImage(image) x += 1 old_shape = shape popDisplay = CoreDisplay() popDisplay.testmotion() time.sleep(5)
帧率低下的核心原因
- 重复创建画布对象:每次循环都新建
Image和ImageDraw实例,带来大量内存分配与销毁开销。 - 冗余的屏幕刷新:单次循环内调用两次
ShowImage,相当于刷新两次屏幕,直接翻倍耗时。 - 全量画布重建:每次都重新创建全黑画布,而非在原有画布上仅更新变化区域。
- 重复旋转操作:每帧都执行
rotate(180),可提前在初始化时完成一次旋转,避免重复计算。
优化方案与代码实现
关键优化点
- 复用画布与绘图对象:在初始化时创建一次画布,后续仅在其上修改内容。
- 单帧单次刷新:每帧仅执行一次
ShowImage,避免冗余刷新。 - 局部更新画布:仅覆盖上一帧的图形,而非重建整个画布。
- 提前旋转画布:初始化时就将画布旋转180度,后续无需重复操作。
优化后的代码
import os import sys import time import spidev as SPI from lib import LCD_2inch from PIL import Image, ImageDraw, ImageFont class CoreDisplay: # Raspberry pi pin configuration: RST = 27 DC = 25 BL = 18 bus = 0 device = 0 def __init__(self): self.disp = LCD_2inch.LCD_2inch() self.disp.Init() self.disp.clear() # 初始化画布,提前旋转180度,避免每帧重复旋转 self.canvas = Image.new("RGB", (self.disp.height, self.disp.width ), "BLACK") self.canvas = self.canvas.rotate(180) self.draw = ImageDraw.Draw(self.canvas) def testmotion(self): x, y = 20, 20 rect_width, rect_height = 40, 40 # 初始绘制第一个矩形 self.draw.rectangle([(x, y), (x + rect_width, y + rect_height)], fill="Cyan") self.disp.ShowImage(self.canvas) for i in range(50): # 用黑色覆盖上一帧的矩形 self.draw.rectangle([(x, y), (x + rect_width, y + rect_height)], fill="BLACK") x += 1 # 绘制新位置的矩形 self.draw.rectangle([(x, y), (x + rect_width, y + rect_height)], fill="Cyan") # 仅刷新一次屏幕 self.disp.ShowImage(self.canvas) # 可选:控制帧率,避免刷新过快(根据需求调整) # time.sleep(0.01) popDisplay = CoreDisplay() popDisplay.testmotion() time.sleep(5)
进阶优化:局部屏幕刷新
如果你的Waveshare LCD库支持局部刷新(查看LCD_2inch.py中是否有ShowPartialImage或类似方法),可以进一步提升帧率——仅刷新矩形移动的区域,而非全屏幕:
# 假设库中有局部刷新方法,参数为图像、起始坐标、区域宽高 # 在绘制新矩形后,仅刷新变化的区域 self.disp.ShowPartialImage(self.canvas, x-1, y, rect_width+2, rect_height+2)
注:局部区域需要包含矩形的移动范围,避免出现残留像素。
额外性能提升建议
- 使用硬件加速的图形库:PIL的软件渲染性能有限,可尝试使用
pygame或OpenCV替代,它们对树莓派的GPU有更好的支持,能显著提升动画帧率。 - 预渲染静态元素:如果眼睛表情有固定的静态部分,提前渲染好,动画时仅更新动态部分。
- 调整SPI总线速度:在初始化SPI时尝试提高总线速度(需确保显示屏支持),比如将SPI时钟频率从默认值调高至16MHz或更高,可减少数据传输时间。
内容的提问来源于stack exchange,提问作者EvanZ
相关产品推荐
相关产品推荐

