はじめに
Raspberry PiとRGB LEDマトリクスを使って、指定したURLから動的にQRコードを生成・表示するサイネージシステムを作成しました。
学内ハッカソンがありその展示で使えたら面白くないか?というロマンから走り出した企画です。
コードを書き上げ、ディスプレイ上のスケーリングも完璧。「あとはスマホで読み取るだけ!」という段階まできて、ハードウェア特有の「物理的な壁」にぶつかり、泥臭いパラメータチューニングを繰り返した備忘録です。
これからLEDマトリクスでQRコードや細かい文字を実用レベルで表示させたい方の参考になれば幸いです。
💡 今回作成した成果物(ソースコード)
GitHubリポジトリにて公開しています。環境変数(.env)を用いた安全なURL管理にも対応させています。
Yo-kan1/rpi-rgbmatrix-qrcaster - GitHub
開発環境と構成
-
ハードウェア:
- Raspberry Pi 4B
- 64x64 RGB LED Matrix Panel (フルカラー / ダイナミック点灯方式)
- Adafruit RGB Matrix Bonnet for Raspberry Pi
-
ソフトウェア:
- Python 3(
qrcode,Pillow,rpi-rgb-led-matrix)
- Python 3(
実装の基本(スケーリングと描画)
URLからQRコードの生成自体はPython3の qrcode ライブラリを使えば簡単です。今回は64x64のパネルに対して、スマホからの視認性を上げるために「1セルを2x2ピクセルに拡大」して中央に配置する処理を qr_display.py に記述しました。
# 抜粋: エッジをくっきりさせるために Image.NEAREST を使用してスケーリング
img_qr_scaled = img_qr.resize((scaled_w, scaled_h), resample=Image.NEAREST)
肉眼で見ると、ドットの欠けもなく綺麗にQRコードが表示されています。完璧です。これは勝ったなとか思いました。
しかしそんな甘くなく、スマホのQRリーダーをかざした瞬間、本当の戦いが始まりました。
立ち塞がる壁:強烈なフリッカー(シマシマ現象)との戦い
スマホのカメラを通すと、画面に斜めの黒い帯(フリッカー)が大量に走り、QRコードの模様が分断されて全く読み取れません。
LEDパネルは人間の目の錯覚を利用して高速でオンオフを繰り返す「ダイナミック点灯」という方式をとっており、その点滅周期とスマートフォンのカメラのシャッタースピードが干渉しているのが原因でした。
これを解決するため、ソフトウェア側で2つの限界チューニングを行いました。
【対策1:通信速度の安定化】
まずはパネルへの信号を安定させるため、通信速度を落とす設定を入れました。標準(1〜4)を大きく超える極限の数値です。
options.gpio_slowdown = 9
【対策2:シャッタースピードを「落とさせる」ための輝度制限】
実はフリッカー対策でもう一つ重要なのが「明るさ」でした。
LEDの発光が強すぎると、スマホのカメラは自動的に「明るすぎる」と判断してシャッタースピードを速くしてしまいます。シャッタースピードが速くなると、LEDの点滅の「消えている瞬間」をカメラがはっきりと捉えてしまい、フリッカーがさらに悪化してしまうのです。
カメラのシャッタースピードを強制的に落とし、フリッカーを映像として馴染ませるために、明るさを限界まで絞り込みました。
options.brightness = 7
gpio_slowdown = 9 と brightness = 7。このパラメータの組み合わせが、今回のハードウェア構成とソフトウェア制御における、最もノイズの少ないスイートスポットでした。
これでようやく、スマホの画面越しでもQRコードの模様が安定して見えるようになりました。
完全からの妥協、そして「勝利」
「よし、これでいける!」
意気揚々とスマホのQRリーダーをかざしました。しかし……読めない。

※実装テストで使用しただけなのでURLの企業様とは関係ありません
画面越しにはあんなに綺麗に模様が見えているのに、リーダーのアルゴリズムは微小なフリッカーや滲みを敏感に察知してしまい、リアルタイムでのスキャンはどうしても弾かれてしまうのです。
ここで私は一つの妥協をしました。
「QRリーダーのライブカメラでは無理でも、一度標準カメラで写真を撮ってしまえばいいのでは?」
シャッターを切ることでフリッカーはごまかされ、「面」として綺麗に固定されます。そして、撮影した写真フォルダからQRコードを読み込ませると……
読めた!!!
一手間増える運用にはなりましたが、「LEDマトリクスで実用的なQRコードを表示し、スマホで読み取って任意のURLへアクセスさせる」という目的をついに達成しました。苦労の末にQRコードが表示され、スマホがURLを認識した瞬間の達成感は凄まじかったです。これは勝った。サイネージシステム完成です。
限界までチューニングした先で待っていた本当の「罠」
展示当日、意気揚々と実際の設置場所でのテストを行いました。しかし、ここで信じられないことが起きます。
なぜか、前日まで成功していた「写真撮影からの読み取り」すら上手くいかない(模様が薄すぎる) のです。
パラメータも完璧。テストしていた部屋ではあんなに綺麗に読めていた。なのに読めない。
絶望しかけたとき、ふと「あること」に気がつきました。
「……教室が、明るすぎる」
原因はプログラムでも基板でもなく、 「環境光」 でした。
蛍光灯などで周囲が明るい環境下だと、スマホのカメラは自動的に全体の露出を下げてしまいます。その結果、極限まで明るさを絞った(brightness = 7)LEDパネルの光と黒背景のコントラストが、カメラのセンサー上で完全に飛んでしまっていたのです。
ソフトウェアの限界まで戦って勝利を確信した末の本当のラスボスが、「光の物理法則(環境要因)」だとは思いませんでした。
最終的な運用フローと今後の課題
現状のハードウェアの制約と環境光の変化を考慮すると、リアルタイムスキャンはもちろん、明るい場所での運用は非常にシビアです。
そのため、現在の確実なワークアラウンドとしては以下のフローになります。
- 暗めの環境を確保する(または手で影を作るなどして環境光を防ぐ)
- 一度スマホの標準カメラで静止画として撮影する
- 撮影した写真からQRコードを読み取る
これなら確実に任意のURLへアクセスできます。
Next Action
ソフトウェアでの対策は限界が見えたので、さらに上の精度(明るい環境での直接読み取り)を求めるならハードウェアの更新や物理ハックが必要になります。
Adafruit Bonnetの基板裏面にあるGPIO 4番と18番ピンをはんだ付けしてショートさせる「ハードウェアPWM化(PWM mod)」を行えばフリッカーを根本から低減でき、明るさを上げても安定する可能性があります。あるいは、制御が複雑なスタティック点灯のハードルを避けるなら、マイコン内蔵LED(NeoPixel / WS2812B)パネルや、そもそも自発光しない電子ペーパーへの移行も有効な選択肢になると考えています。
ハードウェアとソフトウェアの境界線の難しさと面白さを、身をもって体験した開発でした!リポジトリでは実際のコードを公開しているので、興味がある方はぜひ覗いてみてください。
