ESP32でビットマップ画像ファイルを生成し、ブラウザに連続送信してMotion JPEGならぬMotion BMP動画ストリーミングする実験

ESP32,でビットマップ画像を生成し、Motion,JPEG,でブラウザに動画ストリーミングさせてみた。 ESP32 ( ESP-WROOM-32 )

ビットマップ(BMP)ファイル(拡張子 .bmp)の作り方

では、Motion JPEG をやる前に、ビットマップ画像の作り方を紹介します。
以下、Windows 10環境で説明します。

Motion JPEG (MJPEG)では、一般的にJPEG画像を扱いますが、JPEG画像というのは圧縮されていて、構成がとても複雑です。
私の様なアマチュアプログラマではまだまだ勉強不足で、敷居が高いのです。
というわけで、まずはビットマップ(BMP)画像から扱います。

ビットマップファイルのフォーマットはネット上で多くの情報があります。
私は碧色工房さんの以下の記事を参考にさせていただきました。

BMPファイルフォーマット(Windows)

まず、ビットマップファイルのフォーマットを理解するために、パソコンでピットマップファイルを一から作ってみます。
ここでは、RGB565方式を使い、4 x 1 pixel で、赤、緑、青、白という並びの、ごく小さい画角のビットマップファイルを作成してみます。

RGB565は当ブログで過去何度も紹介してきたとおり、1pixel当たり2byteのデータ量だけで済むので、できるだけ高速通信を目指せます。
解像度設定やカラーパレット設定はしません。
よって、画像の実データ直前までのヘッダは合計66byteです。

作成にはバイナリエディタを使います。
私はフリーのStirlingを使いました。
Vector等のサイトで入手できます。
一応、Windows 10 でも問題無く使えています。

バイナリエディタを起動したら、下図を参考にして16進数で数値を入力していきます。
ファイルサイズは
0x4A = 74 byte
あります。

注意していただきたいのは、このバイナリデータの末尾のアドレスが0x49なので、それがファイルサイズかと勘違いし易いのですが、0x49 + 1 = 0x4A が正しいです。
なぜなら、最初のバイトのアドレスがゼロから始まっているからです。

頭の66byte分がヘッダ情報で、上図のピンク色のところが実際の画像ピクセルデータです。
ヘッダ情報についてのザッとした解説は以下です。

  1. 英大文字”BM” 2byte表記
  2. ファイルサイズ 4byte表記
  3. 予約領域1(常にゼロ) 2byte表記
  4. 予約領域2(常にゼロ) 2byte表記
  5. 画像実データまでのオフセットバイト数(常に66 byte = 0x42) 4byte表記
    ————————————-
  6. 情報ヘッダサイズ(常に40 byte = 0x28) 4byte表記
  7. 画像幅(pixel数) 4byte表記
  8. 画像高さ(pixel数) 4byte表記
  9. 画像プレーン数(常に1) 2byte表記
  10. 色bit数(ここでは16 bit = 0x10) 2byte表記
  11. 圧縮形式(RGB565 16 bitの場合、3にする) 4byte表記
  12. 画像データサイズ 4byte表記
  13. X Pixels Per Meter(水平方向の解像度。使わない場合ゼロ) 4byte表記
  14. Y Pixels Per Meter(垂直方向の解像度。使わない場合ゼロ) 4byte表記
  15. Color Used(カラーパレット方式を使わない場合ゼロ) 4byte表記
  16. 重要色数(使わない場合ゼロ) 4byte表記
    ——————————————-
  17. カラーマスク(RGB565の場合、ここでは赤色マスク) 4byte表記
  18. カラーマスク(RGB565の場合、ここでは緑色マスク) 4byte表記
  19. カラーマスク(RGB565の場合、ここでは青色マスク) 4byte表記
  20. 実データ(※リトルエンディアン、つまり、下位バイトから先に書き込む)

冒頭の2バイトは、
「このファイルはビットマップ画像だよ!」
ということ表す “BM” という半角大文字をASCIIコード16進数で表したものです。
‘B’ = 0x42
‘M’ = 0x4D
となるので、書き込む順番はそのままですね。

次のファイルサイズから概ね4byte毎の区切りになります。
そして、リトルエンディアンで入力していくことになります。
リトルエンディアンという用語は、アマチュアには意味不明な用語だと思います。
私も最初は分らず、専門用語大嫌いだったので、読み飛ばしていました。

要するに、下図の様にただ単に下位バイトから先に入力していくことをリトルエンディアンというようです。

例えばファイルサイズが73byteだとすると、
73 = 0x49
となり、これは4byte(32bit)表記にすると、
0x00 00 00 49
という並びになりますが、これをリトルエンディアンにすると、下位バイトから先に入力していくので、
0x49, 0x00, 0x00, 0x00
という並びになります。
分かってしまえばそれほど難しくないですね。
もうかれこれ1か月以上もこの用語に触れていたので、今はもう慣れてしまいました。
こうやってド素人だった自分でも専門用語だらけになってしまうんだろなぁ。。。

