【Raspberry Pi】リアルタイムにグラフを表示する方法

【Raspberry Pi】リアルタイムにグラフに表示する方法

こんにちは! けい(Twitter)です。

今回は、ラズパイでPythonを用いて、リアルタイムにグラフを表示する方法についてまとめていきたいと思います。

リアルタイム描画に必要な準備

pythonのグラフ描画のためのモジュールとして良く使われているmatplotlibを用います。

そのため、matplotlibをpipコマンドでインストールする必要があります。

ラズパイでターミナルを開いて、次のコマンドを実行します。

$ pip3 install matplotlib

これで、matplotlibのインストールが完了します。

ステップ①:乱数をプロット

プログラム

乱数を生成してそれをプロットするというプログラムです。参考
ラズパイだけあれば実行できるので、手軽に試すことができます。

# Library
import numpy as np
import matplotlib.pyplot as plt
import collections

# params
dataLength = 10  # 1つのデータの配列の点数
frame = 25  # プロットするフレーム数
sleepTime = 0.0001  # 1フレーム表示する時間[s]

history = collections.deque(maxlen=dataLength)

# plot
if __name__== "__main__":
    try:
        for i in range(frame): # フレーム回数分グラフを更新
            data = np.random.rand() # プロットするデータを作成
            history.append(data)
            x = list(range(i-len(history), i))
            plt.plot(x,history) # データをプロット
            plt.draw() # グラフを画面に表示開始
            plt.pause(sleepTime) # SleepTime時間だけ表示を継続
            plt.cla() # プロットした点を消してグラフを初期化
    except KeyboardInterrupt:
        plt.close()
plt.close()

matplotlibをインストールしたのに、プログラムエラーが出ることがあります。

TypeError: Couldn't find foreign struct converter for 'cairo.Context'

私もこのようなエラーが出たので、’cairo.Context’をインストールしました。

調べてみると、次のコマンドでインストールできるようです。

$ sudo apt-get install python3-gi-cairo

しかし、実行してみるとまたしてもエラーが、

次のコマンドを要求されました。

$ sudo dpkg --configure -a

このコマンドを実行して、もう一度’cairo.Context’をインストールするコマンドを実行すると、何とかインストールすることができました。

プログラム解説

初めにライブラリをインポートします。

  • numpy:乱数を作るため
  • matplotlib:グラフを描画するため
  • collections:y座標データを保管するため
import numpy as np
import matplotlib.pyplot as plt
import collections

次にパラメータの設定をします。

dataLength = 10  # 1つのデータの配列の点数
frame = 25  # プロットするフレーム数
sleepTime = 0.0001  # 1フレーム表示する時間[s]

続いて、y座標を保管するデータの設定をします。
collectionsのdequeという関数を使うと、ロケット鉛筆のような変数の箱を作ることができます。
この箱に新しいデータを追加すると、古いデータから順に捨てられていきます。
今回は「maxlen」を「10」と設定したので、10個の変数が入る箱が用意されます。

history = collections.deque(maxlen=dataLength)

続いてグラフ描画のためのプログラムです。

frameの回数だけグラフを更新します。
rand()で生成した乱数をhistoryに追加していきます。これがy座標データです。
x座標は、フレーム数としています。

for i in range(frame): # フレーム回数分グラフを更新
    data = np.random.rand() # プロットするデータを作成
    history.append(data)
    x = list(range(i-len(history), i))
    plt.plot(x,history) # データをプロット
    plt.draw() # グラフを画面に表示開始
    plt.pause(sleepTime) # SleepTime時間だけ表示を継続
    plt.cla() # プロットした点を消してグラフを初期化

ステップ②:アナログ値を表示してみる

Arduinoを触ったことがある人なら分かると思いますが、Arduinoでは入力したアナログ値をそのままグラフとしてリアルタイムに表示できます。

それをラズパイでもやってみたいと思います。

アナログ入力にラズパイが対応していないので、アナログ入力をするためのモジュールが必要になります。

回路図

アナログ入力をするための回路を組みます。

アナログ入力モジュールとしてmcp3008を用います。

created by Rinker
スイッチサイエンス(Switch Science)
¥880 (2021/04/14 23:42:36時点 Amazon調べ-詳細)

また、可変抵抗があるとアナログ値が分かりやすいので、可変抵抗も用意します。

created by Rinker
waves
¥580 (2021/04/14 23:42:36時点 Amazon調べ-詳細)

詳しくは、以前のブログでまとめています。

プログラム

# Library
import numpy as np
import matplotlib.pyplot as plt
import collections
from gpiozero import MCP3008

# params
dataLength = 10  # 1つのデータの配列の点数
frame = 50  # プロットするフレーム数
sleepTime = 0.0001  # 1フレーム表示する時間[s]
Vref = 3.33

history = collections.deque(maxlen=dataLength)

def analog_read(channel):
    pot = MCP3008(channel)
    volt = pot.value * Vref
    return volt

# plotting
if __name__== "__main__":
    try:
        for i in range(frame): # フレーム回数分グラフを更新
            data = analog_read(0) # プロットするデータを作成
            history.append(data)
            x = list(range(i-len(history), i))
            plt.plot(x,history) # データをプロット
            plt.xlabel("frame")
            plt.ylabel("Voltage [V]")
            plt.draw() # グラフを画面に表示開始
            plt.pause(sleepTime) # SleepTime時間だけ表示を継続
            plt.cla() # プロットした点を消してグラフを初期化
    except KeyboardInterrupt:
        plt.close()
plt.close()

出力結果

この動画を撮った時は、x軸とy軸のラベル表示がないですが、リアルタイムにグラフに結果を出力することができています。

ステップ③:2つのグラフを同時に出力してみる

ステップ②でせっかくアナログ入力の回路を組んだので、2つのアナログ入力を同時にグラフに出力してみましょう。

回路はそのままでプログラムを変更するだけです。

プログラム

y座標のデータを保管するための箱を2つ用意し、それぞれに値を追加するだけです。

# Library
import numpy as np
import matplotlib.pyplot as plt
import collections
from gpiozero import MCP3008

# params
dataLength = 10  # 1つのデータの配列の点数
frame = 50  # プロットするフレーム数
sleepTime = 0.0001  # 1フレーム表示する時間[s]
Vref = 3.33

history_0 = collections.deque(maxlen=dataLength)
history_1 = collections.deque(maxlen=dataLength)

def analog_read(channel):
    pot = MCP3008(channel)
    volt = pot.value * Vref
    return volt

# plotting
if __name__== "__main__":
    try:
        for i in range(frame): # フレーム回数分グラフを更新
            data_0 = analog_read(0) # プロットするデータを作成
            data_1 = analog_read(1)
            history_0.append(data_0)
            history_1.append(data_1)
            x = list(range(i-len(history_0), i))
            
            plt.plot(x,history_0, label='chanel 0') # データをプロット
            plt.plot(x,history_1, label='chanel 1')
            plt.legend()
            plt.xlabel("frame")
            plt.ylabel("Voltage [V]")
            plt.draw() # グラフを画面に表示開始
            plt.pause(sleepTime) # SleepTime時間だけ表示を継続
            plt.cla() # プロットした点を消してグラフを初期化
    except KeyboardInterrupt:
        plt.close()
plt.close()

出力結果

2つのつまみを回しながら動画を撮るのが大変だったので、画像を保存しました。

2つのアナログ値が表示出来ていることが分かります。