PiAwarreを運用している Raspberry Pi のMIPI CSI-2インターフェイスにカメラモジュールを取り付け、 定点カメラ としても活用して撮影画像を定期的にGoogleドライブへアップロードして、閲覧しやすいような仕組みを構築したり、 照度センサ と連携して 夜間撮影 も出来るようにしてみました。
ハードウェア
今回紹介する定点カメラシステムは、FlightAwareから提供されているSDイメージ版PiAwareが稼働する初代Raspberry Piと、MIPI CSI-2リボンケーブルで繋がるRaspberry Pi Camera Module V1により構成されています(この機器に関する過去の記事はこちら)。
RaspberryPi向けカメラモジュールには、より高性能なV2やHQもありますが、接続先が非力な初代RaspberryPiなので、無難なV1モジュールを選択。その大まかな仕様は次の通りです。
- イメージセンサ : Omnivision OV5647
- 視野角 : 69.1度
- 静止画解像度 : 2592x1944px
- 動画解像度 : 1080p30, 720p60, 480p60/90
- 最大露光時間 : 6秒 (V2モジュールなら10秒)
OSはRaspberry Pi OS Stretchベースなので、本記事はレガシーな raspistill での撮影を前提に進めます。
1 2 3 4 5 6 7 8 9 10 11 |
$ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 9.13 (stretch) Release: 9.13 Codename: stretch $ uname -a Linux piaware1 4.19.66+ #1253 Thu Aug 15 11:37:30 BST 2019 armv6l GNU/Linux $ vcgencmd get_camera supported=1 detected=1 |
raspistillでの基本撮影
raspistill はファイル出力先を指定するのみでも撮影可能ですが、デバッグモードと画像サイズを指定するのみで実行してみます。
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 |
$ raspistill -v -n -w 1080 -h 720 -o /tmp/testcam.jpg "raspistill" Camera App (commit ) Camera Name ov5647 Width 1080, Height 720, filename /tmp/testcam.jpg Using camera 0, sensor mode 0 GPS output Disabled Quality 85, Raw no Thumbnail enabled Yes, width 64, height 48, quality 35 Time delay 5000, Timelapse 0 Link to latest frame enabled no Full resolution preview No Capture method : Single capture Preview No, Full screen Yes Preview window 0,0,1024,768 Opacity 255 Sharpness 0, Contrast 0, Brightness 50 Saturation 0, ISO 0, Video Stabilisation No, Exposure compensation 0 Exposure Mode 'auto', AWB Mode 'auto', Image Effect 'none' Flicker Avoid Mode 'off' Metering Mode 'average', Colour Effect Enabled No with U = 128, V = 128 Rotation 0, hflip No, vflip No ROI x 0.000000, y 0.000000, w 1.000000 h 1.000000 Camera component done Encoder component done Starting component connection stage Connecting camera preview port to video render. Connecting camera stills port to encoder input port Opening output file /tmp/testcam.jpg Enabling encoder output port Starting capture -1 Finished capture -1 Closing down Close down completed, all components disconnected, disabled and destroyed |
実際には意外と時間が掛かります。これは、コマンドを実行してから実際にイメージセンサが撮影開始するまでに、デフォルトで5秒掛けてウォーミングアップしているため。これは次のオプションで変更することが出来ますが、あまり小さな値にしてしまうと、ぼんやり暗い画像になってしまうので、色々と試して以下の設定値としています。
1 2 |
-t 800 撮影までの時刻(ミリ秒) デフォルト 5000 |
他、画像処理ではお馴染みのパラメータが設定可能ですが、これは被写体や好みにもよるところでしょう。
1 2 3 4 5 6 7 8 |
-sh 80 シャープネス (-100 to 100) -br 55 ブライトネス (0 to 100) -sa -50 サチュレーション (-100 to 100) -co 60 コントラスト (-100 to 100) |
以上の設定値を織り込んで再び撮影してみます。
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 |
$ raspistill -v -n -w 1080 -h 720 -t 800 -sh 80 -sa -50 -co 60 -br 55 -o /tmp/testcam2.jpg "raspistill" Camera App (commit ) Camera Name ov5647 Width 1080, Height 720, filename /tmp/testcam2.jpg Using camera 0, sensor mode 0 GPS output Disabled Quality 85, Raw no Thumbnail enabled Yes, width 64, height 48, quality 35 Time delay 800, Timelapse 0 Link to latest frame enabled no Full resolution preview No Capture method : Single capture Preview No, Full screen Yes Preview window 0,0,1024,768 Opacity 255 Sharpness 80, Contrast 60, Brightness 55 Saturation -50, ISO 0, Video Stabilisation No, Exposure compensation 0 Exposure Mode 'auto', AWB Mode 'auto', Image Effect 'none' Flicker Avoid Mode 'off' Metering Mode 'average', Colour Effect Enabled No with U = 128, V = 128 Rotation 0, hflip No, vflip No ROI x 0.000000, y 0.000000, w 1.000000 h 1.000000 Camera component done Encoder component done Starting component connection stage Connecting camera preview port to video render. Connecting camera stills port to encoder input port Opening output file /tmp/testcam2.jpg Enabling encoder output port Starting capture -1 Finished capture -1 Closing down Close down completed, all components disconnected, disabled and destroyed |
また、撮影時刻を -a オプションでアノテーションとして画像へ描くことが出来ます。このとき実際に描画される部分の画像背景によっては見えにくくなるので、アノテーションの背景色を付けておくと便利です。
1 2 3 4 5 6 7 |
$ raspistill -v -n -w 1080 -h 720 -t 800 -sh 80 -sa -50 -co 60 -br 55 -a 1024 -a "$(date '+\%Y-\%m-\%d \%H:\%M:\%S')" -o /tmp/raspicam.jpg -a 1024 注釈の背景色を指定 -a "$(date '+%Y-%m-%d %H:%M:%S')" dateコマンド出力を注釈に |
尚、撮影画像の出力先には、繰り返し撮影する定点カメラの性質を考慮し、tmpfsで領域確保された /tmp フォルダを指定しています(Raspberry Pi OSにおける、tmpfs活用に関する過去の記事はこちら)。
1 2 3 |
$ df -h /tmp Filesystem Size Used Avail Use% Mounted on tmpfs 16M 560K 16M 4% /tmp |
画像をGoogleドライブへアップロード
撮影画像は閲覧しやすいよう、Googleドライブへアップロードすることにし、その仕組みには前回組んだPyDriveのPythonスクリプトを使います。
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 |
from pydrive2.auth import GoogleAuth from pydrive2.drive import GoogleDrive gauth = GoogleAuth() gauth.CommandLineAuth() drive = GoogleDrive(gauth) # https://drive.google.com/drive/u/0/folders/################################# folder_id = '#################################' file_path = '/tmp/raspicam.jpg' ## Check Whether File Exists qstr = "title = \"cam.jpg\" and \"" + folder_id + "\" in parents and trashed=false" files = drive.ListFile({'q': qstr}).GetList() if len(files) > 0: # Overwrite file = files[0] print('File Exists on Drive :\t', file['title'], ' (', file['id'], ')') else: # Create New print('Create New File') file = drive.CreateFile({'title': 'cam.jpg', 'mimeType': 'image/jpeg', 'parents': [{'id': folder_id}]}) ## Upload file.SetContentFile(file_path) file.Upload() |
ただ、この機器のネットワーク環境がやや貧弱な電波環境下のWiFi接続のみであることから、画像アップロード中のネットワークリトライ頻発が懸念されます。そこで timeout コマンドによりアップロードスクリプトに実行時間制限(60秒)を設けることで、時間が掛かり過ぎたら諦めて次のタスクに譲る仕様としてみました。実際には次の要領で実行させるだけ。
1 |
$ timeout 60 python ./pydrive_upload.py |
cronへ登録して定点カメラ化
こうして組み上げたraspistillによる静止画撮影〜PyDriveによるGoogleドライブへの画像アップロードの仕組みをシーケンシャルに連結して、cronへ3分毎の繰り返し実行するよう登録します。
1 |
*/3 * * * * pi /usr/bin/raspistill -n -w 1080 -h 720 -t 800 -sh 80 -sa -50 -co 60 -br 55 -a 1024 -a "$(date '+\%Y-\%m-\%d \%H:\%M:\%S')" -o /tmp/raspicam.jpg && timeout 60 python3 /home/pi/pydrive_upload.py > /dev/null 2>&1 |
この時、raspistillコマンドを直接cronへ記述する際には、アノテーションに使うdateコマンドの日付時刻フォーマット指定に使われている「%」がダメ文字なので「\」でエスケープさせる手間が必要です(cronでは「%」がコマンド終端とみなされるため)。
アップロードされた画像はブラウザで確認出来る他、スマートフォンのGoogleドライブアプリでも簡単に閲覧することが出来てとても便利です。
長時間露光による夜間撮影
これでしばらく運用してみると、当たり前なことなのですが夜間は真っ暗なので、真っ黒な画像をひたすらアップロードすることになります。