次に、情報ヘッダサイズは、上記の6~16番までの合計バイト数で、40 = 0x28 となります。

13番~16番は、今回設定しないのでゼロとします。

カラーマスクについては、ちょっと難しいです。
今回扱うRGB565フォーマットの場合、1pixel が2byteデータです。
(RGB565については、こちらの記事でも少し述べていますので、参照してみてください。)
この2byteのカラーデータをカラーマスクによって、RGBの各値に分離する役目があります。

カラーマスクは4byteデータですが、RGB565の場合は下位の2バイト分だけ使います。
下図の様に赤色マスクの場合、RGB565データの赤色の部分の5bitだけ1になっています。
そのマスクと実際の色データをAND演算すると赤色部分だけ抽出されるというわけです。

このマスクの構成を変えるだけで、RGB888 やRGB444データも扱えるわけです。
それに、色の順番を入れ替えれば、RGB順をBGR順に色反転することもできるわけです。

それぞれのカラーマスクをリトルエンディアン並びにすると、下図の様になります。

そして、このリトルエンディアンにした値をバイナリエディタに入力していきます。

では、次に画像の実データ領域を説明します。

座標(0, 0) 位置のpixel が赤色ならば、
11111000, 00000000
というデータになります。
16進表記では、
0xF8, 0x00
となります。
それをリトルエンディアン並びにすると、
0x00, 0xF8
という順番で入力していきます。

同じく続けて座標(0, 1)位置のpixelが緑色ならば、
00000111, 11100000
となり、16進表記では、
0x07, 0xE0
となります。
リトルエンディアンにして、
0xE0, 0x07
とバイナリエディタに入力していきます。
これを続けて順番に入力行けば、画像データが出来上がります。
座標位置情報は入力する必要無く、ただ単にpixel色を順番に並べていくだけです。

ただ、注意してほしいのは、ビットマップ画像は通常の液晶ディスプレイの座標位置と異なり、下図の様に上下逆になっていて、左下から描画されていきます。
それを考えながら入力していく必要があります。

ということは、自分のように過去に何度も液晶ディスプレイを駆動してきた者としては、上下を反転させたくなりますね。
その場合、HTMLのスタイルシートで上下反転させてやればOKです。
その方法は後で述べます。

そして、もう一つ注意して欲しいのは、実データのデータサイズやファイルサイズ、画像幅などが矛盾していないか確認することです。
どれかを間違えると、画像は表示されません。
特に、バイナリファイルを直入力して、pixelを増やす時に、画像幅やファイルサイズ、データサイズの変更を忘れないように注意してください。
私は何度もやらかしました、、。

では、バイナリエディタで入力し終わったら、拡張子を.bmpにして名前を付けて保存します。
たとえば、
RGB.bmp
みたいな感じです。

そうしたらブラウザを起動して、ビットマップファイルをドラッグ&ドロップしてみます。
あるいは、URL入力欄にファイルのパスを入力しても良いです。
その場合は、¥マークは使わずにスラッシュ’/’にしてください。

(例)
file:///C:/Users/自分のユーザー名/Desktop/test/RGB.bmp

表示させたら、あまりにも画素が小さいので、500%に拡大して見てみます。
下図の様に小さいpixelが左から赤、緑、青、白の順で並んでいればビットマップファイルの作成成功です。

ちゃんと自分の意図した位置にカラーが表示されていればOKです。

ここまでできて、ビットマップファイルの作り方が理解できれば、Arduinoプログラミングは出来たも同然です。
ESP32やM5Stackを使ってビットマップファイルを自在に作ることができます。

ビットマップファイルのヘッダは様々な圧縮モードがあったり、カラーパレットを使う場合があったりして、いろいろ設定可能です。

では、次はArduino core for the ESP32でWiFiでブラウザにビットマップファイルを連続送信して動画ストリーミングする方法を説明します。

