「CORE S3」のカメラ画像をパソコンやスマホのブラウザから確認する方法について詳しく紹介します。
前回は、カメラ画像を本体の液晶画面に表示させる方法をサンプルプログラムで紹介しました。
今回は、サーバー機能を利用して、カメラの画像をフレーム単位でJPEGに変換してストリーム配信します。これには「Arduino」の「WebServer」機能を使ってシンプルに実装しています。
カメラ画像を液晶画面に表示する方法は以下のリンクで詳しく紹介しています。
スポンサーリンク
1.CORE S3とは
2.カメラ画像の送信手順
3.動作確認
4.初期設定
・ArduinoIDE
・PlatformIO
5.サンプルプログラム(コピペ)
6.プログラムの説明
・カメラ画像表示
・カメラ画像のJPEG変換
・ストリーミングカメラサーバー
7.ブラウザモニターサンプルプログラム
8.まとめ
1.CORE S3とは
「CORE S3」は「M5Stack社」のマイコンボードの上位機種で、タッチパネル対応の液晶画面やカメラ、デュアルマイク、スピーカ、振動センサ(6軸 加速度+ジャイロ)、磁気センサ(3軸)、SDカードスロットを内蔵しており、これらを組み合わせて色々なアイデアを試すことができます。
側面に3つの「Groveコネクタ」があり、スイッチやリレー、サーボ、アナログ入力、シリアル通信機器等を接続できます。
電源スイッチも搭載されており、ACアダプター(9〜24V)での使用も可能です。
バッテリー容量は500mAとマイコンボードとしては大容量です。
通信機能は、有線で「I2C」「UART」「SPI(内部M-BUSより)」に対応しており、無線は「Wi-Fi」と「Bluetooth」に対応しています。
「CORE S3」の使い方や機能、端子配列等の詳細は、以下のリンクで詳しく紹介しています。
2.カメラ画像の送信方法
「CORE S3」のカメラで撮影した画像を、Wi-Fi通信経由で「スマホ」や「パソコン」の「ブラウザ」から確認するためには以下のような手順で実現しています。
②CORE S3をサーバーに設定。
③カメラのフレーム画像を取得して「JPEGデータ」に変換。
④ブラウザからの接続(リクエスト)があったときに画像を送信するカメラサーバーを設定。
⑤画像を確認するスマホやパソコンをCORE S3と同じWi-Fi環境(SSID)に接続。
⑥本体起動時に液晶画面に表示される「IPアドレス」をブラウザのアドレスバーに入力。
⑦ブラウザにストリーミング画像が表示されます。
⑧画像を確認しながら、画質や画面の更新時間を最適な値に調整。
3.動作確認
動作については以下のようになります。
本体の電源を入れると、設定した「Wi-Fi接続先(SSID)」への接続が開始されます。
接続が完了すると「IP: 〜」に「IPアドレス」が表示されるのでこれを覚えておきます。
スマホやパソコンでブラウザを起動し、アドレスバーに「IPアドレス(今回の場合は192.168.0.28 ※環境により異なります)」を入力すると上画像のように、カメラの画像を確認することができます。
4.初期設定
開発環境の初期設定について「ArduinoIDE」と「PlatformIO」の初期設定は以下のようになります。
・ArduinoIDE
「ArduinoIDE」の初期設定は以下のようになります。
・ボード
M5CORE S3
・ライブラリ
M5CoreS3(バージョン1.0.0)
M5Unified(バージョン0.1.12)
M5Stackでの「ArduinoIDE」の使い方は、以下のリンクで詳しく紹介しています。
・PlatformIO
「PlatformIO」の初期設定は以下のようになります。
ここでは「バージョン6.5.0」で動作確認しています。
・ボード
M5Stack cores3
・ライブラリ
M5CoreS3(バージョン1.0.0)
M5Unified(バージョン0.1.12)
カメラを使用するには以下のメモリタイプ設定をしておく必要があります。
「platformio.ini」ファイルの内容は以下になります。
[env:m5stack-cores3]
platform = espressif32
board = m5stack-cores3
framework = arduino
board_build.arduino.memory_type = qio_qspi ; フラッシュメモリのタイプを指定
board_build.arduino.partitions = app3M_fat9M_16MB.csv ; フラッシュメモリのパーティション設定
lib_deps =
m5stack/M5CoreS3@^1.0.0
m5stack/M5Unified@^0.1.12
他にも「UIFlow」でも使用できますが、まだカメラは使用できないようです。
「UIFlow」の使い方は簡単ではありますが以下のリンクで紹介しています。
5.サンプルプログラム(コピペ)
サンプルプログラムは以下になります。「コピペ」して書き込んでください。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。
#include "M5CoreS3.h"
#include <WebServer.h> //サーバー設定用
#define JPEG_QUALITY 20 // JPEG画像品質(0〜100)
#define JPEG_UPDATE_TIME 200 // JPEG画像更新時間
WebServer server(80); // サーバー設定、ポート80で接続
// ローカルWi-Fi接続実行関数 *****************************************************
void localWifiConnect() {
// ローカルWi-Fi接続先設定
const char ssid[] = "自宅のWi-Fi接続先(SSID)を設定"; //接続先SSID
const char pass[] = "Wi-Fi接続先のパスワードを設定"; //接続先パスワード
// Wi-Fi接続確認(IPアドレスの取得で確認)
CoreS3.Display.println("Wi-Fi Search!");
while (WiFi.localIP()[0] == 0) { // IPアドレスが取得されるまで繰り返し
WiFi.begin(ssid, pass); //ローカル Wi-Fi接続実行
CoreS3.Display.print("・");
delay(3000); // 再接続待ち
}
// SSID, 接続IPアドレス表示
CoreS3.Display.print("\nSSID : "); // 接続先SSID表示
CoreS3.Display.println(WiFi.SSID());
CoreS3.Display.print("IP : "); // IPアドレス表示
CoreS3.Display.println(WiFi.localIP());
Serial.println(WiFi.SSID()); // シリアル出力
Serial.println(WiFi.localIP());
}
// カメラのストリーム画像を液晶画面に表示する関数 ****************************************
void streamCamera() {
// カメラからフレームを取得
if (CoreS3.Camera.get()) {
// フレームを画面に表示(x座標, y座標, 画面幅, 画面高さ, フレームバッファポインタ)
CoreS3.Display.pushImage(0, 0, 320, 240, (uint16_t *)CoreS3.Camera.fb->buf);
CoreS3.Camera.free(); // 取得したフレームを解放
} else {
Serial.println("Camera capture failed"); // エラー表示
}
}
// ルート(IPアドレス)アクセス時処理関数( "/stream" にリダイレクト) ********************
void handleRootPath() {
server.sendHeader("Location", "/stream"); // クライアントに対してLocationヘッダーを送信
server.send(302, "text/plain", ""); // ステータスコード302で応答し、リダイレクトを指示
}
// ストリーミング処理関数("IPアドレス/stream" アクセス時) *****************************
void handleStreamPath() {
WiFiClient client = server.client(); // 現在のクライアントの接続を取得
String response = "HTTP/1.1 200 OK\r\n"; // HTTPレスポンスの初めの行
response += "Content-Type: multipart/x-mixed-replace;boundary=frame\r\n\r\n"; // マルチパートでストリームデータを送信することと、区切りを「frame」とすることを宣言
server.sendContent(response); // マルチパートヘッダーを送信
// リクエストがある限りクライアントへレスポンスとしてJPEG画像を送信
uint8_t *out_jpg; // JPEGデータのポインタ格納用
size_t out_len; // JPEGデータの長さ格納用
while (true) { // 終了条件まで持続的にストリーミング
if (CoreS3.Camera.get()) { // カメラから新しいフレーム取得に成功したら
// フレームをjpegに変換してwebsocket送信
if(frame2jpg(CoreS3.Camera.fb, JPEG_QUALITY, &out_jpg, &out_len)) { // フレームをJPEGに変換し成功ならマルチパート形式でヘッダー設定
response = "--frame\r\n"; // 個々のフレームを判断する区切り文字を指定
response += "Content-Type: image/jpeg\r\n"; // Content-TypeにJPEGを指定
response += "Content-Length: " + String(out_len) + "\r\n\r\n"; // Content-Lengthに「out_len」を指定してヘッダー設定終了
server.sendContent(response); // マルチパートヘッダーを送信
// 液晶画面にフレームを表示
CoreS3.Display.pushImage(0, 0, 320, 240, (uint16_t *)CoreS3.Camera.fb->buf);
// JPEGデータ送信
client.write(out_jpg, out_len); // 変換されたJPEGデータをクライアントに送信
server.sendContent("\r\n\r\n"); // マルチパートの区切りシンボルを送信
free(out_jpg); // JPEGデータを格納していたメモリを解放
CoreS3.Camera.free(); // カメラのフレームバッファを解放
} else { // フレームをJPEGに変換できなかった場合
Serial.println("Failed to convert the frame to JPEG"); // エラー表示
}
} else { // フレームの取得に失敗したら
Serial.println("Camera capture failed"); // エラー表示
}
if (!client.connected()) { // クライアントが切断されたら
break; // ループを抜ける
}
delay(JPEG_UPDATE_TIME); // 送信するJPEG画像更新間隔(ローカルWi-Fi時は500推奨)
}
}
// カメラサーバー実行関数 **********************************************************
void startCameraServer() {
server.on("/", handleRootPath); // ルートパスのリクエストハンドラを設定
server.on("/stream", handleStreamPath); // ストリームパスのリクエストハンドラを設定
server.begin(); // サーバーを開始する
}
// 初期設定 ------------------------------------------------------------------
void setup() {
auto cfg = M5.config(); // 本体初期化
CoreS3.begin(cfg);
Serial.begin(9600); // シリアル通信初期化
// 液晶画面初期化
CoreS3.Display.setTextColor(CYAN); // 文字色
CoreS3.Display.setFont(&fonts::lgfxJapanGothicP_24); // フォント
CoreS3.Display.setTextSize(1); // テキストサイズ
CoreS3.Display.fillScreen(BLACK); // 背景設定
CoreS3.Display.setCursor(0, 0); // 表示開始位置左上角(X,Y)
// カメラ初期化
if (!CoreS3.Camera.begin()) { // 初期化に失敗したら
CoreS3.Display.drawCentreString("Camera Init Fail", 160, 185); // エラー表示
}
localWifiConnect(); // ローカルWi-Fi接続実行有効にする
delay(3000); // Wi-Fi接続情報表示時間
startCameraServer(); // カメラサーバーを開始
}
// メイン --------------------------------------------------------------------
void loop() {
server.handleClient(); // サーバーのハンドリング
streamCamera(); // カメラフレームを画面表示
}
6.プログラムの説明
サンプルプログラムの各部の動作について、各動作ごとに詳しく紹介します。
・カメラ画像表示
カメラ画像を表示するプログラムをサンプルプログラムから抜粋したものは以下になります。
// カメラのストリーム画像を液晶画面に表示する関数 ****************************************
void streamCamera() {
// カメラからフレームを取得
if (CoreS3.Camera.get()) {
// フレームを画面に表示(x座標, y座標, 画面幅, 画面高さ, フレームバッファポインタ)
CoreS3.Display.pushImage(0, 0, 320, 240, (uint16_t *)CoreS3.Camera.fb->buf);
CoreS3.Camera.free(); // 取得したフレームを解放
} else {
Serial.println("Camera capture failed"); // エラー表示
}
}
カメラ画像を表示するにはまず「CoreS3.Camera.get()」を実行します。
次に上コードの「6行目」のように以下を実行することでカメラで撮影された画像が表示されます。
以上を繰り返し実行することで、カメラが撮影したフレームが連続的に画面に表示されることで、動画として確認することができます。
カメラの画像を画面に表示するだけのシンプルなサンプルプログラムは、以下のリンクで詳しく紹介しています。
・カメラ画像のJPEG変換
今回作成したカメラサーバーでは、撮影した画像を「JPEG」データに変換して送信しています。
カメラの画像をJPEG変換するプログラムは以下のようになります。
uint8_t *out_jpg; // JPEGデータのポインタ格納用
size_t out_len; // JPEGデータの長さ格納用
if (CoreS3.Camera.get()) { // カメラから新しいフレーム取得に成功したら
frame2jpg(CoreS3.Camera.fb, 100, &out_jpg, &out_len); // JPEG変換
}
カメラ画像をJPEGデータに変換する場合にも、まずは「CoreS3.Camera.get()」を実行します。
次に上コードの「5行目」のように、以下を実行することでJPEG変換を行います。
・カメラのフレームバッファ:「CoreS3.Camera.fb」で指定します。
・画質:「1〜100」で指定、数値が大きいほど画質は良くなりますがデータ量は増加します。
・JPEGデータのポインタ:JPEGデータの格納先アドレスのポインタ
・JPEGデータ長のポインタ:JPEGデータの長さ格納先のポインタ
カメラ画像のJPEG変換やJPEG画像の液晶画面への表示方法については、以下のリンクで詳しく紹介しています。
・ストリーミングカメラサーバー
ブラウザのアドレスバーに「IPアドレス」を入力すると「IPアドレス/stream」にリダイレクト(再接続)され以下の関数「handleStreamPath()」が実行されます。
// ストリーミング処理関数("IPアドレス/stream" アクセス時) *****************************
void handleStreamPath() {
WiFiClient client = server.client(); // 現在のクライアントの接続を取得
String response = "HTTP/1.1 200 OK\r\n"; // HTTPレスポンスの初めの行
response += "Content-Type: multipart/x-mixed-replace;boundary=frame\r\n\r\n"; // マルチパートでストリームデータを送信することと、区切りを「frame」とすることを宣言
server.sendContent(response); // マルチパートヘッダーを送信
// リクエストがある限りクライアントへレスポンスとしてJPEG画像を送信
uint8_t *out_jpg; // JPEGデータのポインタ格納用
size_t out_len; // JPEGデータの長さ格納用
while (true) { // 終了条件まで持続的にストリーミング
if (CoreS3.Camera.get()) { // カメラから新しいフレーム取得に成功したら
// フレームをjpegに変換してwebsocket送信
if(frame2jpg(CoreS3.Camera.fb, JPEG_QUALITY, &out_jpg, &out_len)) { // フレームをJPEGに変換し成功ならマルチパート形式でヘッダー設定
response = "--frame\r\n"; // 個々のフレームを判断する区切り文字を指定
response += "Content-Type: image/jpeg\r\n"; // Content-TypeにJPEGを指定
response += "Content-Length: " + String(out_len) + "\r\n\r\n"; // Content-Lengthに「out_len」を指定してヘッダー設定終了
server.sendContent(response); // マルチパートヘッダーを送信
// 液晶画面にフレームを表示
CoreS3.Display.pushImage(0, 0, 320, 240, (uint16_t *)CoreS3.Camera.fb->buf);
// JPEGデータ送信
client.write(out_jpg, out_len); // 変換されたJPEGデータをクライアントに送信
server.sendContent("\r\n\r\n"); // マルチパートの区切りシンボルを送信
free(out_jpg); // JPEGデータを格納していたメモリを解放
CoreS3.Camera.free(); // カメラのフレームバッファを解放
} else { // フレームをJPEGに変換できなかった場合
Serial.println("Failed to convert the frame to JPEG"); // エラー表示
}
} else { // フレームの取得に失敗したら
Serial.println("Camera capture failed"); // エラー表示
}
if (!client.connected()) { // クライアントが切断されたら
break; // ループを抜ける
}
delay(JPEG_UPDATE_TIME); // 送信するJPEG画像更新間隔(ローカルWi-Fi時は500推奨)
}
}
「4〜6行目」ではアクセスがあったクライアントへ「マルチパートヘッダー」を送信しています。
ヘッダ情報の区切りとして「frame」を設定しています。
「11行目」の「while文」の中でカメラの画像を、取得したフレームごとにJPEGデータに変換し送信し続けます。(クライアントとの接続が遮断されると終了するようにしています。)
「14〜18行目」でカメラ画像をJPEGデータに変換して、マルチパートヘッダーにJPEGデータを送信することと、JPEGデータの長さを指定して準備します。
「20,21行目」ではカメラの画像を本体の液晶画面に表示しています。
(それほど大きな負荷ではないようなので、無効にしても画像更新速度が早くなることはありませんでした。)
「23行目」でJPEG画像を送信しています。
「24行目」でパートの終了と次の開始を示すタグを送信します。
データを送信したら「25行目」のようにJPEGデータを格納していたメモリを解放します。
最後に「28行目」のようにカメラのフレームバッファを解放して、1フレームの送信が完了します。
「36〜38行目」ではクライアントからのアクセスが遮断された時に「while」ループを抜けるようにしています。
7.ブラウザモニターサンプルプログラム
ブラウザに画像を表示させるための「html」ファイルのサンプルも紹介しておきます。
下のサンプルプログラムをパソコンで「メモ帳」等のテキストアプリに貼り付けて、ファイル名は何でも良いので、拡張子を「.html」として保存してください。
保存した「html」ファイルを開くとブラウザが起動し、下画像のような画面が表示されるので、画面内のテキストボックスにカメラの「IPアドレス」を入力してエンターを押します。
ブラウザ画面に下画像のようにカメラ画像が表示されます。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。
<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CAMERA-MONITOR</title>
<style>
body{font-family: sans-serif; background-color: #222222; max-width: 480px; margin: 0 auto; align-items: center;}
h1 {color:#ffffff; text-align: center; font-size: 28px; margin: 10px auto;}
div {display: flex; flex-direction: row; justify-content: center; margin-top: 10px;}
</style>
</head>
<body>
<h1>CAMERA-MONITOR</h1>
<div>
<form id="id_form1" onsubmit="return startStream()">
<input id="input_IP" type="text" placeholder="カメラIPアドレス" />
</form>
</div>
<hr>
<div id="output"></div>
<script type="text/javascript">
// カメラ画像取得
const startStream = () => {
let target = document.getElementById("output");
let ip = document.forms.id_form1.input_IP.value;
console.log("Start Stream: http://" + ip + "/stream");
stream = "<img src='http://" + ip + "' width='100%' height='100%'>";
target.innerHTML = stream;
return false;
}
</script>
</body>
</html>
ブラウザで操作できる遠隔操作監視モニタについては以下のリンクで詳しく紹介しています。
8.まとめ
「COE S3」のカメラ画像をブラウザから確認する方法について詳しく紹介しました。
カメラ画像を本体の液晶画面に表示させるのは簡単でしたが、画像をストリーム配信するのは、仕組みの理解からでなかなか手間取りました^^;
今回は「Arduino」の「WebServer」機能を利用して、カメラの画像をフレーム単位で「JPEG」データに変換し、「HTTP通信」で1枚ずつ連続して送信するストリーミングカメラサーバーを使って実現しました。
パソコンやスマホのブラウザから「IPアドレス」を入力するだけでカメラ画像を確認することができますが、画面の更新頻度は通信環境により制限されます。
私の環境では通信速度が遅く、画質や更新速度を上げるとフリーズすることがよくありました。
ストリーミング画像の更新頻度については、お使いの環境に合わせて設定して動作確認してみてください。
今回はストリーミングカメラサーバーの1つの方法として実現できましたが、もっといい方法があるように思います・・・「WebSocket」通信ではやり方が悪いだけと思いますが、更新頻度はもっと遅かったので、今回は「HTTP通信」を採用しました。
「WebSocket」通信については、遠隔操作、監視を行う「IoT」には最適の方法と思いますので、もう少し学習して、いいものができたらまた公開したいと思います。
スポンサーリンク
コメント