図06.通常露光125msでの夜間撮影
そこで夜間は撮影を停止していた時期もあったのですが、せっかくなので長時間露光による夜間撮影を試してみます。まずは露光時間を3秒に設定し、ISO感度も最大にして撮影してみると。
1 2 3 4 5 6 7 |
$ raspistill -v -w 1080 -h 720 -t 800 -n -sh 80 -co 60 -br 55 -a 1024 -sa -50 -ss 3000000 -ISO 800 -a "$(date '+%Y-%m-%d %H:%M:%S')" -o /tmp/raspicam.jpg -ss 3000000 シャッター速度(マイクロ秒表記) -ISO 800 ISO感度(最大800) |
次にこのカメラユニットの最大露光時間である6秒を試してみます。
1 |
$ raspistill -v -w 1080 -h 720 -t 800 -n -sh 80 -co 60 -br 55 -a 1024 -sa -50 -ss 6000000 -ISO 800 -a "$(date '+%Y-%m-%d %H:%M:%S')" -o /tmp/raspicam.jpg |
ちょっと全体的に赤いのはおそらく基板上の赤色LEDが窓に反射しているのだと思われます。デフォルトではAutoになっているホワイトバランスをgreyworldに指定すると、さらに見やすくなります。
1 2 3 4 |
$ raspistill -v -w 1080 -h 720 -t 800 -n -sh 80 -co 60 -br 55 -a 1024 -sa -50 -ss 6000000 -ISO 800 -awb greyworld -a "$(date '+%Y-%m-%d %H:%M:%S')" -o /tmp/raspicam.jpg -awb greyworld ホワイトバランスプロファイル |
照度センサと連動した撮影モード切り替え
ここまでの試行錯誤を経て確定した日中と夜間それぞれの撮影モードを、24時間運用においてどのように適用するか考察してみます。
基本的に時間帯で切り替えるのですが、日の出・日没は季節によって変化します。また、明らかに日が出ている時間にまで照度センサ値で判定するのもナンセンスでしょうし、これは夜中でもまた言えることでしょう。
以上に加え、bashでは単純な時刻比較が出来ないことから、大雑把にHour時(00〜23)を使って次の要領で切り替えようと思います。
- 00〜03時台 夜間
- 04〜07時台 照度判定
- 08〜16時台 日中
- 17〜19時台 照度判定
- 20〜23時台 夜間
このRaspberry Piには、冗長化構成した温湿度気圧センサBME280と照度センサBH1750から成る、環境計測システムが構築されており、それは次のような計測ログを毎回上書き出力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ cat /tmp/ssr_upload -- Data -- CPU Temp = 47.62 deg C Illumi1 = 1242.50 lux Illumi2 = 1130.00 lux Temp1 = 26.14 deg C Temp2 = 26.49 deg C Humidity1 = 41.24 % Humidity2 = 41.56 % Pressure1 = 1009.29 hPa Pressure2 = 1009.81 hPa PressureZ1 = 1017.93 hPa PressureZ2 = 1018.45 hPa -- ThingSpeak -- <Response [200]> |
ここから必要な照度センサ値を取り出すのですが、bashは小数点のある数値演算が出来ないので、少し乱暴ですが小数点以下を切り取ってしまい、冗長化により2つある照度センサの値を加算してゼロかどうかを以って、夜間撮影とするかどうか判断することにします。
そしてようやく組み上げたのがこちらのスクリプトです。時間帯の判断から、照度判定、静止画撮影ののち、Googleドライブへアップロードまでこなします。
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 |
#!/bin/bash function doshot () { img="/tmp/raspicam.jpg" case $1 in 1) #DAYSHOOT echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Day Shoot Start" raspistill -w 1080 -h 720 -t 800 -n -sh 80 -co 60 -br 55 -a 1024 -a "$(date '+%Y-%m-%d %H:%M:%S')" -o $img echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Day Shoot End" ;; 0) #NIGHTSHOOT echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Night Shoot Start" raspistill -w 1080 -h 720 -t 800 -n -sh 80 -co 60 -br 55 -sa -50 -ss 4000000 -ISO 800 -awb greyworld -a 1024 -a "$(date '+%Y-%m-%d %H:%M:%S')" -o $img echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Night Shoot End" esac } function isNumeric () { if [ $# -ne 1 ]; then echo 1 else expr "$1" + 1 >/dev/null 2>&1 if [ $? -ne 0 ]; then echo 1 else echo 0 fi fi } echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Batch Start" case $(date '+%H') in "00"|"01"|"02"|"03"|"20"|"21"|"22"|"23") #NIGHT doshot 0 ;; "08"|"09"|"10"|"11"|"12"|"13"|"14"|"15"|"16") #DAY doshot 1 ;; "04"|"05"|"06"|"07"|"17"|"18"|"19") #ILLUMI ssrfile="/tmp/ssr_upload" if [ -e $ssrfile ]; then i=`grep Illumi1 $ssrfile | cut -d "=" -f2 | grep -Eo '[+-]?([0-9]*[.])?[0-9]+' | sed s/\.[0-9,]*$//g` j=`grep Illumi2 $ssrfile | cut -d "=" -f2 | grep -Eo '[+-]?([0-9]*[.])?[0-9]+' | sed s/\.[0-9,]*$//g` if [ `isNumeric $i` -eq 0 ] && [ `isNumeric $j` -eq 0 ]; then IL=$(($i+$j)) echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] IL: $IL" if [ $IL -eq 0 ]; then #NIGHT doshot 0 else #DAY doshot 1 fi else echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Illumi1: $i" echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Illumi2: $j" echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Wrong SSR Value. Force Day Shoot" #DAY doshot 1 fi else echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] No SSR File Exists. Force Day Shoot" #DAY doshot 1 fi ;; *) #ERR echo "INVALID_TIME: Aborting..." exit 1 esac echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] Await 10s..." sleep 10 if [ -e $img ]; then echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] PyDrive Upload Start" timeout 50 python3 ./pydrive_upload.py echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] PyDrive Upload End" else echo "[`date +"%Y/%m/%d(%a) %k:%M:%S"`] No Img File Exists. PyDrive Upload Cancelled" fi |
問題無く期待通りに動作することを確認したら、cronへは単純に次のように登録します。
1 |
*/2 * * * * pi /home/pi/PiSurvCam.sh > /tmp/PiSurvCam.log 2>&1 |
各時間帯における実行ログを確認してみました。ログには日付時刻を出力するよう仕込んでおいたので、特に時間の掛かる夜間の所要時間から、cronの実行間隔を決めています。
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 |
【日中】 $ cat /tmp/PiSurvCam.log [2022/02/24(Thu) 12:20:01] Batch Start [2022/02/24(Thu) 12:20:01] Day Shoot Start [2022/02/24(Thu) 12:20:03] Day Shoot End [2022/02/24(Thu) 12:20:03] Await 10s... [2022/02/24(Thu) 12:20:13] PyDrive Upload Start [2022/02/24(Thu) 12:20:44] PyDrive Upload End 【照度判定→日中】 $ cat /tmp/PiSurvCam.log [2022/02/24(Thu) 17:04:01] Batch Start [2022/02/24(Thu) 17:04:01] IL: 587 [2022/02/24(Thu) 17:04:01] Day Shoot Start [2022/02/24(Thu) 17:04:03] Day Shoot End [2022/02/24(Thu) 17:04:03] Await 10s... [2022/02/24(Thu) 17:04:13] PyDrive Upload Start [2022/02/24(Thu) 17:04:44] PyDrive Upload End 【照度判定→夜間】 $ cat /tmp/PiSurvCam.log [2022/02/24(Thu) 18:02:01] Batch Start [2022/02/24(Thu) 18:02:01] IL: 0 [2022/02/24(Thu) 18:02:02] Night Shoot Start [2022/02/24(Thu) 18:02:31] Night Shoot End [2022/02/24(Thu) 18:02:31] Await 10s... [2022/02/24(Thu) 18:02:41] PyDrive Upload Start [2022/02/24(Thu) 18:03:13] PyDrive Upload End 【夜間】 $ cat /tmp/PiSurvCam.log [2022/02/24(Thu) 21:46:01] Batch Start [2022/02/24(Thu) 21:46:01] Night Shoot Start [2022/02/24(Thu) 21:46:31] Night Shoot End [2022/02/24(Thu) 21:46:31] Await 10s... [2022/02/24(Thu) 21:46:41] PyDrive Upload Start [2022/02/24(Thu) 21:47:11] PyDrive Upload End |
デバッグがてら数ヶ月この仕様で運用しながら、いずれはPiCameraライブラリを使って全てのスクリプトをPythonベースに書き直したいと思っています。