↓ メインコンテンツへスキップ

思い出をePaper に映そう!

·3464 文字·7 分
わて
著者
わて

ラズパイを使ってWaveshare のePaper(カラー電子ペーパー)に写真を表示させる方法を紹介します。

我が家では思い出の写真を映していて、写真は一定時間で別のものに切り替わるようにもしてます。

切り替えができるのはePaper ならではの楽しみ方です。

使ったもの
#

  • ラズパイ zero WH(これ)
  • 電子ペーパー Waveshare のリンク(これ)
  • 百均のフレーム(たぶんSeria に売ってた)
  • ラズパイの電源(マイクロB)、マイクロSDカード、ラズパイを覆えるな袋的なもの

作り方
#

ePaper に写真が映るまでの仕組みは、以下の通りです。

ディレクトリの中に写真がいくつか入ってる
↓
ランダムに一枚の写真を選ぶ(shell)
↓
その写真をePaperに映す(python)

本来なら全部Python で書けるのですが、ゆくゆくはNASのディレクトリをマウントする動作とかも盛り込む予定なので、写真を選ぶ作業だけはshell を使っています。

ハードウェア設定
#

ePaper とラズパイを接続
#

ePaper 本体とラズパイの中間アダプターボードを接続します。

FPC and mid board

FPC(金色のペラペラのやつ)の先込み向きは決まってて写真のように取り付けます。厳密に言うと、FPCの先端が金色の方が基盤側を向くように取り付けます。

中間アダプターボードは黒い部分を上げてロック解除できるので、ロック解除後FPCを差し込みます。

そして中間ボードの逆側は40ピンのメスがあるボードにつながってるので、それをラズパイ40ピンと接続します。

ラズパイゼロにOS入れる
#

今回はePaper に映し出すため専用なので、デスクトップ環境は不要で、Raspberry Pi OS Lite (32-bit) を入れました。

OSを入れるときはラズパイイメージャーを使って、sshも有効にしておきます。このOS は「Raspberry Pi OS (Other)」から選べます。

OSイメージ

起動や再起動には5~10分かかります。洗い物とかしながら待ちます。
起動後はIP を固定しておくと今後便利です。

IP固定 nmtui
sudo nmtui
↓
wlan0を選択(Wi-Fiじゃない場合は使ってるネットワークを選ぶ)
↓
IPv4 CONFIGURATION の項目が <Automatic> になっているので<Manual>へ
↓
固定したいIPとルーターのIPアドレスを設定
↓
完了したら最初の画面のQuitを押下
↓
再起動

SPI を有効にする
#

ePaperの制御にはSPIインターフェースの有効化が必須なので、有効化します。

sudo raspi-config を実行
↓
Interface Options → I4 SPI をYes に
↓
reboot

再起動後、ls /dev/spidev0.* で /dev/spidev0.0や /dev/spidev0.1が見れればOKです。

ソフトウェア設定
#

ソフトウェア側では大まかにこんな感じの設定をします。

  1. python: ePaperに写真を表示
  2. bash: 表示したい写真をランダムに選ぶ
  3. cron: そのbashを時間指定で繰り返し

作業を進めると、最終的なディレクトリ構造は以下のようになります。

# 筆者は一連の作業を`ePaper`というディレクトリで行いました

ePaper
 |-- images      # 写真を集めたディレクトリ
 |-- main.py     # ePaper に写真を表示するスクリプト
 |-- main.sh     # 写真を選択 & main.py を呼び出し
 └-- e-Paper     # Waveshare が提供するライブラリ

いろいろインストール
#

必要なライブラリをインストールします。
python 画像処理ライブラリpillow をインストール↓

sudo apt update
sudo apt install python3-pillow

今回使用したePaper はWaveshare のもので、ライブラリはGithub に用意されてます。ただ、ラズパイzeroのイメージにはgitが入っていないので、まずはgit のインストールからします。

# gitのインストール
sudo apt install -y git

# Waveshare のラズパイをクローン
git clone https://github.com/waveshare/e-Paper.git

Python スクリプト(ePaper に表示)
#

images ディレクトリを作って、ePaper に映したい写真を入れときます。

ssh にTeraterm を使っている場合はドラックアンドドロップで移せます。(/tmp とかに一旦入れ、そのあと作ったディレクトリに動かしましょう)

