RPi保姆

我不时地想做些奇怪的事情。显然,这是一件无用的事情,无法证明投资的合理性,并且在投资成立六个月后,它就将灰尘堆积在架子上。但另一方面,它在情感量,获得的经验和新故事方面完全证明了自己的正确性。我在哈布雷(Habré)上甚至有两篇关于此类实验的文章:Alcoorgan智能喂鸟器



好。现在该谈论一个新的实验了。他如何收集它,产生了什么以及如何重复它。







从某种意义上说,一个平凡的事件促使我进入一个新项目,一个儿子出生了。我为自己安排了一个月的假期。但是孩子原来是安静的-有空闲时间。并把睡在他旁边。



许多不同的房子用于计算机视觉的嵌入式硬件。结果,我决定做一个视频保姆。但并不像所有商店那样乏味。还有一些更聪明,更有趣的东西。



这篇文章将以叙述的方式撰写,以了解玩具的发展方式,发展方向和发展方向。



本文有几项补充:



  1. 视频在这里我展示和说明一切是如何工作的。
  2. 一篇关于VC的小文章,我告诉您为什么这样的事情最有可能无法正常生产,以及这种ML系统的局限性。
  3. github上所有内容+ RPi的现成图像在文章的结尾,描述了如何使用它。


选择一个主意



婴儿监视器最常见的功能是随时查看孩子的情况。不幸的是,这并不总是有效。您不会一直观看广播,这很不方便。通常可以将婴儿放在茧中入睡,为什么总是要录像?结果,以下集合开始在一起:



  1. 系统应该可以随时通过电话观看视频或照片
  2. 系统应该响应孩子的醒来,并通知它
  3. 系统应检测到面部丢失以防止SIDS


平台选择



我在Habré上发表了一篇长篇文章,内容涉及比较不同的平台。在全球范围内,对于像我正在做的原型一样,有几种选择:



  1. Jetson Nano. + ( Nano), , . . — TensorRT. . , , , TensorRT .
  2. VIM3. , . — .
  3. Raspberry PI + Movidius. . , , .
    1. , .
    2. . .
  4. Raspberry PI 4-在使用OpenCV时,最好处理开放的网络,这应该足够了。但是,有人怀疑不会有足够的性能。
  5. 珊瑚-我掌握了它,就性能而言,它会过去,但是我的另一篇文章说了为什么我不喜欢它:)


总计-我选择了Rpi + movidius。我拥有它,我可以使用它。





计算机是Raspberry Pi 3B,神经处理器是Movidius MyriadX。这是可以理解的。

其余的-沿着桶的底部刮,另外购买。







相机



我检查了三种不同的产品:



  • 来自RaspberryPI的相机。嘈杂,不便的电缆,没有方便的附件。得分。
  • 某种IP摄像机。非常方便,因为它不需要包含在RPI中。相机与计算机分离。我的手机甚至有两种模式,白天和黑夜。但是我没有给我足够的脸质量。
  • 来自Genius的网络摄像头。我已经使用它大约5年了,但是最近有些事情变得不稳定了,但是对于RPI来说是正确的。而且,事实证明,可以将其拆下来,并从那里拆下红外滤光片。另外,后来证明,这是麦克风的唯一选择。






过滤器的变化如下:







通常,很明显这不是产品解决方案。但这有效。



如果有的话,那么在代码中,您将看到剩余的片段,可以切换到其他两种类型的相机。如果您更改1-2个参数,甚至可能会完全起作用。



灯光



我有一个照明器,上面躺着一个旧难题。



我给它焊接了某种电源。很好







将其指向天花板-房间已点亮。







屏幕



对于某些操作模式,我需要一个监视器。停在这里尽管我不确定这是否是正确的决定。也许我应该选全长的。但是稍后会更多。







营养



孩子在任意地方睡觉。因此,当系统由移动电源供电时,操作会更容易。我之所以选择它,仅仅是因为它是在家中远足的:







OpenVino



让我们来看看OpenVino。如前所述,OpenVino的最大优势是大量的预训练网络。什么对我们有用。



人脸检测。OpenVino中有许多这样的网络:



  1. 1个
  2. 2
  3. 3


识别面部关键点为了启动以下

面部定向网络,我们需要这样做孩子的活动以及他在看的地方。

眼睛方向识别-如果您尝试进行

深度分析交互也许结果可能是

骨架分析

嗯,还有许多其他有趣的问题……



这些网络的主要缺点将是它们的主要优势-他们的预培训...



可以纠正,但是现在我们正在做一个快速的原型,我们的目标不是在100%的情况下都可以工作,但是至少可以带来的基本工作一些好处。



走。通用逻辑版本1



由于我们正在开发嵌入式设备,因此我们需要以某种方式与之交互。接收照片/警报信号。因此,我决定通过电报进行与处理低谷时相同的操作但是请记住。



