まえがき
この記事は以下の「容体判定の掌握 -概論- 【レスキューロボットコンテスト2025】」の続きです.まだその記事を読んでいないのであれば,そちらの記事を先に読んでからこちらの記事を読むことを推奨します.前回は容体判定の概論でしたが,今回はそれ以外の概論というイメージです.
【前回の記事】
プログラム解説は次回以降にして,この記事では,プログラムの設計やGUIの設計の思想,生成AIの利用方法を説明します.
また,前回でも話しましたが,プログラム開発はC++を独学した機械系学科所属の私一人で行ったため,情報学部生が育む常識や知識を備えていない可能性があり,そんな方々には読みにくい文章である可能性があることをご了承ください.
設計思想
まず,設計思想について説明します.「そんなモン説明せずにさっさとプログラムの解説しろ」と思われるかもしれませんが,結構大事な概念です.おそらく,レスコンへ出場するためにプログラムを作成しようとしている人はプログラミング初心者が大半だと思います.設計思想を抜きにして,レスコンに使用される大きなプログラムを作ると,途中までは問題ないかもしれませんが,最終局面までに破綻するでしょう.
モジュール化しよう
プログラムは機能ごとに細分化したほうがいいです.例えば,映像通信に関する機能,音声通信に関する機能,QR読取りに関する機能,音声解析に関する機能などをそれぞれ異なるヘッダーファイル内に実装するということです.レスコンのように複数の機能が集まる巨大なプログラムを一つのソースファイルにコーディングすることは非常に大変です.私のいくつか上の世代はそれやっていたらしいです. それぞれのヘッダーファイルを寄せ集めたらレスコンプログラムがほぼ完成していることが理想と思います.
大会で使用したプログラムの構成
それではここでモジュール化の具体例として,私が開発したプログラムのファイル構成を見てみます.フレームワークはSiv3Dを活用しています.以下のように21のファイルで構成されていることがわかります.
| ファイル名 | 説明 |
|---|---|
| stdafx.hpp | プリコンパイル済みヘッダです.OpenCVやTPIPライブラリなどの外部ライブラリをここでincludeします.includeの順番が不適切だと,リンカーのエラーが出ることがあります. |
| DeviceConfig.hpp | ラズパイやTPIPなどの通信機器の種類やパソコンのIPアドレス,UDP通信をする際に使用するポート番号などの通信に関する情報をここで一括で宣言します.これらの情報は接続画面に表示される表の各セルの情報を含みます. |
| DeviceFactory.hpp | 通信機器に応じて使用するクラスを指定する関数が宣言されています.このプログラムの拡張性の要です. |
| GUI.hpp | GUIに用いるプルダウンやラジオボタン,スライドボタンをここで宣言しています.また,通信接続画面にて表示される表もここで宣言しています. |
| Controller.hpp | ゲームコントローラから受信した情報を処理し,通信機器にコマンドを送信します. |
| ControllerDrawer.hpp | ゲームコントローラからの入力状況をGUIに可視化します. |
| AudioConfig.hpp | 音声解析に必要なサンプリングレートや音声解析の精度を決める定数,FFTする周波数の最小値,最大値を宣言しています. |
| ISoundReceiver.hpp | 多態性を用いて,ラズパイとTPIP4の音声受信のクラスを区別せず取り扱えるように純粋仮想関数を持つ抽象基底クラスを宣言しています.拡張性の要であり,TPIP5が今後導入されても,そのクラスを継承することで,Main.cppを大きく修正する必要がありません. |
| Sound_rsp.hpp | ISoundReceiver.hppにて宣言された音声受信クラスをラスパイ用に継承しています. |
| Sound_tpip4.hpp | ISoundReceiver.hppにて宣言された音声受信クラスをTPIP4用に継承しています. |
| SignalDetector.hpp | BiquadFilterを用いて受信した音声の低音高音のノイズを除去し,窓関数を用いて解析精度を上げたあと,後述のfft.hppやGoerztel.hppに音声を渡します. |
| fft.hpp | 受信した音声をFFTする関数が宣言されています.操作画面のカメラ映像上に表示される波形のデータを求められます. |
| Goerztel.hpp | Goerztelのアルゴリズムが実装されており,12音階の周波数のスペクトルの履歴を求めるデータを求められます. |
| SignalVisualizer.hpp | fft.hppやGoerztel.hppにて求めたデータを用いて,最終的にGUIにて表示される具体的な波形を描画します. |
| IVideoReceiver.hpp | 多態性を用いて,ラズパイとTPIP4の映像受信のクラスを区別せず取り扱えるように純粋仮想関数を持つ抽象基底クラスを宣言しています.拡張性の要であり,TPIP5が今後導入されても,そのクラスを継承することで,Main.cppを大きく修正する必要がありません. |
| Video_rsp.hpp | IVideoReceiver.hppにて宣言された映像受信クラスをラズパイ用に継承しています. |
| Video_tpip4.hpp | IVideoReceiver.hppにて宣言された映像受信クラスをTPIP4用に継承しています. |
| Video_draw.hpp | 受信した映像をGUIに描画します.8色フィルタの機能もここに実装されています. |
| QRread.hpp | 映像内のQRを読取る機能が実装されています. |
| Main.cpp | Main関数があり,上記のヘッダファイルを取りまとめたレスコンプログラムの具体的なロジックが記載されています. |
| stdafx.cpp | プリコンパイル済みヘッダをincludeしています. |
モジュール化のメリット
モジュール化のメリットには,以下のことが挙げられます.
①開発しやすい
②拡張しやすい
③引継ぎしやすい
モジュール化してあれば,開発中にバグが発生してしまった場合,どこにバグがあるのかを特定しやすくなり,「この機能の性能をより向上させたいな」と思ったときにも,どのプログラムを修正すればいいのかがわかりやすくなります.例えば,QR読取りの性能を高めたい場合,その機能を実装しているヘッダーファイルをいじるだけでよくなります.私はこのおかげで何度も命拾いしています.
また,新しく機能を増やしたい場合,既存のファイルのようにヘッダファイルを新規作成すればよいです.加えて,引継ぎを前提として活動しているチームの場合,どの機能がどのファイルに記載されているのかが明確になるため,引継ぎもしやすくなります.悪いとこ無しです.
拡張性が高いと今後の開発に便利
ISoundReceiver.hppやIVideoReceiver.hppのように純粋基底クラスを継承すると,今度,TPIP5のような新しい通信機器を使用しなければならなくなったとき,Video_rspやSound_rspのようにVideo_tpip5やSound_tpip5を新規作成するだけで,ほとんどMain.cppを修正せず,プログラムを使いまわすことができるようになります.C++の勉強をしていると,そこそこ後半に出てくる概念であり,大きなプログラムを作成した経験がないと恩恵を感じられないかと思いますが,知らなかった人は勉強すると良いと思います.
それだけでなく,開発中に突然使用する通信機器を変更するなんてこともあるかもしれません.これに対処するため,DeviceConfig.hppで使用する通信機器の種類やIPアドレス,ポート番号を一括で管理し,それを元に,使用する通信機器に応じてDeviceFactory.hppを用いて使用するクラスを振り分けることをしています.実際に,DeviceConfig.hppに記載されている通信機器の種類を,例えば,ラズパイからTPIP4に書き換えプログラムをビルドすると,瞬時にその変更が反映されます.
ここではDeviceConfig.hppにIPアドレスなどの情報を記載していますが,その変更を反映させるにはプログラムを毎度ビルドする必要があります.より拡張性を高めたいのであれば,CSVファイルを新規作成して,その中に通信機器に関する情報をすべて記載し,プログラムでそのファイルを参照させたほうが毎度ビルドをする必要がなくなり便利です.変更があるなら,そのCSVファイルを修正するだけです.Siv3DにはCSVクラスが存在し,CSVファイルを取りつかうことは比較的容易ですが,実用上.hppでも問題ないならそれでもいいと思います.
バージョン管理
私は1人で開発していたため,特にGitHubやBitbucketなどのようなプログラム管理プラットフォームは使用していませんでしたが,やれるならやっておいたほうがいいですし,複数人で開発をするならほぼ必須と思います.
私は本選3日前に,QR読取りの性能向上のためにプログラムをいじっていたとき,あるライブラリを試してみるべく,環境構築をしたところ,既存のプログラムをビルドできなくなり,非常に肝を冷やしました.数時間格闘した後,結局,ある設定をオフにすることでこの問題を回避しましたが,いまだにトラウマです.
あくまでも余力があればですが,バージョン管理を行うことを強く推奨します.それに,バージョン管理をすれば,ああでもない,こうでもないと試行錯誤することも楽になります.
見やすいGUIについて
GUIの設計も奥が深く,それに関する数多くの書籍や動画が公開されており,独学はしやすいです.しかし,資料によって思想が異なり,例えば,ハンバーガーマークを推奨するものがあれば,外道と評価するものもあります.そんなときは使いやすさを重視してください.GUIは見やすければ,使いやすければ正義です.
GUIのデザインの考案よりも優先するべきは画像認識や音声解析の精度の向上です.そのため,GUIのデザインを突き詰めるのは開発に余力のある場合に限ります.
【参考】
レスコンで使用したGUI
レスコンで使用したGUIを紹介します.前回の記事でも紹介しましたが,以下のURLから,そのGUIが動作している様子を確認できます.
【参考】
画像を添えて説明します.
通信接続画面
一枚目はロボットとの通信を開始するGUIです.プログラム起動直後に表示されます.左上のラジオボタンから無線通信と有線通信を切り替えることができます.本選会場の控室では無線通信ができないので,有線通信にも対応できないと厳しいです.
中央の表にはラズパイやTPIP4,通信先のパソコンのIPアドレスが記載されています.ssh接続する際に役立てられますし,パソコンのIPアドレスの確認もここでできます.
仕様上,一つのパソコンで複数のTPIP4と接続することができない一方,ラズパイは複数接続が可能です.そのため,「通信機種」がラズパイだと「接続」のボタンがチェックボックスであり,TPIP4だとラジオボタンになっています.2つのボタンの違いは各自調べると良いですが,簡単に言うと,チェックボックスは同時に複数選択できるようにするとき使用し,ラジオボタンは複数ある選択肢のうち一つのみ選択できるようにするとき使用します.いろいろUIにはお作法があるようです.
ロボット操作画面
2枚目はロボットを操作する画面です.各部位の説明をしてあるのが3枚目の画像です.
3枚目には,現在操作しているロボットに関する情報を赤っぽい色で「メイン関連」として示し,通信接続しているものの操作はしていないロボットの情報を緑っぽい色で「サブ関連」として示しています.この画像ではサブ関連は1つだけですが,2つ以上に増やすことも可能です.
メイン関連には,ロボット名の下に,操作しているロボットのカメラの映像が大きく表示され,その上にマウスカーソルを合わせると,現在そのロボットが拾っている音声をFFTした結果が表示されます.その右側に特定の周波数のスペクトル強度の履歴が表示されます.詳しい説明は前回の記事を参照して下さい.そのほかにも,映像・音声の通信状況,コントローラの入力情報,カメラ切替,QR読取結果,8色フィルタ,ロボットの最大移動速度変更に関するUIが表示されています.
サブ関連には,ロボット名の下に,ロボットのカメラ映像,映像・音声の通信状況が表示されています.
設計方法
PowerPointを活用する
ボタンの位置やアイコンの位置を設計するときはPowerPoint(以下「パワポ」という)を使用しました.
プログラミングで座標を扱うと,理想の位置の座標が一体いくつなのか判断が難しく,手探りで座標を決めるしかありません.そのため,理想の位置にボタンがあるのかどうかはビルドするまで確認できず,微調整に手間が生じます.また,開発を行っているパソコンと実際にプログラムを動かすパソコンが異なることはよくあると思います.すると,解像度やアスペクト比,拡大率によって,ボタンなどの位置がずれるかもしれません.しかし,この問題はパワポを使用すればほぼ解決できます.
パワポのスライドがパソコンに写る範囲とします.下の図のように,スライドの縦横に表を貼ると,目盛り代わりになります.ここでは,公約数を多く持つように縦横ともに12目盛りくらいにしておきます.
Siv3Dは画面の左上が原点であり,右方向がx軸の正方向,下方向がy軸の正方向です.目盛りにより座標が決定でき,例えば,メインカメラの左上の頂点は (4, 1) であることが画像から読み取れると思います.Siv3Dなら画面の解像度(幅と高さのサイズ)求められるので,例えばそれが (1920, 1080) であるとき,プログラム上でメインカメラの左上の座標を表現すると
( 1920×\frac{4}{12}, 1080×\frac{1}{12} ) = ( 640, 90 )
と表現できます.
このように,頭の中だけで (640, 90) という座標を導くのは困難である一方,パワポを使用すると,目盛りによる座標さえわかればプログラム上での座標に変換できます.これはパワポのスライドがどのパソコンから見てもほぼ同じように見えることを利用したものですが,著しくパソコンの画面が一般的なアスペクト比より乖離していると,GUIがはみ出てしまうことがあり,それには注意が必要です.また,位置だけでなく,図形の大きさもパワポの目盛りいくつ分であるのかを数え,プログラム基準に変換すると,パソコンによって,図形の大きさが異なってしまうことがほぼなくなります.
オペレータに対する優しさ
私のGUIのコンセプトは「スッキリ最小限,オペレータに必要な情報だけ」です.できるだけ文章を書かず,アイコンやボタンだけで示したほうがスッキリしますし,どの情報に注目するべきなのかも明確になります.映像・音声受信状況はアイコンの形状や色の変化で示していますし,ラジオボタンやスライドボタンは目立つ色にしています.クリックできるボタンは明らかに目立たせると無難です.詳しくは勉強してください.
また,ショートカットキーを用意することもおすすめします.例えば,操作するロボットの切替はキーボードの数字キー,カメラの切替はTabキーで行えるようにしています.すべてにショートカットキーを用意する必要はないかと思いますが,頻繁に行う操作には用意したほうが良いです.実際にオペレータからのウケは良かったです.たとえUIの見た目が良くても,UXが悪ければ悪いプログラムです.
生成AIを上手に使いこなすために
正直,私の素のプログラミング能力は,自力でクラスを実装できるところまでであり,画像認識,音声解析の知識は深くありません.そのような専門的な知識や技術は生成AIに頼りました.
どの生成AIを使用したか
開発初期から中盤は無料版のChatGPTを使用し,本選までの終盤は有料版Gemini(学生なら無料)を使用していました.2025年6月くらいの評価ですので,現在とは異なるかと思いますが,圧倒的に有料版Geminiのほうが賢いです.QR読取精度向上はこれのおかげです.
生成AI使用のコツ
やみくもに生成AIにプロンプトを与えてはいけません.コツがあります.
モジュール化
これはプログラム設計の話でも出てきました.実装したいプログラム内容を細分化しましょう.同時に2つ以上のことを実装させてはいけません.映像通信だけ,音声通信だけというように一つだけに集中させましょう.そうしないと,こちらの保守が難しくなると同時に,出力内容の質も落ちます.
前提・目的の共有
QR読取や音声解析がどのような環境で行われるのか,詳細に説明したうえでプロンプトを与えましょう.また,何ができればゴールなのかも教えましょう.そうでないと,的外れな出力をされます.また,ライブラリを使用する場合はバージョンの情報や,OSのバージョンの情報も必要かもしれません.
正式名称・専門用語の使用
生成AIに頼り切りだと,良い出力はされません.すべてを理解する必要はありませんが,専門用語くらいは押さえておく必要はあります.例えば,デジタル信号処理の教科書に目を通し,窓関数やローパスフィルター,ハイパスフィルター,Goerztelアルゴリズム,FFTなどといった専門用語だけでも押さえないと,曖昧なプロンプトしか書けず,曖昧な音声解析のプログラムしか出力されません.生成AIによってプログラミングのハードルは下がりましたが,結局,勉強は必要です.アルゴリズムの選定を行ったあと,「よし,あとはプログラムに落とし込むだけだ」という状況になれれば,勝ちです.
次回予告
次回からやっとプログラム解析に入ります.しかし,非常にクリティカルな内容であるため,サークル内でも公開が渋られています.そのため,公開までに非常に時間がかかる,あるいは公開はされないかもしれません.




