ラズベリーパイで作る省電力ライブカメラ(後編)~ ラズベリーパイ研究室

ラズベリーパイで作る省電力ライブカメラ(前編)では、Raspberry Pi のカメラモジュールを使ったコマ撮りでのライブカメラを作ってみました。

この後編では、ソケットサーバー「HAL」を使い、複数台の Raspberry Pi に接続したカメラモジュールからの映像を一元管理してみたいと思います。

おススメ!記事
Raspberry Pi 用「HAL」で、
カップラーメン・タイマーを作ってみよう!
ラズパイDIYの決定版! ソケットサーバー「HAL」をご紹介します。

※誠に申し訳ありませんがこちらの電子工作のコンテンツは弊社の実験制作例となっております。十分な安全が保障されているわけではないため、参照や実施は自己責任となってしまいますのでご注意ください。

HALを使って複数のカメラを一元管理

ラズベリーパイで作る省電力ライブカメラ(前編)では、Raspberry Pi のカメラモジュールを使ったコマ撮りでのライブカメラを作ってみました。

この後編では、ソケットサーバー「HAL」を使い、複数台の Raspberry Pi に接続したカメラモジュールからの映像を一元管理してみたいと思います。

概念図

今回構築したシステムは、下図のようなものです。

システム構成

Raspberry Pi 2 と 3 を用いていますが、Raspberry Pi B や B+ 等の古いものでも構わないはずです。

図では WEB サーバーは別のコンピューターとして示してありますが、これは WEB ページを閲覧するパソコンの中に WEB サーバーを立てて localhost としてアクセスしても構いませんし、あるいは、Raspberry Pi のどちらかに WEB サーバーをインストールして起動しても構いません。

各 Raspberry Pi には当サイトの製品である、ソケットサーバー「HAL」がインストールされており、TCP/IPソケット(BSDソケット)でサーバーと通信を行います。

このようにブラウザとRaspberry Pi の間に WEB サーバーを立てることで、複数の Raspberry Pi につながっているライブカメラを一元管理することができるようになるのです。

さて、では HAL の myActions.php に記述するコードを見てみましょう。

myActions.php のコード

class MyActions
{
public function action(HALDto $dto, $read_sock, $method)
{
// 以下のコードを参考に、あなたのアプリケーションを記述して下さい。
if(preg_match("/capture/ui", $method)){

ob_start();
system("sudo raspistill -o - -t 1 -w 640 -h 480 -e jpg -q 50 -n -rot 180");
$data = ob_get_contents();
ob_end_clean();

$response = "data:image/jpeg;base64," . base64_encode($data);
$this->send($read_sock, $response);
Bye::exec($dto, $read_sock);
}
}

/**
* クライアントに文字列を送信する
*
* @param resource $read_sock 接続ソケットリソース
* @param string $words 返信内容
*/
private function send($read_sock, $words)
{
$words .= "\n";
socket_write($read_sock, $words, strlen($words));
}
}

上記では、“capture" という命令が渡されるとカメラモジュールで撮影を行います。バッファリングの開始と終了、標準出力に出力された画像データの取得については、前編の ラズベリーパイで作る省電力ライブカメラ(前編) と同じです。

違うのは、ブラウザではなく接続してきた WEB サーバーにレスポンスを返すことです。

コードの下にある private メソッドの send() は、汎用的に利用するレスポンス用のメソッドです。

$response = "data:image/jpeg;base64," . base64_encode($data) . "\n";
socket_write($read_sock, $response, strlen($response));

としても同じことですが、あとで他に様々な命令を実装するようになった時、レスポンス用のメソッドが切り分けてあればそれを共通で使うことができて便利です。

HAL の myActions.php への記述は以上です。なお、/HAL/setting/HALSetting.php で、IPアドレスとポート番号の指定をするのをお忘れなく。

WEB サーバーのアプリケーション

WEB サーバーに置くファイルは最低2つです。1つはブラウザから直接アクセスするライブカメラ閲覧画面、もう1つはその画面から非同期で画像データを取得するAPIです。

まず、ライブカメラ閲覧画面のコードを先に示します。

index.html

<!DOCTYPE html>
<html>
<head>
<title>MyCamera2</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
(function(){

var loadable = true;
var idx = 0;

$(document).ready(function(){
var cameras = [
{ip: "192.168.1.12", port: 9002, img: $("#my-camera0")},
{ip: "192.168.1.15", port: 9005, img: $("#my-camera1")}
];
setInterval(function(){
if(loadable){
loadable = false;
getPicture(cameras[idx]);
}
}, 100);
});

function getPicture(camera)
{
var target = camera.img;
var temp = $("<img/>");
$.ajax({
type: "post",
url: "camera.php",
data: {ip: camera.ip, port: camera.port}
}).done(function(result){
temp.on("load", function(){
target.attr("src", $(this).attr("src"));
if(idx === 0){
idx = 1;
} else {
idx = 0;
}
loadable = true;
$(this).remove();
});
temp.attr("src", result);
});
}
})();
</script>
</head>
<body>
<img id="my-camera0" src="" style="height: 480px;">
<img id="my-camera1" src="" style="height: 480px;">
</body>
</html>

