好。现在该谈论一个新的实验了。他如何收集它,产生了什么以及如何重复它。
从某种意义上说,一个平凡的事件促使我进入一个新项目,一个儿子出生了。我为自己安排了一个月的假期。但是孩子原来是安静的-有空闲时间。并把睡在他旁边。
许多不同的房子用于计算机视觉的嵌入式硬件。结果,我决定做一个视频保姆。但并不像所有商店那样乏味。还有一些更聪明,更有趣的东西。
这篇文章将以叙述的方式撰写,以了解玩具的发展方式,发展方向和发展方向。
本文有几项补充:
- 视频在这里我展示和说明一切是如何工作的。
- 一篇关于VC的小文章,我告诉您为什么这样的事情最有可能无法正常生产,以及这种ML系统的局限性。
- github上的所有内容+ RPi的现成图像。在文章的结尾,描述了如何使用它。
选择一个主意
婴儿监视器最常见的功能是随时查看孩子的情况。不幸的是,这并不总是有效。您不会一直观看广播,这很不方便。通常可以将婴儿放在茧中入睡,为什么总是要录像?结果,以下集合开始在一起:
- 系统应该可以随时通过电话观看视频或照片
- 系统应该响应孩子的醒来,并通知它
- 系统应检测到面部丢失以防止SIDS
平台选择
我在Habré上发表了一篇长篇文章,内容涉及比较不同的平台。在全球范围内,对于像我正在做的原型一样,有几种选择:
- Jetson Nano. + ( Nano), , . . — TensorRT. . , , , TensorRT .
- VIM3. , . — .
- Raspberry PI + Movidius. . , , .
- , .
- . .
- Raspberry PI 4-在使用OpenCV时,最好处理开放的网络,这应该足够了。但是,有人怀疑不会有足够的性能。
- 珊瑚-我掌握了它,就性能而言,它会过去,但是我的另一篇文章说了为什么我不喜欢它:)
总计-我选择了Rpi + movidius。我拥有它,我可以使用它。
铁
计算机是Raspberry Pi 3B,神经处理器是Movidius MyriadX。这是可以理解的。
其余的-沿着桶的底部刮,另外购买。
相机
我检查了三种不同的产品:
- 来自RaspberryPI的相机。嘈杂,不便的电缆,没有方便的附件。得分。
- 某种IP摄像机。非常方便,因为它不需要包含在RPI中。相机与计算机分离。我的手机甚至有两种模式,白天和黑夜。但是我没有给我足够的脸质量。
- 来自Genius的网络摄像头。我已经使用它大约5年了,但是最近有些事情变得不稳定了,但是对于RPI来说是正确的。而且,事实证明,可以将其拆下来,并从那里拆下红外滤光片。另外,后来证明,这是麦克风的唯一选择。
过滤器的变化如下:
通常,很明显这不是产品解决方案。但这有效。
如果有的话,那么在代码中,您将看到剩余的片段,可以切换到其他两种类型的相机。如果您更改1-2个参数,甚至可能会完全起作用。
灯光
我有一个照明器,上面躺着一个旧难题。
我给它焊接了某种电源。很好
将其指向天花板-房间已点亮。
屏幕
对于某些操作模式,我需要一个监视器。停在这里。尽管我不确定这是否是正确的决定。也许我应该选全长的。但是稍后会更多。
营养
孩子在任意地方睡觉。因此,当系统由移动电源供电时,操作会更容易。我之所以选择它,仅仅是因为它是在家中远足的:
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进行操作,那么像往常一样会出现更多问题。
所以。一切都汇集在一起,网络正在运行,我们得到了类似的结果(通过在头顶,方向,关键点上运行堆栈)
可以看出:当孩子用手遮住脸/转头时,脸常常会丢失。并非所有指标都稳定。
下一步是什么?如何分析入睡?
我看一下那些网格,想到的第一件事就是识别情绪。当孩子入睡且安静时,他的脸上会有中性的表情。但这不是那么简单。这是一张深蓝色的图,这是一个沉睡的孩子一个小时的中性表情:
其余的图是悲伤/愤怒/喜悦/惊喜。甚至还不是颜色所在位置的本质。不幸的是,我们看到的是网络数据不稳定。在以下情况下会发生不稳定性:
- 面部阴影过多(在夜间并不罕见)
- 孩子的脸不在OpenVino训练集中=>任意切换到其他情绪
- 孩子实际上做鬼脸,包括在梦中
总体而言,我并不感到惊讶。我以前遇到过能够识别情绪的网络,并且它们总是不稳定的,包括由于情绪之间的过渡不稳定-没有明确的界限。
好吧,在情感的帮助下醒来是无法识别的。到目前为止,我不想自己教什么,所以我决定尝试在相同的网络基础上,但在另一端尝试。其中一张网给出了磁头旋转角度。这已经更好了(从时间上看相机的总偏差(以度为单位))。醒前至少5-10分钟:
更好。但是...儿子可能在睡觉时开始摇头。反之亦然,如果您设置了较大的阈值,请醒来,然后再摇头。每次都收到通知...不幸的是:(
大约有一个小时的睡眠时间)
所以我们仍然需要进行正常识别。
版本1中遇到的问题
让我们总结一下我在第一个版本中不喜欢的所有内容。
- 自动开启。每次重新启动该玩具,通过SSH连接并运行监视脚本都不方便。在这种情况下,脚本应:
- 检查相机的状态。可能是相机关闭/未插入。系统必须等待用户打开相机。
- 检查加速器的状态。与相机相同。
- 检查网络。我想在家里和乡下都用。或者也许在其他地方。再说一次,我不想通过ssh登录=>如果没有互联网,我需要制定一种算法来连接wiFi。
- 醒来,网络培训。尚未出现简单的方法,这意味着有必要训练神经元以识别睁开的眼睛。
自动开启
通常,自动运行方案如下:
- 我一开始就启动程序。我该怎么做-我写了另一篇文章,并不是说在RPi上这样做很简单。简而言之:
- OpenVino
- , —
- Movidius-
-
- — QR- wifi
- telegram . — QR-
哈哈哈网络已经出现。但是,事实证明,它只有在我开始开发后才启动。在发行版和文档中,当我或多或少地完成所有工作时就已经出现了。现在我正在写一篇文章,并找到了更新。
但是,我不会重做,所以我会像以前那样写。
训练这样的网络非常容易。在上面,我说过我是按帧选择眼睛的。没什么了:增加保存所有在框架中相遇的眼睛。事实证明,这样的数据集:
仍然需要标记和训练它。我所描述的标记处理中更详细这里(和该过程的10分钟的视频这里)。Toloka用于标记。大约花了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问题
当前的实现在质量上有所不同,但是仍然存在许多问题:
- . , :
- - ⅓ .
- . . , , . , .
- . ?
?
我没有重新训练面部检测。与眼睛识别不同,这需要做很多工作。并具有数据集的收集和质量培训。
当然,您可以在您儿子的脸上进行操作,可能会比当前网络更好。但是对于其他人,没有。而且,也许对于我的儿子来说,两个月后-也不会。
收集普通数据集需要很长时间。
声音
遵循声音识别的经典路径并训练神经元将是可能的。通常,它不会很长,最多不会比眼睛识别长几倍。但是我不想弄乱收集数据集,所以我使用了一种更简单的方法。您可以使用现成的WebRTC工具。几行显示,一切都变得优雅而简单。
我发现的缺点是,不同麦克风的解决方案质量不同。某个地方发出吱吱声,而某个地方只有大声喊叫。
来吧还有什么
在某个时候,我通过与妻子播放一个5秒钟的自我循环视频来进行测试:
很明显,儿子在视场上粘在人们的脸上(监视器将他挂了30分钟)。这个想法诞生了:控制面部表情。这不仅是静态视频,还是互动选项。原来是这样的(当儿子的情绪发生变化时,视频序列会切换):
“爸爸,你他妈的他妈的吗?!”
可能应该尝试使用大型显示器。但是我还没有准备好。
也许您需要替换正在播放的视频。幸运的是,它很简单-视频是从单独的图像播放的,其中帧更改已调整为FPS。
也许您需要等待(在当前水平上,孩子可能根本不了解他的情绪和屏幕之间的联系)
接着?
在我看来,最有希望的方向之一就是尝试通过视角/姿势的方向来控制某些物理对象/灯光/电机。
但是到目前为止,我还没有对这个问题进行深入的思考。相反,现在,我将测试情绪管理。
最终的外观,描述,想法
现在一切如何工作(本文开头有一个较大的视频):
- 所有控制通过Telegramm +通过摄像机进行。
- 如果您不需要用情绪来控制视频,那么整个设备将如下所示:
- 它通过打开移动电源上的电源来启动。
- 如果存在已连接的网络,则设备已准备就绪,可以工作了
- 如果没有网络,则需要在网络上显示QR码,系统会自动启动
- 通过Telegramm,您可以选择一组事件进行监视:
- 每次发生有趣的事件时,都会发送一条通知:
- 您可以随时从设备请求照片以查看发生了什么
一般来说,来自亲人的评论:
- 人脸检测器效果不佳。这实际上是任何不适合儿童使用的探测器的功能。通常,这不会干扰唤醒检测(至少会张开双张正常照片)。目前没有重新培训的计划。
- 在没有屏幕的情况下,发射会有些不透明(无论是否读取了QR码)。屏幕上有很多电线。我认为最正确的选择是在GPIO上放置二极管。然后根据状态点亮它们(存在连接,相机不工作,Movidius不工作,电报没有连接等)。但尚未完成
- 有时很难固定相机。因为我有一对三脚架,所以我可以用某种方式进行管理。没有他们,也许没有任何效果。
- 确实可以让您腾出一些时间并给予行动自由。它是否比带有流媒体功能的普通婴儿监视器/视频监视器还要多?我不知道。也许会容易一些。
- 实验的好东西。
如何启动
就像我在上面说的-我试图列出所有信息源。这个项目很大而且很繁琐,所以也许我忘了一些东西或者没有提供详细的工具。随时提出和澄清。
有几种扩展所有内容的方法:
- 来自github的朋友们。这是一种更复杂的方法,它会花费很长时间来配置RPi,也许我忘记了一些东西。但是您可以完全控制该过程(包括RPi设置)。
- 使用现成的图像。在这里,我们可以说这是无礼且不安全的。但这要容易得多。
Github
主存储库位于此处-github.com/ZlodeiBaal/BabyFaceAnalizer
它包含两个需要运行的文件:
- 用于初始化/检查网络状态/设置的脚本为QRCode.py(请记住,对于此脚本,有更详细的描述)。他连接了WiFi,并检查Telegram中是否有该机器人的设置。
- 主要的工作脚本是face.py
除了。Git中缺少两件事:
- 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
- 须藤apt-get install python-dev
- 须藤apt-get install portaudio19-dev
- sudo pip3安装pyaudio
- 使用“模型”文件夹中的“ Get_models.py”脚本从OpenVino存储库下载模型
形成
图片发布在这里(5演出)。
要点:
- 使用标准的登录密码(pi,树莓派)
- 启用SSH访问
- 默认情况下,WiFi未连接,并且未配置系统将用于监视的购物车中机器人的地址。
如何在图像中设置WiFi
第一种选择是在启动后显示带有文字的QR码:
WIFI:T:WPA;P:qwerty123456;S:TestNet;;
其中P之后是网络密码,S之后是网络标识符。
- 如果您的手机使用的是Android 10,则当您单击“共享网络”时会自动生成此类QR码
- 如果没有,则可以在www.the-qrcode-generator.com上生成它
第二个选项是SSH进入RPi(通过有线连接)。或打开显示器和键盘。并把文件
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生成它。
第二个选项是SSH到RPi(通过有线连接)。或打开显示器和键盘。并将上述tg_creedential.txt文件放在“ / home / pi / face_detect”文件夹中。
关于童年的评论
当我收集第一版并将其展示给妈妈时,我突然收到一个答案:
“哦,我们在您的童年时代几乎都做过。”
“?!”
“好吧,他们把你的婴儿车带到阳台上,从窗户里扔了一个麦克风,这是公寓里放大器里的东西。”
通常,突然发现这是遗传性的。
关于配偶的评论
“你妻子有什么反应?”
“她是怎么让你对儿子做实验的?!”
他们问了不止一次。
但是,我很好地毁了我的妻子。在这里,她有时甚至会写关于哈布雷的文章。
PS1
我不是信息安全专家。当然,我试图确保没有密码显示在任何地方,等等,并且每个人都可以自行配置,并在启动后指示所有安全信息。
但我不排除自己错过了某处。如果您看到明显的错误,我将尝试修复它。
PS2
我很可能会在电报频道或VKontakte组中讨论该项目的更新。如果我积累了很多有趣的东西,那么我将在这里发表另一本出版物。