mkdir images
# ここに表示したい写真を入れる

※2026/9/13 追記
iPhone で撮った写真は.HEICという拡張子が付き、電子ペーパーで表示できなかったので、その対処をします。
libheif-examplesはHEIC画像をRAM 上でJPEGに変換してくれるCLIツールを提供しています。

sudo apt install -y python3-pil libheif-examples

次に以下のようにmain.py を書きます。(Gemini ありがとう)

main.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import sys
import os
import argparse
import subprocess
import io
from PIL import Image, ImageOps, ImageEnhance
import tempfile

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(BASE_DIR, 'e-Paper', 'RaspberryPi_JetsonNano', 'python', 'lib'))

from waveshare_epd import epd7in3e

PALETTE_COLORS = [
    0, 0, 0,        # 黒
    255, 255, 255,  # 白
    255, 0, 0,      # 赤
    255, 255, 0,    # 黄
    0, 0, 180,      # 青
    0, 255, 0,      # 緑
] + [0] * (768 - 18)

def load_image_in_memory(image_path):
    ext = os.path.splitext(image_path)[1].lower()

    if ext in ['.heic', '.heif']:
        ram_dir = "/dev/shm" if os.path.exists("/dev/shm") else None

        with tempfile.NamedTemporaryFile(suffix=".jpg", dir=ram_dir, delete=True) as tmp_file:
            cmd = ["heif-convert", image_path, tmp_file.name]
            subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, check=True)

            img = Image.open(tmp_file.name).convert("RGB")
            img.load() 
            return img
    else:
        return Image.open(image_path).convert("RGB")


def convert_in_memory(image_path, target_size=(800, 480)):
    img = load_image_in_memory(image_path)

    img = ImageOps.exif_transpose(img)

    img = img.resize(target_size, Image.Resampling.LANCZOS)

    img = ImageEnhance.Brightness(img).enhance(1.2)
    img = ImageEnhance.Contrast(img).enhance(1.15)
    img = ImageEnhance.Color(img).enhance(1.3)

    palette_img = Image.new("P", (1, 1))
    palette_img.putpalette(PALETTE_COLORS)

    dithered_img = img.quantize(palette=palette_img, dither=Image.Dither.FLOYDSTEINBERG)

    return dithered_img.convert("RGB")