コメント

  1. 組み込みプログラマ より:

    mgo-tecさん、お久しぶりです。

    ESP32でカメラ画像の送信に興味を持って、調べていたらまたmgo-tecさんのページにたどり着きました。今回もいろいろ読ませて頂きました。ありがとうございます。
    記事を読ませて頂いたお礼に少しだけ気になった事を書かせていただきます。
    (いつもの事ですが参考にならなかったら無視して頂いて結構です。)

    UDPの説明で「これは、UDP規格の下位層で再送設定されているようで、UDPデフォルトの正常な動作のようです。」とありますが、UDPの規格では恐らく再送される事はないと思います。(私が四半世紀以上間違った知識を持っていたのでなければ100%あり得ないのではないかと思います。)
    なぜかと言うと、TCPではデータを送信すると受信側はそのデータを受け取ったという返事(ACK)を送信側に返信します。このACKが一定時間以内に送信側に返らないと、送信側はパケロスが発生したと考えて再送をするという仕組みになっています。
    しかし、UDPではデータを送信したらしっぱなしで、相手に届いたのかどうかは気にしません。なのでUDP層で再送を実施する方法自体がないと思われます。(少なくとも私がLinuxやWindowsでネットワークプログラミングをしていた頃はUDPは再送制御の無いプロトコルという認識でした。)
    又、「UDPはESP32やM5Stackなどの自作IoT機器間のハンドシェイク(コネクション確立)が簡単で実現しやすいのが利点です。ただ、パケットロスは避けられません。」とも書かれていますが、UDPにはそもそもコネクション確立という概念がありません。複数の機器がそれぞれ自分のUDPポートをオープンして、それらのポート間でお互いにデータを投げ込むだけなので、TCPのようなコネクションの確立フェーズはありません。(なので相手のポートがオープンしていなくても送信自体は正常にできてしまいます。)
    パケロスについては、一般的にUDPを使用して高信頼性の通信をする場合は、UDPポートを使用するアプリケーション層で受信時の到達確認の応答や、送信時の一定時間以内の返信が無い場合の再送をシーケンスとして考えて作成します。(送信側はデータ送信後に受信側からの返事を待ち、返事が来たら次のデータを送信する様にし、受信側はデータを受信したら返信をする様にして、TCPがやっている再送制御をアプリケーションが実施する様にします。)
    今回の様なケースでは、上記の様にデータの到達確認制御を入れる様にすれば、受信側の限界に近いパフォーマンスが出せるのではないかと思いました。(今回のケースでは受信側が取りこぼしている確率が高いと思われますので。)

    又、今回のソースを見ていて、ポート80と81のタスクを別々に作成して、コマンド受信側のポート(ポート80でしょうか)のプライオリティを高く設定すれば、ポート81のスレッドで画像の送信をしながらポート80でコマンドを受信した時に即座にコマンドに応答できる様に出来そうな気がしました。(Serverクラスがマルチタスクに対応していればの話になってしまいますが。)

    それでは、失礼します。

    • mgo-tec mgo-tec より:

      組み込みプログラマさん

      たいへんお久しぶりですね。
      2020年に書いたつたない記事をまたまた読んでいただき、ありがとうございます。

      私は現在、諸事情でほとんどESP32を触っておらず、このブログもしばらく放置状態です。

      さてさて、確かにUDPってコネクション確立とか、再送処理とか無いはずですね。
      ただ、うろ覚えですが、この記事を書いた当初は、UDPなのになぜか再送処理っぽい動きをしていたので、そういう想像で記事を書いた記憶があります。
      今思えば、組み込みプログラマさんがおっしゃる通り、Arduino core ESP32がアプリケーション層で何やら処理していそうな気がしますね。

      当時はUDPでの動画送信はどうやってもうまくいかず、Twitterでお世話になった「らびやん」さんから教えてもらい、結局はTCPで送信した方が確実で早くで発熱もしなかったのでした。
      もしかしたら、UDPでもうまくプログラミングすれば、TCPよりも良いかも知れませんね。
      ただ、今は残念ながら再検証する時間が無いのですが…。
      そんなこんなで、いつもいろいろ教えて頂き、ありがとうございます。
      記事も時間がある時に修正を入れたいと思います。
      m(_ _)m

      • 組み込みプログラマ より:

        mgo-tecさん、お返事を頂きましてありがとうございます。
        現在はEPS32を触っていないとの事ですが、せっかくここまで色々と調べたり作ったりしてきたのですから、また面白いプログラムを作ってプログに公開できる様になると良いですね。
        以前にも書きましたが、mgo-tecさんの書いたこのブログは色々な方に大変役立っていると思います。私もESP32を使って何かしようと調べるとよくこのブログの記事がヒットして、拝見させて頂いています。
        ある意味、mgo-tecさんの財産といっても過言ではないくらい素晴らしい内容だと思いますので、これからも運用して頂けると参考にする私たちにとっても大変有難いと思いました。
        私なども、色々と忙しかったりなんだか面倒くさくなってしまったりする事も何度もありましたが、そんな時は少し手を休めて距離を置いたりして、またやりたくなった時に再開する様にしていました。
        mgo-tecさんも、またESP32やその他ガジェトなどを使った面白いプログラムを作れる様な環境になる事を祈っています。
        それでは、失礼します。

        • mgo-tec mgo-tec より:

          組み込みプログラマさん

          とてもありがたいお言葉、感謝感謝です。
          多くの読者に役に立っているのなら嬉しい限りです。
          でも、いろいろと未熟者で、今は反省ばかりですが…。

          ブログ休眠中でも、コメント投稿で問合せがあり、ブログ運営の大変さを今さら感じています。
          今は本業と生活が厳しく、暇が全く無い状態なのです。
          ブログ運営だけで生活できるだけの報酬が出れば、是非ともブログを再開したいんですけどね~…。
          それは無理としても、いつか生活に余裕ができたら再開したいと思っています。

          そんなわけで、また何かお気づきの点がありましたら、コメント頂けると幸いです。
          うれしいコメントありがとうございました~。
          m(_ _)m

タイトルとURLをコピーしました