You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

适配Waveshare三色电子墨水屏的图像转换问题:ESP32内存不足求解

问题描述

我正尝试将图像文件处理为可在黑/白/红三色电子墨水屏上显示的格式,但遇到了输出分辨率相关的问题。

根据显示屏的示例代码,它需要两个字节数组(一个用于黑白,一个用于红色),每个数组为15000字节,该电子墨水屏的分辨率为400×300。

我使用以下Python脚本生成两个BMP文件:一个黑白文件、一个红色文件。脚本运行正常,但每个文件大小达360000字节,无法放入ESP32内存中。输入的PNG图像大小为195316字节。

我使用的库中有一个函数EPD_4IN2B_V2_Display(BLACKWHITEBUFFER, REDBUFFER);,它要求完整图像(黑白通道、红色通道各一个)加载到内存中,但当前的图像大小无法在ESP32上运行。而示例中每个颜色通道仅使用15KB,因此我认为自己在图像处理环节遗漏了某些步骤。

能否有人指出我遗漏的内容?我该如何更新Python图像处理脚本以解决此问题?

我使用的是Waveshare 4.2英寸三色电子墨水屏和Waveshare ESP32驱动板,大部分Python代码基于StackOverflow的一篇帖子,但始终找不到问题所在。

输入图像、黑白通道输出、红色通道输出如下:
Input Image
BW
Red

现有Python代码

import io
import traceback
from wand.image import Image as WandImage
from PIL import Image

# This function takes as input a filename for an image
# It resizes the image into the dimensions supported by the ePaper Display
# It then remaps the image into a tri-color scheme using a palette (affinity)
# for remapping, and the Floyd Steinberg algorithm for dithering
# It then splits the image into two component parts:
# a white and black image (with the red pixels removed)
# a white and red image (with the black pixels removed)
# It then converts these into PIL Images and returns them
# The PIL Images can be used by the ePaper library to display
def getImagesToDisplay(filename):
    print(filename)
    red_image = None
    black_image = None
    try:
        with WandImage(filename=filename) as img:
            img.resize(400, 300)
            with WandImage() as palette:
                with WandImage(width = 1, height = 1, pseudo ="xc:red") as red:
                    palette.sequence.append(red)
                with WandImage(width = 1, height = 1, pseudo ="xc:black") as black:
                    palette.sequence.append(black)
                with WandImage(width = 1, height = 1, pseudo ="xc:white") as white:
                    palette.sequence.append(white)
                palette.concat()
                img.remap(affinity=palette, method='floyd_steinberg')
                
                red = img.clone()
                black = img.clone()

                red.opaque_paint(target='black', fill='white')
                black.opaque_paint(target='red', fill='white')
                
                red_image = Image.open(io.BytesIO(red.make_blob("bmp")))
                black_image = Image.open(io.BytesIO(black.make_blob("bmp")))

                red_bytes = io.BytesIO(red.make_blob("bmp"))
                black_bytes = io.BytesIO(black.make_blob("bmp"))

    except Exception as ex:
        print ('traceback.format_exc():\n%s',traceback.format_exc())

    return (red_image, black_image, red_bytes, black_bytes)


if __name__ == "__main__":
    print("Running...")

    file_path = "testimage-tree.png"
    with open(file_path, "rb") as f:
            image_data = f.read()

    red_image, black_image, red_bytes, black_bytes = getImagesToDisplay(file_path)

    print("bw: ", red_bytes)
    print("red: ", black_bytes)

    black_image.save("output/bw.bmp")
    red_image.save("output/red.bmp")

    print("BW file size:", len(black_image.tobytes()))
    print("Red file size:", len(red_image.tobytes()))
解决方案

你的核心问题是:当前生成的BMP文件是每像素3字节(24位真彩色)的格式,而Waveshare的电子墨水屏需要的是位压缩格式——每8个像素用1字节存储,这样400×300的分辨率总字节数就是 (400×300)/8 = 15000字节,正好和示例中的大小匹配。

