降雨検知システムを作る4 後編1-2

宿題の機能についての考察

Intensity(diff)値から1時間当たりのmmℓ/hを算出する

1. scikit-learnによる方程式の導出

同期検波で得られる diff(差分振幅)は、水滴によって10kHzの信号が、降雨無し時のセンサーが乾
燥している時に比べてどれだけ遮られたかを示す値です。
これを時間積分(加算)した値は、理論上「センサー面を通過した総水滴量」に比例します。

実測の降雨カップ(雨量計)の値と紐づけて、Pythonの scikit-learn(線形回帰:LinearRegression など)で 「実測雨量 = 傾き × diffの1時間合計値 + 切片」 という方程式を作ることで、設置環境に完全に最適化した校正式が得られます。

多項式回帰(Polynomial Regression)を使う
水滴が激しくなると、水滴同士が重なって diff の伸びが鈍くなる(飽和現象)ことがあります。scikit-learn で直線(y = ax + b)にするのではなく、2次曲線(y = ax² + bx + c や、ESP32の既存コードにもある累乗根(y = a * x^b を使ってフィッティングさせると、大雨時の誤差が劇的に減ります。

2. 正しい結果に近づけるためのサンプリング回数

実測データ(サンプリング回数)は、最低でも 10回〜20回(10〜20時間分)、理想を言えば 30
回以上 必要です。

ただし、回数よりも「雨の強さのバリエーション(網羅性)」が極めて重要になります。
NGな例: 小雨(1mm/h未満)のデータばかり50回集めても、大雨のときの方程式は作れません。
OKな例: 小雨、普通の雨、大雨(ザーザー降り)のデータが、それぞれ数回ずつ(計15〜20本)バ
 ランスよく含まれていれば、少ないサンプリング回数でも非常に精度の高い方程式が得られます。

屋外で毎時間カップを測りに行くのは、夜間や仕事中など肉体的に限界があります。
正直自分も1~3回程度ならまだしも1カ月も掛けてそれを行う勇気が出てきません。
Amazon等で入手できる 「転倒ます型雨量計(スイッチ接点出力付きの雨量コップ)」 の信号をMQTT
送信して、0.2mm雨が降るたびにNode-REDが自動で「実測カウント」を記録できるようにすれば、ら
くちんで「実測雨量」と「diff合計値」の2つをNode-REDが全自動で1時間ごとにCSVへ保存してくれ
ため、何もしなくても数日雨を待つだけで、100本以上の完璧な機械学習用サンプリングデータが自
動的に手に入ります。
これが良さそうな感じがするので早速ポチって入手して見たいと思います。
上手く行ったら後日ご紹介します。

Bnineteenteam 雨量センサー転倒バケツ雨量計
485通信、解像度0.2mm 
購入価格 ¥8,198
購入先 アマゾン

3. Node-RED:毎正時から1時間自動集計・CSV保存フロー

降雨中にNODE-REDフローにある「開始->1時間ごとにdeff(Intensity)値をcsvファイルに書き出す。」
のInjectボタンをクリックするとRasberry PI上の「/home/pi/」にrain_mmh.csvが1時間ごとに書き出
されます。
計測と同じ時間に、測定用ますで測定します。
理想的には30回以上計測します。

skycabin 雨量計
降雨量測定 レインゲージ
購入価格¥998
購入先 アマゾン

4. 収集した計測値を「scikit-learn」を利用したpythonスクリプトに入れる

収集した「rain_mmh.csv」CSVファイル(日時, diff合計値)の右側に、以下のように手動でカンマと
実測雨量(下記黄色部分)を書き足して保存します。
2026-06-20 23:00:00,150.50,0.2
2026-06-20 24:00:00,450.20,0.5
2026-06-21 01:00:00,820.00,1.2
(これを30行分、またはそれ以上並べて準備する)

次に以下のスクリプトを実行する。
Jupyter NotebookかGoogle Colaboratoryなどで実行します。

# !pip install pandas scikit-learn matplotlib numpy

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.metrics import r2_score

# =====================================================================
# 1. データの読み込み (CSVファイルを直接ロードします)
# =====================================================================
# ファイル名やパスは環境に合わせて変更してください
csv_file_path = "rain_mmh.csv"

# CSVにヘッダーがない前提で読み込み、列に名前をつけます
df = pd.read_csv(csv_file_path, header=None, names=["timestamp", "diff_sum", "measured"])

# CSVから計算用のデータを抽出(何行あっても自動で全件取得します)
diff_sum_1h = df["diff_sum"].values
measured_rain = df["measured"].values

# データ数の確認表示
print(f"読み込んだサンプリングデータ数: {len(diff_sum_1h)} 組\n")

# 機械学習モデル用に型を整形
X = diff_sum_1h.reshape(-1, 1)
y = measured_rain

# =====================================================================
# 2. 2次多項式回帰によるフィッティング
# =====================================================================
model = make_pipeline(PolynomialFeatures(degree=2), LinearRegression())
model.fit(X, y)

lin_reg = model.named_steps['linearregression']
coef = lin_reg.coef_
intercept = lin_reg.intercept_

# =====================================================================
# 3. 変換式(キャリブレーション方程式)の表示
# =====================================================================
print("----- 降雨量校正用 変換式 -----")
print(f"降雨量 (mm/h) = {coef[2]:.12f} * (diff合計)^2 + {coef[1]:.8f} * (diff合計) + {intercept:.6f}")

# 決定係数 (R2値)
rain_pred = model.predict(X)
r2 = r2_score(y, rain_pred)
print(f"決定係数 (R2スコア) = {r2:.4f}")

# =====================================================================
# 4. グラフによる可視化
# =====================================================================
x_range = np.linspace(min(diff_sum_1h), max(diff_sum_1h), 300).reshape(-1, 1)
y_range = model.predict(x_range)

plt.figure(figsize=(8, 5))
plt.scatter(diff_sum_1h, measured_rain, color='red', s=30, label=f"Actual Data ({len(diff_sum_1h)} points)")
plt.plot(x_range, y_range, color='blue', linewidth=2, label="Polynomial Regression (Degree=2)")

plt.xlabel("1-Hour Accumulated Diff Value (Node-RED CSV)")
plt.ylabel("Rainfall Intensity (mm/h)")
plt.title("Rain Sensor Calibration Curve")
plt.legend()
plt.grid(True, linestyle="--", alpha=0.6)
plt.show()