日常的にパソコンを使うことから、光が画面に当たり見にくくなることを避けるために、カーテンを閉め切っているのだが、天気を確認するために外の様子を見ることが億劫になってきたので、机に向かいながらも外の天気がどうなっているか確認したいと思い始めた。特に冬であれば雪が降っているかどうかはかなり重要な外出の判断材料になるし、ほかの季節であっても雨であれば自転車が使えないからだ。
わざわざ立ち上がって窓際まで確認しにいってもいいのだが、それでも面倒なことはあるので、定期的に最新の外の様子を撮影してその画像をDiscordに投稿することができれば、最新の天気を簡単に把握できるようになるわけだ。それに、撮影を続けることで何に使うのかはまだ目処が立ってはいないが、天気データも集まるし、別のことに応用できるかもしれないと考えている。
ということで、天気確認用カメラプロジェクトを開始したのである。
概要
おおまかな作戦はこうだ。
- 屋外に向けたカメラから定期的に画像を取得する
- 取得した画像をクラウドストレージに保存する
- 夜間にも対応したいので赤外線センサー付きカメラを使う
- 定期的に最新の画像をDiscordのチャンネルに投稿する
カメラから画像を取得する部分と画像をDiscordのチャンネルに投稿する部分は1システムで完結させることもできるし、分割することもできるが、今回は分割することにした。理由は主に2つある:
- ひとつの仕組みで作るのは簡単だが、物理的・ソフトウェア的どちらにおいても壊れるときは同時に壊れてしまう。
- ひとつの仕組みで作ると、どこかのレイヤーで同じアーキテクチャを使う必要がある。カメラの映像取得はOpenCVの十八番だが、ほぼC++かPythonを選択せざるを得なくなる。
こうした理由から冗長性や適材適所を重視して、今回はそれぞれRaspberry PiとAWS Lambdaの構成にした。
試してみる
ネットワークカメラ
USBカメラとどちらを選択するかは悩んだが、汎用的に利用できるネットワークカメラにした。すでにRaspberry PiはNASとして使っており、USBストレージが2本刺さっているので、USBカメラでは電力不足にならないか不安でもあった。
電源の課題
屋外に設置するとソーラー駆動やバッテリー、あるいは電源の引き回しが必要になるが、面倒を避けるために室内に設置してガラス越しに撮影することにした。結果的に購入したのはEZVIZ C3TNで、安くて一般的なネットワークカメラだ。このカメラはスマホ・タブレット利用も考えられていて専用アプリもあるが、今回はRTSPプロトコル1で動画フレームが取得できればよかったので、専用アプリは使っていない。消費電力は6Wだが、深夜に動かす必要はないので、ダイヤル式のプログラムタイマーを購入した2。
IPアドレスの固定化
プログラムタイマーによって電源が制御されるので、IPアドレスが固定化されていないとカメラをネットワーク内で見つけることが難しくなる。使っている無線Wi-Fiルーターのコンソールから、カメラのMACアドレスを指定してIPアドレスの払い出しを固定化した。
RTSPによる動画フレーム取得
分かりにくいのだが、EZVIZ C3TNはスマホの設定アプリからRTSPの有効化オプションがあり、これがないとネットワーク経由でアクセスできない。有効化したあとで、
rtsp://username:password@ipaddress:554/H.264
のURLでVLC Media PlayerでもPython + OpenCVの構成でも動画を取得できることが確認できた。
個人情報保護法への配慮
個人情報保護法の施行によって、防犯カメラ等の用途が明らかであるもの以外のカメラは利用目的を提示しなければならなくなった。一般住宅街では方向や角度に注意が必要だし、なにより利用目的を明らかにするために、ホームセンターで無地のプレートを購入して「天気確認用カメラ」と紙を貼ってカメラの横に配置した。