このプログラムは jQuery を使って同階層にある camera.php を呼び出しています。レスポンスは Base64 でエンコードされた JPEG ファイルが返されるので、それを <img/> タグにセットして読み込みが終わった段階で実際に表示されている <img/> タグにセットしなおします。

その後でカメラを切り替え、再び camera.php を呼び出します。これを延々と繰り返すわけです。

つぎは、ブラウザからの要求に応えて Raspberry Pi と通信を行って画像データを取得する API プログラムです。

camera.php

<?php
/**
* HAL 用サンプルプログラム
*
* (c) 2016 Katsuhiko Miki
*
* ※ このファイルについての詳細は、http://feijoa.jp/php365/firstLogin/loginForm/ をご確認下さい。
* ※ このプログラムは PHP に詳しくない方でも理解しやすいよう、初歩的な記述を用いています。
*/
namespace Feijoa\HAL;
require("SOCKET.php");

$socket = new SOCKET();

$ip = filter_input(INPUT_POST, "ip");
$port = (int)filter_input(INPUT_POST, "port");
if(empty($ip)){ echo ""; exit; }
if(!preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ui", $ip)){ echo ""; exit; }

// HAL の IPアドレスとポート番号
$result = $socket->connect($ip, $port);

if(!$result){
echo "";
} else {
$result = $socket->write("capture");

if(!$result){
echo "";
} else {
$data = $socket->read(1024*1024*50);
echo $data;
}
$socket->disconnect();
}

これが API プログラムの全てです。データを受け渡すだけなので、特に難しいことはしていません。

$ip = filter_input(INPUT_POST, "ip");
$port = (int)filter_input(INPUT_POST, "port");

この 2 行は、ブラウザから渡されたリクエストの値を変数に格納しています。

PHP の古い書き方ですと、

$ip = isset($_POST["ip"])? $_POST["ip"] : "";

のように、直接スーパーグローバル変数 $_POST を参照することが多くありますが、この変数は自由に書き換えが出来てしまうので直接操作するのはあまり適切ではないとされるようになりました。

filter_input(TYPE, PARAM_NAME); は、TYPE で示された情報の PARAM_NAME で得られる値を取得し、その値が設定されていない場合は null を返すメソッドです。

if(empty($ip)){ echo ""; exit; }
if(!preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ui", $ip)){ echo ""; exit; }

この 2 行は、ブラウザから渡されたリクエストの整合性をチェックしているバリデーションです。IP アドレスが渡されないか、1~3桁の数値を .(ピリオド)で区切った4つの塊になっていない場合、空文字列をブラウザに返して API の実行を終了させます。(2016/07/04追記、間違えて4桁(d{1,3})と書いていましたが修正しました。クレジットカード決済した直後でボケていました。)

なお、$port については (int) で整数型にパースしているため、例えば文字列などが渡された場合には 0 に置き換えられます。

こうして、ブラウザから渡されたリクエストの値が適切であるかを確認するのは重要な事です。

もし仮に、API に対してリクエストされた値 $val を使って

exec("sudo {$val}");

のような処理が有ったとすると、$val に "rm -rf /" のような値を埋め込むことで、サーバーの全ての情報を全て消し去ることもできてしまう危険性があります。

ライブカメラを見てみよう

さて、ではライブカメラを動作させてみましょう。ソケットサーバー「HAL」のパッケージについては、今回は音声認識は利用しないので Julius、及び voice_client.php を起動する必要はありません。

sudo php -q /var/www/HAL/system/hal.php

として、HAL を起動するだけで大丈夫です。ブラウザで、index.html にアクセスしてみた結果が以下です。

画面の再描画がかなり遅いと思われるでしょう。JavaScript では非同期で複数のリクエストを投げる事はできるのですが、何故か、結果については両方のリクエストの処理が終わってからでないと次のリクエストを発行出来ませんでした。ですから2つ同時にリクエストをするのはやめて、1つのリクエストを処理したら別のカメラにリクエストを投げるようなコードにしてあります。

恐らく iframe を使って別の WEB ページとして埋め込めばもっとパフォーマンスが良くなるのでしょうが、これはサンプルプログラムですので、これでよしとします。すいません。

パフォーマンス確認画面はこちら。

1% 程度しか CPU を利用していないのがお分かりいただけるかと思います。これこそが、Raspberry Pi らしい省電力設計であり、本企画の目指したところなのです。

この記事へのコメント

※現在コメントはMarkdown記法が強制です。>>Markdown の書き方


この記事に返信

このコメントに返信