对于第一个版本,我决定:



  • 在RPi上运行指定的网络(我希望立即进行所有操作,突然性能会允许)。这将使您看到更多用于解决问题的选项/可能的开发方式
  • 编写一个通用程序模板。
  • 提出一种识别唤醒的算法。
  • 制定一种算法来发送面部表情通知


除了周围的一堆错误外,其他一切都进展顺利。这是ComputerVision固有的功能,我已经习惯了。



这是我所遇到的事情的简短摘要:



  1. OpenVino RPi ( 2020) - from openvino.inference_engine import IECore. OpenVino ( OpenCV ), , .
  2. OpenVino , -generate_deprecated_IR_V7
  3. OpenVino ( , ) Movidius int 8 . int32 . RPi int8 . , .
  4. OpenVino . , OpenVino . , — .
  5. OpenVino , Intel ( , ).
  6. PyTorch 1.5 onnx, 1.4…


但是,这就是方法。。。我敢肯定,如果我通过TensorRT进行操作,那么像往常一样会出现更多问题。



所以。一切都汇集在一起​​,网络正在运行,我们得到了类似的结果(通过在头顶,方向,关键点上运行堆栈)







可以看出:当孩子用手遮住脸/转头时,脸常常会丢失。并非所有指标都稳定。



下一步是什么?如何分析入睡?



我看一下那些网格,想到的第一件事就是识别情绪。当孩子入睡且安静时,他的脸上会有中性的表情。但这不是那么简单。这是一张深蓝色的图,这是一个沉睡的孩子一个小时的中性表情:







其余的图是悲伤/愤怒/喜悦/惊喜。甚至还不是颜色所在位置的本质。不幸的是,我们看到的是网络数据不稳定。在以下情况下会发生不稳定性:



  • 面部阴影过多(在夜间并不罕见)
  • 孩子的脸不在OpenVino训练集中=>任意切换到其他情绪
  • 孩子实际上做鬼脸,包括在梦中


总体而言,我并不感到惊讶。我以前遇到过能够识别情绪的网络,并且它们总是不稳定的,包括由于情绪之间的过渡不稳定-没有明确的界限。



好吧,在情感的帮助下醒来是无法识别的。到目前为止,我不想自己教什么,所以我决定尝试在相同的网络基础上,但在另一端尝试。其中一张网给出了磁头旋转角度。这已经更好了(从时间上看相机的总偏差(以度为单位))。醒前至少5-10分钟:







更好。但是...儿子可能在睡觉时开始摇头。反之亦然,如果您设置了较大的阈值,请醒来,然后再摇头。每次都收到通知...不幸的是:(





大约有一个小时的睡眠时间)



所以我们仍然需要进行正常识别。



版本1中遇到的问题



让我们总结一下我在第一个版本中不喜欢的所有内容。



  1. 自动开启。每次重新启动该玩具,通过SSH连接并运行监视脚本都不方便。在这种情况下,脚本应:

    • 检查相机的状态。可能是相机关闭/未插入。系统必须等待用户打开相机。
    • 检查加速器的状态。与相机相同。
    • 检查网络。我想在家里和乡下都用。或者也许在其他地方。再说一次,我不想通过ssh登录=>如果没有互联网,我需要制定一种算法来连接wiFi。
  2. 醒来,网络培训。尚未出现简单的方法,这意味着有必要训练神经元以识别睁开的眼睛。


自动开启



通常,自动运行方案如下:



  • 我一开始就启动程序。我该怎么做-我写了另一篇文章,并不是说在RPi上这样做很简单。简而言之:

    • OpenVino
    • , —
  • Movidius-
    • — QR- wifi
  • telegram . — QR-




OpenVino中没有现成的眼睛识别网络。

哈哈哈网络已经出现。但是,事实证明,它只有在我开始开发后才启动。在发行版和文档中,当我或多或少地完成所有工作时就已经出现了。现在我正在写一篇文章,并找到了更新

但是,我不会重做,所以我会像以前那样写。



训练这样的网络非常容易。在上面,我说过我是按帧选择眼睛的。没什么了:增加保存所有在框架中相遇的眼睛。事实证明,这样的数据集:







仍然需要标记和训练它。我所描述的标记处理中更详细这里(和该过程的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,您可以选择一组事件进行监视:





  • 每次发生有趣的事件时,都会发送一条通知:





  • 您可以随时从设备请求照片以查看发生了什么



一般来说,来自亲人的评论:



  1. 人脸检测器效果不佳。这实际上是任何不适合儿童使用的探测器的功能。通常,这不会干扰唤醒检测(至少会张开双张正常照片)。目前没有重新培训的计划。
  2. 在没有屏幕的情况下,发射会有些不透明(无论是否读取了QR码)。屏幕上有很多电线。我认为最正确的选择是在GPIO上放置二极管。然后根据状态点亮它们(存在连接,相机不工作,Movidius不工作,电报没有连接等)。但尚未完成
  3. 有时很难固定相机。因为我有一对三脚架,所以我可以用某种方式进行管理。没有他们,也许没有任何效果。
  4. 确实可以让您腾出一些时间并给予行动自由。它是否比带有流媒体功能的普通婴儿监视器/视频监视器还要多?我不知道。也许会容易一些。
  5. 实验的好东西。


如何启动



就像我在上面说的-我试图列出所有信息源。这个项目很大而且很繁琐,所以也许我忘了一些东西或者没有提供详细的工具。随时提出和澄清。



有几种扩展所有内容的方法:



  1. 来自github的朋友们。这是一种更复杂的方法,它会花费很长时间来配置RPi,也许我忘记了一些东西。但是您可以完全控制该过程(包括RPi设置)。
  2. 使用现成的图像。在这里,我们可以说这是无礼且不安全的。但这要容易得多。


Github



主存储库位于此处-github.com/ZlodeiBaal/BabyFaceAnalizer

它包含两个需要运行的文件:



  1. 用于初始化/检查网络状态/设置的脚本为QRCode.py(请记住,对于此脚本,有更详细的描述)。他连接了WiFi,并检查Telegram中是否有该机器人的设置。
  2. 主要的工作脚本是face.py


除了。Git中缺少两件事:



  1. WiFi凭证文件-wpa_supplicant_auto.conf
  2. 具有Telegram-bot凭证的文件-tg_creedential.txt


您可以让系统在下次启动时自动创建它们。您可以通过填写空白字段来使用以下内容:



tg_creedential.txt
token to access the HTTP API — , @BotFather telegram "/newbot"

socks5://… — ,

socks5 — ,

socks5 — ,



wpa_supplicant_auto.conf
network={

ssid="******"

psk="*******"

proto=RSN

key_mgmt=WPA-PSK

pairwise=CCMP

auth_alg=OPEN

}



RPi调整口哨声和假货



不幸的是,您不能只在RPi上放置并运行脚本。这是稳定工作所需要的:



  1. 根据说明安装l_openvino_toolkit_runtime_raspbian_p_2020.1.023.tgz-docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_raspbian.html
  2. 安装自动运行
  3. 删除有关默认密码的消息(也许不是必需的,但它困扰了我)-sudo apt purge libpam-chksshpwd
  4. 关闭屏幕保护程序-www.raspberrypi.org/forums/viewtopic.php?t=260355
  5. 对于音频检测:



    • pip3安装webrtcvad
    • 须藤apt-get install python-dev
    • 须藤apt-get install portaudio19-dev
    • sudo pip3安装pyaudio
  6. 使用“模型”文件夹中的“ Get_models.py”脚本从OpenVino存储库下载模型


形成



图片发布在这里(5演出)。



要点:



  1. 使用标准的登录密码(pi,树莓派)
  2. 启用S​​SH访问
  3. 默认情况下,WiFi未连接,并且未配置系统将用于监视的购物车中机器人的地址。


如何在图像中设置WiFi



第一种选择是在启动后显示带有文字的QR码:



WIFI:T:WPA;P:qwerty123456;S:TestNet;;


其中P之后是网络密码,S之后是网络标识符。



  1. 如果您的手机使用的是Android 10,则当您单击“共享网络”时会自动生成此类QR码
  2. 如果没有,则可以在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

}



将您的Wi-Fi设置转到“ / home / pi / face_detect”文件夹。



如何在图像中设置电报机器人



第一种选择是在启动后显示带有文字的QR码:



tg_creedential.txt
token to access the HTTP API — , @BotFather telegram "/newbot"

socks5://… — ,

socks5 — ,

socks5 — ,



通过www.the-qrcode-generator.com生成它

第二个选项是SSH到RPi(通过有线连接)。或打开显示器和键盘。并将上述tg_creedential.txt文件放在“ / home / pi / face_detect”文件夹中。



关于童年的评论



当我收集第一版并将其展示给妈妈时,我突然收到一个答案:

“哦,我们在您的童年时代几乎都做过。”

“?!”

“好吧,他们把你的婴儿车带到阳台上,从窗户里扔了一个麦克风,这是公寓里放大器里的东西。”


通常,突然发现这是遗传性的。



关于配偶的评论



“你妻子有什么反应?”

“她是怎么让你对儿子做实验的?!”

他们问了不止一次。

但是,我很好地毁了我的妻子。在这里,有时甚至会写关于哈布雷的文章。



PS1



我不是信息安全专家。当然,我试图确保没有密码显示在任何地方,等等,并且每个人都可以自行配置,并在启动后指示所有安全信息。



但我不排除自己错过了某处。如果您看到明显的错误,我将尝试修复它。



PS2



我很可能会在电报频道VKontakte组中讨论该项目的更新如果我积累了很多有趣的东西,那么我将在这里发表另一本出版物。



All Articles