接下来修改你的Python脚本,主要做两个关键调整:

  1. 将处理后的图像转换为单通道的二值图像(仅黑/白),而不是保留24位彩色格式。
  2. 将二值图像按位打包,生成符合要求的字节数组,而不是直接保存为BMP文件。

修改后的完整代码

import io
import traceback
from wand.image import Image as WandImage
from PIL import Image

def getImagesToDisplay(filename):
    print(filename)
    red_buffer = None
    black_buffer = None
    try:
        with WandImage(filename=filename) as img:
            img.resize(400, 300)
            # 创建三色调色板
            with WandImage() as palette:
                with WandImage(width=1, height=1, pseudo="xc:red") as red:
                    palette.sequence.append(red)
                with WandImage(width=1, height=1, pseudo="xc:black") as black:
                    palette.sequence.append(black)
                with WandImage(width=1, height=1, pseudo="xc:white") as white:
                    palette.sequence.append(white)
                palette.concat()
                # 映射为三色图像
                img.remap(affinity=palette, method='floyd_steinberg')
                
                # 生成红色通道二值图像:红色为有效像素(对应字节位1),其他为0
                red = img.clone()
                red.opaque_paint(target='black', fill='white')
                red.opaque_paint(target='red', fill='black')
                red.format = 'pbm'  # 转换为单通道二值格式
                red_pil = Image.open(io.BytesIO(red.make_blob()))
                
                # 生成黑白通道二值图像:黑色为有效像素(对应字节位1),其他为0
                black = img.clone()
                black.opaque_paint(target='red', fill='white')
                black.opaque_paint(target='black', fill='black')
                black.format = 'pbm'
                black_pil = Image.open(io.BytesIO(black.make_blob()))
                
                # 将二值图像打包为字节数组(每8像素1字节,高位对应左侧像素)
                def image_to_buffer(pil_img):
                    buffer = bytearray()
                    pixels = pil_img.load()
                    width, height = pil_img.size
                    for y in range(height):
                        byte = 0
                        for x in range(width):
                            # 黑色像素时设置对应位
                            if pixels[x, y] == 0:
                                byte |= (1 << (7 - (x % 8)))
                            # 每8个像素写入一个字节
                            if (x + 1) % 8 == 0:
                                buffer.append(byte)
                                byte = 0
                        # 处理每行末尾不足8个像素的剩余位
                        if width % 8 != 0:
                            buffer.append(byte)
                    return buffer
                
                red_buffer = image_to_buffer(red_pil)
                black_buffer = image_to_buffer(black_pil)

    except Exception as ex:
        print('traceback.format_exc():\n%s', traceback.format_exc())

    return (black_buffer, red_buffer)


if __name__ == "__main__":
    print("Running...")
    file_path = "testimage-tree.png"
    
    black_buffer, red_buffer = getImagesToDisplay(file_path)
    
    print("BW buffer size:", len(black_buffer))  # 输出应为15000
    print("Red buffer size:", len(red_buffer))  # 输出应为15000
    
    # 保存为二进制文件,直接供ESP32驱动库使用
    with open("output/black_buffer.bin", "wb") as f:
        f.write(black_buffer)
    with open("output/red_buffer.bin", "wb") as f:
        f.write(red_buffer)

关键修改说明

  • 转换为二值图像:通过opaque_paint将目标颜色转为黑色,其他转为白色,再保存为PBM单通道二值格式,彻底消除彩色BMP的冗余数据。
  • 位打包逻辑:image_to_buffer函数遍历每个像素,将8个像素的状态(黑/白)打包到1个字节中,完全匹配Waveshare显示屏的内存布局要求。
  • 输出二进制文件:直接生成.bin格式的字节数组,无需解析BMP头,可直接被ESP32的驱动库读取使用。

现在生成的两个字节数组大小都是15000字节,完全符合显示屏的要求,能够正常加载到ESP32内存中。


内容的提问来源于stack exchange,提问作者Mike Buss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 12:51:56