Raspberry Pi
家でNASとして利用していたが、5V3Aの電力を食うとんでもないやつだし、ほとんどがアイドル時間でもったいないのでコイツを流用することにした。ネットワークカメラに対してOpenCVでアクセスしてフレームを取得するために、まずはPythonをインストールした。
Pythonのインストール
直接システムのPython環境をいじるのは好みではないので、pyenvを使ってPython 3.13をインストールした。特にバージョンにこだわりがあるわけではないが、最近のプログラミング言語で欲しい機能が一通り揃っているので、特に縛りがなければ最近は3.13を選択している。
カメラ画像を取得する
OpenCVライブラリを使うと簡単にフレームが取得できる。私が書いたコードはこういう感じのもので、取得したフレームをPillow(PIL)の画像形式に変換して返すようにしている。
# INetworkCameraはインタフェースで、このコードはその実装
class NetworkCamera(INetworkCamera):
def __init__(self, url: str) -> None:
self._url = url
def get_frame(self, delay: int = 10) -> NetworkCameraImage | None:
capture = cv2.VideoCapture(self._url)
if capture.isOpened():
for _ in range(delay):
capture.grab()
ret, frame = capture.retrieve()
capture.release()
if ret:
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
return NetworkCameraImage(PIL.Image.fromarray(rgb))取得した画像は1920x1080ピクセルとわりと大きめなので1/2に縮小している。天気を確認するだけであれば、それほど解像度は必要ないし、クラウドストレージでの管理も安く済むからだ。
Amazon S3にアップロード
取得した画像はAmazon S3にアップロードしている。推奨はされていないがアクセストークン方式の連携で、権限も対象バケットとそのPutObjectだけに制限してある。
異なる用途のために3つの画像をアップロードしているが、中身は同じである。
YYYY/MM/DD/HHMMSS.jpg(記録用)latest.jpg(最新の画像)latest.jpg.tmp(最新の画像・Discordアップロード用)
Lambda (Node.js)
AWS Lambdaはサーバーレスでコードを実行してくれるサービスで、常時起動が必要ないのであればコストも安くほぼ無料枠内で収まるので、最善策と言える。スクリプト言語はオンライン上のエディタでコードを書いてトライアルアンドエラーができるし、エラーログも出力される。今回はそれほど難しいことをやらないということと、手軽に動かしたかったので、Node.jsを選択してみた。
Discordに画像を投稿する
LambdaではAmazon S3にアップロードされた最新の画像を定期的にDiscordにアップロードする。定期的な実行はEventBridgeで実現でき、crontabと同じようなことができる。Discordは双方向的なやりとりが必要なら開発者コンソールからアプリ登録が必要だが、一方的な投稿であればウェブフックが使えるので、それを使ってS3から取得した画像を投稿する。手順の次のとおりである:
- Amazon S3に
latest.jpg.tmpがあるか確認する - あればその画像を取得してDiscordに投稿する
latest.jpg.tmpを削除する
いつの画像まで投稿したか?を記録して、そこから先の時刻の画像を投稿する方法も考えられるが、「投稿前の画像あれば投稿する」という方式にすることで、追加のファイルなしにやりたいことが実現できるし、カメラがプログラムタイマーで動いているときだけファイルが作られることからも、この仕組みはうまく噛み合っている。

(実際には閑静な住宅街が映っています)
コードを実装する
普段JavaScriptは書いているものの、Node.jsは書かないので、ここではGoogle Geminiと相談したり、最近Lambdaに提供されはじめた自動コード補完を使ったりして、開発効率重視で実装を進めた。結局のところ、主義の観点から半分くらいは手書きをすることになったが、一番面倒であまり考えたくなかった部分は生成AIが書いてくれたので助かった。
わかったこと
カメラ
昼間の映像性能は満足できる水準にあり、特に課題はない。しかし、夜間は赤外線ライトがガラスに反射して白飛び(サチる)してしまうので、なるべくガラスに近づける必要がある3。また、撮影箇所が夜間になると暗くなりすぎることもあり、赤外線をもってしても何も映らなくなってしまった。
少し残念に思いつつも、深夜に天気を知る必要はあまりないので、これは無視することとした。これは悪いことだけでもなく、プログラムタイマーのOFF時間も増やせるので、結果的に電気代も節約できる。
EZVIZ C3TNの設定は少々わかりにくい。最近はなんでもスマホでやらせようとするし、ユーザー登録をさせようとする。説明書も詳しく書かれているわけではないので、RTSPを有効にするオプションを見つけるのにかなりの時間を要した。
費用
追加で購入したものはネットワークカメラとプログラムタイマー、あとは電源タップと延長コードくらいで6,000円を超える程度で済んだ。
- サンワサプライ 電源延長コード 3m ¥1,018
- ELPA EDLPコード付 タップ 3個口 ¥654
- BN-LINK 24時間 スイッチ式 プログラム コンセントタイマー ¥1,380
- EZVIZ C3TN ¥3,486
¥4,980
Raspberry Piは3のModel Bを使っている。当時は¥3,500程度で購入できたが、今や半導体不足やメモリ高騰のおかげで¥8,800ほどになっている。正規代理店で一番安く購入できるのは今も昔もKSYである。
おわりに
ネットワークカメラを1台持っておくと、ちょっとしたことに応用できて便利である。特に近年はPythonの飛躍的な普及によってあらゆるデータの取扱いが簡単になったし、生成AIのおかげでコードを考えるよりもやりたいことに注力できるようになった。
巷では見守りカメラのようなサービス込みの製品も提供されているが、こうした自分でいろいろできるDIY的な開発ほど面白いものもそうそうない。OpenCVやPillowといった画像処理ライブラリも充実しているので、思いつき次第でなんでもできる。