上手。新しい実験について話す時が来ました。彼がそれをどのように集めたか、何が起こったのか、そしてそれをどのように繰り返すか。
私はある意味で平凡な出来事によって新しいプロジェクトに促されました-息子が生まれました。 1ヶ月前に自分で休暇を取りました。しかし、子供は静かであることが判明しました-自由な時間がありました。そして、彼の隣に眠りを置きます。
多くの異なる家コンピュータビジョン用の組み込みハードウェア。その結果、私はビデオ乳母を作ることにしました。しかし、すべての店がいっぱいであるほど退屈ではありません。そして、よりスマートで興味深いもの。
記事は、おもちゃの開発がどのように進んだのか、どこに来たのか、そして次にどこに行くのかを理解するために、物語のように書かれます。
この記事にはいくつかの追加があります。
- すべてがどのように機能するかを示して説明するビデオ。
- VCに関する小さな記事で、そのようなことが通常の生産に至らない可能性が高い理由と、この種のMLシステムの制限について説明します。
- githubのすべての並べ替え+ RPi用の既製の画像。記事の最後に、それを使用する方法の説明。
アイデアの選択
ベビーモニターの最も一般的な機能は、いつでも子供に何が起こっているかを確認することです。残念ながら、これは常に機能するとは限りません。あなたはいつも放送を見るわけではないでしょう、それは便利ではありません。赤ちゃんは一般的に繭の中で近くで眠ることができます、なぜいつもビデオですか?その結果、最初に次のコレクションがまとめられました。
- システムは、電話からいつでもビデオや写真を見ることができるようにする必要があります
- システムは、子供の目覚めに応答し、それについて通知する必要があります
- システムは、SIDSを防ぐために、欠落している面を検出する必要があります
プラットフォームの選択
さまざまなプラットフォームの比較について、Habréに関する長い記事がありました。世界的に、私が行っているようなプロトタイプには、いくつかのオプションがあります。
- Jetson Nano. + ( Nano), , . . — TensorRT. . , , , TensorRT .
- VIM3. , . — .
- Raspberry PI + Movidius. . , , .
- , .
- . .
- Raspberry PI 4-OpenCVを介して作業する場合は、オープンネットワークを破棄することをお勧めします。これで十分です。しかし、十分なパフォーマンスが得られないのではないかという疑いがありました。
- コーラル-私はそれを手にしています、それはパフォーマンスの面では合格ですが、私の他の記事は私がそれを好きではない理由を述べています:)
そこで、Rpi + movidiusを選びました。私はそれを手元に持っています、私はそれで働くことができます。
鉄
コンピューターはRaspberryPi 3B、ニューロプロセッサーはMovidius MyriadXです。これは明らかです。
残り-バレルの底に沿って削り、追加で購入しました。
カメラ
私が持っていた3つの異なるものをチェックしました:
- RaspberryPIのカメラ。うるさくて不便なケーブル、便利な取り付けがありません。得点。
- ある種のIPカメラ。RPIに含める必要がないため、非常に便利です。カメラはコンピューターから分離されています。私のセルには、昼と夜の2つのモードがありました。しかし、私が持っていたものは、顔の十分な品質を与えませんでした。
- GeniusのWebcam。すでに5年ほど使っていますが、最近不安定になっていますが、RPIとしてはちょうどいいです。さらに、簡単に分解して、そこからIRフィルターを取り外すことができることがわかりました。さらに、後で判明したように、これがマイクを備えた唯一のオプションでした。
そして、フィルターは次のように変化します
。一般に、これが製品ソリューションではないことは明らかです。しかし、それは機能します。
どちらかといえば、コードには、他の2種類のカメラに切り替えるための残りの部分が表示されます。おそらく、1〜2個のパラメーターを変更すると、何かが完全に機能するでしょう。
点灯
私は古い問題の1つでイルミネーターを横に置いていました。
なんらかの電源をはんだ付けしました。よく輝いています。
天井に向けてください-部屋は明るくなっています。
画面
一部の操作モードでは、モニターが必要でした。これで止まりました。これが正しい決定かどうかはわかりませんが。たぶん私はフルレングスのものを取るべきだった。しかし、それについては後で詳しく説明します。
栄養
子供は任意の場所で眠ります。そのため、システムがパワーバンクから電力を供給されている場合は簡単です。私はこれを選びました、それは単にそれがハイキングのために家にあるという理由で:
OpenVino
OpenVinoを少し見てみましょう。上で述べたように、OpenVinoの大きな利点は、事前にトレーニングされたネットワークが大量にあることです。私たちに役立つものは何ですか。
顔検出。OpenVinoにはそのようなネットワークがたくさんあります。
顔の要点の認識。次の
顔の向きのネットワークを起動するためにこれが必要です。子供の活動と彼が探している場所。
視界認識の方向-
深さ分析を相互作用させようとした場合?たぶんそれは
スケルトン分析になるでしょ
うまあ、他にもたくさんの興味深いものがあります...
これらのネットワークの主な欠点はそれらの主な利点です-事前トレーニング...
これは修正できますが、今私たちは簡単なプロトタイプを作っています、私たちの目標は100%のケースで機能することではありませんが、少なくともいくつかの利点。
行く。一般的なロジックバージョン1
組み込みデバイスを開発しているので、何らかの方法でそれと対話する必要があります。写真/アラーム信号を受信します。それで、私はテレグラムを介して、トラフをしたときと同じことをすることにしました。しかし、覚えておいてください。
最初のバージョンでは、次のことを決定しました。
- 指定されたネットワークをRPiで起動します(すべてを一度に実行したいのですが、突然パフォーマンスが可能になります)。これにより、問題を解決するためのより多くのオプション/開発の可能性のある方法を確認できます
- 一般的なプログラムテンプレートを作成します。
- 目覚めを認識するアルゴリズムを考え出します。
- 顔の喪失に関する通知を送信するアルゴリズムを作成します
たくさんのバグを除けば、すべてが多かれ少なかれうまくいきました。これはComputerVisionに固有のものです...私はそれに慣れています。
これが私が出くわしたものの簡単な要約です:
- OpenVino RPi ( 2020) - from openvino.inference_engine import IECore. OpenVino ( OpenCV ), , .
- OpenVino , -generate_deprecated_IR_V7
- OpenVino ( , ) Movidius int 8 . int32 . RPi int8 . , .
- OpenVino . , OpenVino . , — .
- OpenVino , Intel ( , ).
- PyTorch 1.5 onnx, 1.4…
しかし、これがその方法です... TensorRTを実行した場合、いつものように、さらに多くの問題が発生すると確信しています。
そう。すべてがまとめられ、ネットワークが実行され、次のようなものが得られます(スタックを頭、向き、キーポイントの上に実行する
ことによって):子供が手で覆ったり頭を回したりすると、顔が失われることがよくあります。すべての指標が安定しているわけではありません。
次は何ですか?眠りに落ちることを分析する方法は?
私はそれらのグリッドを見て、最初に頭に浮かぶのは感情を認識することです。子供が眠っていて静かなとき、彼の顔には中立的な表情があります。しかし、それはそれほど単純ではありません。これは紺色のグラフです。これは1時間眠っている子供のニュートラルな表現です。
残りのグラフは悲しい/怒っている/喜び/驚きです。色のどこにあるのかという本質すらありません。残念ながら、ネットワークデータは不安定であり、これが私たちの目に見えるものです。不安定性は次の場合に発生します。
- 顔の過度の影(夜は珍しいことではありません)
- 子供の顔はOpenVinoトレーニングセットにありませんでした=>他の感情への任意の切り替え
- 子供は夢の中も含めて実際に顔を作ります
全体的に、私は驚きませんでした。私は以前に感情を認識するネットワークに遭遇しました、そしてそれらは感情間の移行の不安定さのためを含めて常に不安定です-明確な境界はありません。
さて、目覚めは感情の助けを借りて認識できません。今のところ、自分で何かを教えたくなかったので、同じネットワークをベースにして、反対側で試してみることにしました。ネットの1つは頭の回転角度を与えます。これはすでに優れています(カメラを時間単位で見ることからの総偏差)。目覚める前の最後の5-10分:
より良い。しかし...息子は眠っている間に頭を振り始めるかもしれません。またはその逆に、大きなしきい値を設定した場合は、目を覚まし、その後頭を振らないでください。毎回通知を受け取るには...悲しいことに:(
約1時間のスリープ時間があります)
したがって、通常の認識を行う必要があります。
バージョン1で発生した問題
最初のバージョンで気に入らなかったものをすべて要約しましょう。
- 自動スタート。このおもちゃを毎回再起動し、SSH経由で接続し、監視スクリプトを実行するのは便利ではありません。この場合、スクリプトは次のことを行う必要があります。
- カメラの状態を確認してください。カメラの電源がオフになっているか、接続されていないことがあります。システムは、ユーザーがカメラの電源を入れるのを待つ必要があります。
- アクセラレータのステータスを確認しています。カメラと同じです。
- ネットワークをチェックしています。家でも田舎でも使いたいです。または多分どこか。繰り返しになりますが、sshを介してログインしたくありません=>インターネットがない場合にwiFiに接続するためのアルゴリズムを作成する必要があります。
- 目を覚ます、ネットワークトレーニング。単純なアプローチは導入されていません。つまり、開いた目を認識するようにニューロンをトレーニングする必要があります。
自動スタート
一般に、自動実行スキームは次のとおりです。
- 私は最初にプログラムを起動します。方法-RPiでそれを行うのは簡単だと言うのではなく、別の記事を書きました。要するに:
- OpenVino
- , —
- Movidius-
-
- — QR- wifi
- telegram . — QR-
ハハハ。ネットワークはすでに登場しています。しかし、結局のところ、私が開発を始めて初めて発売されました。そして、リリースとドキュメントでは、私が多かれ少なかれすべてをしたときにすでに現れました。今、私は記事を書いていて、更新を見つけました。
でもやり直しはしないので、やり直します。
このようなネットワークをトレーニングするのは非常に簡単です。上で、私はフレームによる目の選択を使用したと言いました。何も残っていません:フレームで出会ったすべての目の保存を追加します。そのようなデータセットが判明しまし
た。マークを付けてトレーニングする必要があります。ここでマーキングプロセスについて詳しく説明しました(そしてここで10分間のプロセスのビデオをご覧ください))。マーキングにはトロカを使用しました。タスクの設定には約2時間、マークアップには5分、予算の300ルーブルかかりました。
学ぶとき、私はあまり考えたくなかったので、問題を解決するのに十分な品質の意図的に高速なネットワーク、mobilenetv2を使用しました。データセットのロード、初期化、保存を含むコード全体は、100行未満で完了しました(ほとんどがオープンソースから取得され、数十行を書き直しました)。
隠しテキスト
import numpy as np
import torch
from torch import nn
from torch import optim
from torchvision import datasets, transforms, models
data_dir = 'F:/Senya/Dataset'
def load_split_train_test(datadir, valid_size = .1):
train_transforms = transforms.Compose([transforms.Resize(64),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
])
test_transforms = transforms.Compose([transforms.Resize(64),
transforms.ToTensor(),
])
train_data = datasets.ImageFolder(datadir,
transform=train_transforms)
test_data = datasets.ImageFolder(datadir,
transform=test_transforms)
num_train = len(train_data)
indices = list(range(num_train))
split = int(np.floor(valid_size * num_train))
np.random.shuffle(indices)
from torch.utils.data.sampler import SubsetRandomSampler
train_idx, test_idx = indices[split:], indices[:split]
train_sampler = SubsetRandomSampler(train_idx)
test_sampler = SubsetRandomSampler(test_idx)
trainloader = torch.utils.data.DataLoader(train_data,
sampler=train_sampler, batch_size=64)
testloader = torch.utils.data.DataLoader(test_data,
sampler=test_sampler, batch_size=64)
return trainloader, testloader
trainloader, testloader = load_split_train_test(data_dir, .1)
print(trainloader.dataset.classes)
device = torch.device("cuda" if torch.cuda.is_available()
else "cpu")
model = models.mobilenet_v2(pretrained=True)
model.classifier = nn.Sequential(nn.Linear(1280, 3),
nn.LogSoftmax(dim=1))
print(model)
criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.003)
model.to(device)
epochs = 5
steps = 0
running_loss = 0
print_every = 10
train_losses, test_losses = [], []
for epoch in range(epochs):
for inputs, labels in trainloader:
steps += 1
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
logps = model.forward(inputs)
loss = criterion(logps, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if steps % print_every == 0:
test_loss = 0
accuracy = 0
model.eval()
with torch.no_grad():
for inputs, labels in testloader:
inputs, labels = inputs.to(device), labels.to(device)
logps = model.forward(inputs)
batch_loss = criterion(logps, labels)
test_loss += batch_loss.item()
ps = torch.exp(logps)
top_p, top_class = ps.topk(1, dim=1)
equals = top_class == labels.view(*top_class.shape)
accuracy += torch.mean(equals.type(torch.FloatTensor)).item()
train_losses.append(running_loss / len(trainloader))
test_losses.append(test_loss / len(testloader))
print(f"Epoch {epoch + 1}/{epochs}.. "
f"Train loss: {running_loss / print_every:.3f}.. "
f"Test loss: {test_loss / len(testloader):.3f}.. "
f"Test accuracy: {accuracy / len(testloader):.3f}")
running_loss = 0
model.train()
torch.save(model, 'EyeDetector.pth')
そして、モデルをONNXに保存するためのさらに数行:
隠しテキスト
from torchvision import transforms
import torch
from PIL import Image
use_cuda=1
mobilenet = torch.load("EyeDetector.pth")
mobilenet.classifier = mobilenet.classifier[:-1]
mobilenet.cuda()
img = Image.open('E:/OpenProject/OpenVinoTest/face_detect/EyeDataset/krnwapzu_left.jpg')
mobilenet.eval()
transform = transforms.Compose([transforms.Resize(64),
transforms.ToTensor(),
])
img = transform(img)
img = torch.unsqueeze(img, 0)
if use_cuda:
img = img.cuda()
img = torch.autograd.Variable(img)
list_features = mobilenet(img)
ps = torch.exp(list_features.data.cpu())
top_p, top_class = ps.topk(1, dim=1)
list_features_numpy = []
for feature in list_features:
list_features_numpy.append(feature.data.cpu().numpy())
mobilenet.cpu()
x = torch.randn(1, 3, 64, 64, requires_grad=True)
torch_out = mobilenet(x)
torch.onnx.export(mobilenet, x,"mobilnet.onnx", export_params=True, opset_version=10, do_constant_folding=True,
input_names = ['input'],output_names = ['output'])
print(list_features_numpy)
Open Vinoでモデルをさらに呼び出すには、モデルをONNXに保存する必要があります。私はint8に変換することを気にせず、32ビット形式のままモデルを残しました。
精度、品質指標の分析?..なぜそれがアマチュアプロジェクトにあるのですか?そのようなものは異なって価格設定されます。「システムが機能している」ことを示すメトリックはありません。システムが機能するかどうかにかかわらず、実際に理解するだけです。エラーが1%でも、システムの使用が不快になる可能性があります。私はたまたま反対です。20%のエラーと同様ですが、システムはそれらが表示されないように構成されています。
これらのことは、実際には「機能するかどうか」を確認するのが簡単です。そして、作業の基準をすでに理解している-必要に応じてメトリックを入力する。
バージョン2の問題
現在の実装は質的に異なりますが、それでもいくつかの問題があります。
- . , :
- - ⅓ .
- . . , , . , .
- . ?
?
私は顔の検出を再訓練しませんでした。目の認識とは異なり、これははるかに多くの作業です。そして、データセットの収集と質の高いトレーニングを行います。
もちろん、あなたはあなたの息子の顔でそれをすることができます、おそらく現在のネットワークより少しでもうまくいくでしょう。しかし、残りの人々にとっては、違います。そして、おそらく、私の息子にとっては2か月で-そうではありません。
通常のデータセットの収集には長い時間がかかります。
音
音認識の古典的な道をたどり、ニューロンを訓練することは可能でしょう。一般に、それはそれほど長くはなく、せいぜい目の認識よりも数倍長くなります。しかし、データセットの収集に煩わされたくなかったので、より簡単な方法を使用しました。既製のWebRTCツールを使用できます。数行で、すべてがエレガントでシンプルであることがわかります。
私が見つけた欠点は、ソリューションの品質がマイクごとに異なることです。どこかがきしむ音でトリガーされ、どこかで大声でのみトリガーされました。
どうぞ、他に何が
ある時点で、妻と一緒に自分のループした5秒間のビデオを実行して、テストを実施
しました。息子が視野内の人々の顔にくっついていることは明らかでした(モニターが30分間彼を吊るしました)。そして、そのアイデアが生まれました:顔の表情をコントロールすること。これは単なる静的ビデオではなく、インタラクションオプションです。それは次のようなものになりました(息子の感情が変わると、ビデオシーケンスが切り替わります):
「お父さん、あなたはクソクソですか?!」
おそらく大きなモニターで試してみるべきでしょう。しかし、私はまだ準備ができていません。
たぶん、再生中のビデオを置き換える必要があります。幸い、それは簡単です。ビデオは別々の画像から再生され、フレームの変更はFPSに合わせて調整されます。
たぶんあなたは待つ必要があります(現在のレベルでは、子供は単に彼の感情と画面の関係を理解していないかもしれません)
その後?
最も有望な方向性の1つは、私には、ビュー/ポーズの方向を通していくつかの物理的なオブジェクト/ライト/モーターを制御しようとすることです。
しかし、これまでのところ、私はこの問題について深く考えていません。むしろ、今のところ、私は感情管理をテストします。
最終的にどのように見えるか、説明、考え
すべてが現在どのように機能するか(記事の冒頭に大きなビデオがあります):
- すべての制御は、テレグラム+カメラを介して行われます。
- 感情を込めてビデオを制御する必要がない場合、デバイス全体は次のようになります。
- パワーバンクの電源を入れることから始まります。
- 接続されたネットワークがある場合、デバイスはすでに使用できる状態になっています
- ネットワークがない場合は、ネットワークにQRコードを表示する必要があり、システムが自動的に起動します
- Telegrammを使用して、監視する一連のイベントを選択できます。
- 興味深いイベントが発生するたびに、通知が送信されます。
- いつでも、デバイスから写真をリクエストして、何が起こっているかを確認できます
一般的に、愛する人からのレビュー:
- 顔検出器はあまりうまく機能しません。これは実際には、子供向けに調整されていない検出器の機能です。通常、これはウェイクアップ検出の妨げにはなりません(少なくとも、目を開いた状態の通常の写真が2、3枚表示されます)。現在、再トレーニングする予定はありません。
- 画面がない場合、わずかに不透明な起動(QRコードが読み取られたかどうかに関係なく)。そして、画面にはたくさんのワイヤーがあります。最も正しいオプションは、GPIOにダイオードを配置することだと思います。そして、状態に応じて点灯させます(接続がある、カメラが動かない、Movidiusが動かない、電報に接続されていないなど)。しかし、まだ行われていません
- カメラの固定が難しい場合があります。三脚を持っているのでなんとかできます。そして、それらがなければ、おそらく何も機能しなかったでしょう。
- 本当に時間を解放し、動きの自由を与えることができます。ストリーミング付きの通常のベビーモニター/ビデオモニター以上のものですか?私は知らない。多分少し簡単です。
- 実験のためのクールなもの。
起動方法
上で述べたように、私はすべてのソースをレイアウトしようとしました。プロジェクトは大規模で分岐しているので、何かを忘れたか、詳細な指示をしなかったのかもしれません。お気軽に質問して明確にしてください。
すべてを拡張するには、いくつかの方法があります。
- githubからのSors。これはより複雑な方法であり、RPiの構成に長い時間がかかります。何かを忘れた可能性があります。ただし、プロセス(RPi設定を含む)を完全に制御できます。
- 既製の画像を使用してください。ここでは、それは優雅で安全ではないと言うことができます。しかし、それははるかに簡単です。
Github
メインリポジトリはここにあります--github.com/ZlodeiBaal/BabyFaceAnalizer
これは実行する必要のある2つのファイルで構成されています。
- ネットワークのステータス/設定を初期化/確認するためのスクリプトはQRCode.pyです(このスクリプトについては、より詳細な説明があることを忘れないでください)。彼はWiFiに接続し、Telegramにボットの設定があることを確認します。
- 主な作業スクリプトはface.pyです
その上。Gitには2つの欠けているものがあります。
- WiFi資格情報ファイル-wpa_supplicant_auto.conf
- Telegram-botの資格情報を含むファイル-tg_creedential.txt
次回の起動時に、システムに自動的に作成させることができます。空白のフィールドに入力すると、次のように使用できます。
tg_creedential.txt
token to access the HTTP API — , @BotFather telegram "/newbot"
socks5://… — ,
socks5 — ,
socks5 — ,
socks5://… — ,
socks5 — ,
socks5 — ,
wpa_supplicant_auto.conf
network={
ssid="******"
psk="*******"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP
auth_alg=OPEN
}
ssid="******"
psk="*******"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP
auth_alg=OPEN
}
RPiチューニングホイッスルとフェイク
残念ながら、RPiにスクリプトを配置して実行することはできません。安定した作業に必要な他の要素は次のとおりです。
- 指示に従ってインストールしl_openvino_toolkit_runtime_raspbian_p_2020.1.023.tgz - docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_raspbian.htmlを
- 自動実行をインストールする
- デフォルトのパスワードに関するメッセージを削除します(おそらく必要ではありませんが、気になります)-sudo apt purge libpam-chksshpwd
- スクリーンセーバーをオフにする-www.raspberrypi.org/forums/viewtopic.php?t=260355
- 音声検出の場合:
- pip3インストールwebrtcvad
- sudo apt-get install python-dev
- sudo apt-get install portaudio19-dev
- sudo pip3 install pyaudio
- 「Models」フォルダの「Get_models.py」スクリプトを使用して、OpenVinoリポジトリからモデルをダウンロードします
形
画像はこちら(5ギガ)に掲載されています。
いくつかのポイント:
- 標準のログインパスワードが使用されます(pi、raspberry)
- SSHアクセスが有効
- デフォルトでは、WiFiは接続されておらず、システムが監視に使用するカート内のボットのアドレスは構成されていません。
画像にWiFiを設定する方法
最初のオプションは、起動後にテキストとともにQRコードを表示することです。
WIFI:T:WPA;P:qwerty123456;S:TestNet;;
ここで、Pの後はネットワークパスワード、Sの後はネットワーク識別子です。
- Android 10を搭載した携帯電話の場合、[ネットワークを共有]をクリックすると、このようなQRコードが自動的に生成されます。
- そうでない場合は、www.the-qrcode-generator.comで生成できます。
2番目のオプションは、RPiにSSHで接続することです(有線で接続することにより)。または、モニターとキーボードの電源を入れます。そしてファイルを置く
wpa_supplicant_auto.conf
network={
ssid="*********"
psk="*******"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP
auth_alg=OPEN
}
ssid="*********"
psk="*******"
proto=RSN
key_mgmt=WPA-PSK
pairwise=CCMP
auth_alg=OPEN
}
wi-fi設定を「/ home / pi / face_detect」フォルダーに移動します。
画像にテレグラムボットを設定する方法
最初のオプションは、起動後にテキストとともにQRコードを表示することです。
tg_creedential.txt
token to access the HTTP API — , @BotFather telegram "/newbot"
socks5://… — ,
socks5 — ,
socks5 — ,
socks5://… — ,
socks5 — ,
socks5 — ,
www.the-qrcode-generator.com を介して生成する
2番目のオプションは、RPi(有線で接続)にSSHで接続することです。または、モニターとキーボードの電源を入れます。そして、上記のtg_creedential.txtファイルを「/ home / pi / face_detect」フォルダーに置きます。
子供の頃についての注意
私が最初のバージョンを集めて母に見せたとき、私は突然の答えを受け取りました:
「ああ、私たちはあなたの子供時代にほとんど同じことをしました。」
「?!」
「まあ、彼らはあなたと一緒に馬車をバルコニーに置き、アパートのアンプに含まれていた窓からマイクを投げました。」
一般的に、それは遺伝性であることが突然判明しました。
配偶者についての注意
「あなたの妻はどのように反応しましたか?」
「そして、彼女はどうやってあなたにあなたの息子を実験させたのですか?!」
彼らは何度も尋ねました。
しかし、私は妻をよく台無しにしました。ここでは、彼女は時々ハブレに関する記事を書いています。
PS1
私は情報セキュリティの専門家ではありません。もちろん、パスワードなどがどこにも表示されないようにし、開始後にすべてのセキュリティ情報を表示して、全員が自分で構成できるようにしました。
しかし、私はどこかで何かを逃したことを排除しません。明らかなエラーが表示された場合は、修正を試みます。
PS2
おそらく、このプロジェクトの更新については、テレグラムチャネルまたはVKontakteグループで話します。面白いものがたくさん溜まったら、ここで別の出版物を作ります。