def main():
    parser = argparse.ArgumentParser(
        description="7.3inch カラー電子ペーパー (HAT E) にHEIC/JPEG/PNG写真を直接描画するCLIツール"
    )
    parser.add_argument(
        "image_path",
        type=str,
        help="表示したい写真のパス (例: images/sample.HEIC)"
    )

    args = parser.parse_args()
    image_path = os.path.abspath(args.image_path)

    if not os.path.exists(image_path):
        print(f"エラー: 指定されたファイルが見つかりません -> {image_path}", file=sys.stderr)
        sys.exit(1)

    try:
        print(f"画像をメモリ上で処理中: {image_path}")
        frame_image = convert_in_memory(image_path)

        epd = epd7in3e.EPD()
        epd.init()
        epd.Clear()

        print("電子ペーパーへ描画中...")
        epd.display(epd.getbuffer(frame_image))

        epd.sleep()

    except subprocess.CalledProcessError as e:
        print(f"HEIC変換エラー: heif-convert の実行に失敗しました。\n{e.stderr.decode()}", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"エラーが発生しました: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
2026/08/22時点のスクリプト(HEIC非対応)
```py {title="main.py" linenos=true}
import sys
import os
import argparse
from PIL import Image, ImageOps

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.join(BASE_DIR, 'e-Paper', 'RaspberryPi_JetsonNano', 'python', 'lib'))

from waveshare_epd import epd7in3e

# 7.3inch e-Paper (E) color palette (RGB)
PALETTE_COLORS = [
    0, 0, 0,        # black
    255, 255, 255,  # white
    255, 0, 0,      # red
    255, 255, 0,    # yellow
    0, 0, 180,      # blue
    0, 255, 0,      # green
] + [0] * (768 - 18)

def convert_in_memory(image_path, target_size=(800, 480)):
    img = Image.open(image_path).convert("RGB")
    img = ImageOps.exif_transpose(img)
    img = img.resize(target_size, Image.Resampling.LANCZOS)
    # img = img.transpose(Image.ROTATE_180)

    palette_img = Image.new("P", (1, 1))
    palette_img.putpalette(PALETTE_COLORS)
    dithered_img = img.quantize(palette=palette_img, dither=Image.Dither.FLOYDSTEINBERG)
    return dithered_img.convert("RGB")

def main():
    parser = argparse.ArgumentParser(
        description="A CLI tool to display an image to 7.3inch color ePaper (HAT E)"
    )
    parser.add_argument(
        "image_path",
        type=str,
        help="path you want to display(ex: /mnt/nfs/photos/my_photo.jpg)"
    )

    args = parser.parse_args()
    image_path = os.path.abspath(args.image_path)

    if not os.path.exists(image_path):
        print(f"error: {image_path} was not found")
        return

    try:
        print("processing...")
        frame_image = convert_in_memory(image_path)

        print("initializing ePaper...")
        epd = epd7in3e.EPD()
        epd.init()
        epd.Clear()

        print("drawing...")
        epd.display(epd.getbuffer(frame_image))

        epd.sleep()
        print("done")

    except Exception as e:
        print(f"error: {e}")

if __name__ == "__main__":
    main()
```

実行するには、引数として写真のパスを渡します。(表示には20〜30秒かかります)

python3 main.py image_path

筆者が試すと、映し出された写真の青色が強めだったのでPALETTE_COLORSの値を青のみ180としています。
他の色も調整したい場合は、その色の値を増減させます。振り幅は0~255です。

# 7.3inch e-Paper (E) color palette (RGB)
PALETTE_COLORS = [
    0, 0, 0,        # black
    255, 255, 255,  # white
    255, 0, 0,      # red
    255, 255, 0,    # yellow
    0, 0, 180,      # blue
    0, 255, 0,      # green
] + [0] * (768 - 18)

また、もし表示された写真の上下が逆なら以下のコメントアウトを外します。

main.py
# img = img.transpose(Image.ROTATE_180)

bash(写真をランダムに選ぶ)
#

ePaper に投影されただけでも喜びMAXですが、ここから写真のランダム抽出と自動化を実装します。

先に述べたように、現在は写真をローカルに置いていますが、今後表示したい写真が増えたとき、NASのディレクトリをマウントしてそこから写真を選ぶようにしたいと思っています。

マウント絡みはbash で書く方が楽だと思うので、このような方針で設定してます。

main.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#! /bin/bash

cd /home/pi/ePaper

get_image() {
        images=(images/*)
        count=${#images[@]}

        if [ "$count" -eq 0 ]; then
                echo "there are no images"
                exit 1
        fi

        rand_index=$(( RANDOM % count ))

        selected_image="${images[$rand_index]}"
        echo "$selected_image"
}

/usr/bin/python3 main.py $(get_image)

最後にcronで自動実行させます。
設定するにはcrontab -eで設定を開き、最後の行に以下を追加します。

以下の例では、午前と午後の3時と再起動時にmain.shが実行されるようにしてます。

crontab にて
1
2
3
0 3,15 * * * /home/pi/ePaper/main.sh

@reboot sleep 60 && /home/pi/ePaper/main.sh

再起動したり指定した時間になって写真が切り替わればOKです。
cron で正しく動作しないときは、以下のようにしてログを確認してみてください。きっとAI が助けてくれます。

crontab にて
1
2
3
0 3,15 * * * /home/pi/ePaper/main.sh >> /home/pi/ePaper/cron.log

@reboot sleep 60 && /home/pi/ePaper/main.sh >> /home/pi/ePaper/cron.log

完成
#

あとはフレームに入れて完成です。フレームは100均とかで売ってる挟むタイプ(伝われ)が使いやすいです。こんな感じ↓

frame

ePaper本体以外の中間ボードやラズパイ本体の収納はどうするかというと、結論、「各々で頑張れ」なのですが、筆者は絶縁ビニールに入れてフレームの裏側に置いてます。
「普段見えないからこれでいいや」です。

package

この記事が写真を表示する以外にもラズパイとePaper を使った工作の参考になれば嬉しいです。

今後はNAS を設定して、そこから写真を選べるような変更を加えたいと思います。また別の記事にするので見ていただければと思います。