因为发现助教给炼丹任务提供了 64 张图片作为训练集, 64 张图片作为验证集, 所以果断放弃了炼丹任务. (update: 发现还是炼丹任务好做)

打算用 buildroot 从底向上 build 一个能跑起 python-gpio 的 linux. 另开一文记录一下坑们.

(补充) 计划

  • 先不管镜像大小, 用 buildroot 搞出一个能跑的系统. (好像 buildroot 部分差不多了.)
  • 实时性补丁
  • 进一步裁剪内核. (即使不栽应该也比 raspbian 小很多了. 毕竟老师那边 raspbian 裁剪纪录是 200M, 而 buildroot 现在才不到 150.)

build

使用了默认配置 $ make raspberrypi3_defconfig 节省了很多自己摸索的时间.

配置静态 ip 的时候发现可能是由于 networking 的版本问题, 必需要在 interfaces 配置文件里把 netmask 写在 address 上面, 否则不 work.

另外选装上了 openssh 以支持 ssh 登录. 在折腾了半天之后终于能 ssh 上树莓派上的系统了.

然后发现它有一个轻量级的 dropbear 作为 sshd. 于是果断把 Openssh 给删了.

另外神奇的锅是 eth0 会过一会再 ready, 于是 init.d 里的 ifup -a 并不能把 eth0 给 enable. 解决方案是加一个异步的 sleep 10 秒再 ifup 一次. 很醉.

RPi.GPIO

瞎折腾一阵发现减不下来 rootfs 大小之后, 觉得第一要务是把 GPIO 程序跑上. 于是装了 python 和一个 RPi.GPIO 的 python 包 (直接在 .config 里改就行了). 然后 rootfs 直接从 60MB 暴涨到 100MB. Python 真是太可怕了.

upd: 嫌打补丁麻烦 放弃了.

OpenCV

转眼间折腾了大半个星期才装上 opencv. (v4l 还没搞定)

才明白 br2 的正确用法… 在 .config 里东西消失代表有 dependency 没有被满足, 此时需要手工查 Config.in 来把 dependency 满足一下.

opencv python 的锅在于它依赖 numpy 而 numpy 依赖 glibc 而我用的是 uclibc. 解决方案是网上有人给出了 numpy 的不依赖 glibc 的 patch. 但问题是这个 patch 用的是 1.8.0 而现在已经到 1.16.0 了. 不过降级看起来影响不大. 记得要删掉 hash 否则会报错.

另外 rootfs-overlay 可以很好地解决各种 config 在重配之后的覆盖问题.

Raspivid 和摄像头驱动

用 rpi-userland 这个包可以装上 raspistill 之类的一系列东西.

为了启动 camera 需要在 boot 里面加一些镜像并在 config.txt 里面加上 start_x=1. 参考了 stackoverflow

于是加上 netcat 后终于能把摄像头监控跑起来了.

直播推流

为了 http 加了 nginx. 准备直接用 python 静态生成 http 的方法来减少工作量.

找到一个 nginx-rtmp 的 nginx module 来支持直播的广播. 还没完全搭好.

后面还需要再加一些用户态的东西大概就能搞定基本需求了.

nginx-rtmp 还是很好配置的, 抄 readme 即可. 一个坑在于它的 exec_static 我最开始怎么也启不起来. 后来发现要在 nginx.conf 里加一个 user root;. 大概只有 root 才有权限执行东西?

下一个坑是 rtmp 协议比较老, 找不到好用的 html5 的前端播放器, 只有 video.js 有一个基于 flash 的版本, 显然不能要. 仔细观察发现 nginx-rtmp 支持 hls 这个比较新的比较科学的格式, 而且 video.js 也通过 contrib 的方式支持了 hls. 就很开心地写 (抄 example) 了一个前端的网页, 于是现在可以做到视频流直播了.

运动监测

首先要把 rtmp 的视频接进 opencv. 目测是一个不小的坑. 确实是.

发现的第一个锅是 import cv2 跪了??? 经过一顿猛如虎的检修发现首先是 ffmpeg 找不到 libmmal. 于是直接禁掉好了, 反正也不用.

第二个锅是 /lib/uclibc-xxx.so 直接变成 invalid binary 了 (可能是 64位的 binary). 于是连夜换了 glibc, 然后发现 glibc 能够正常工作. 感到迷惑.

总之 opencv 能用了, 很好的是 cv.VideoCapture 可以直接抓取 rtmp 流, 省了很多事.

运动监测算法我想得比较简单, 直接拿两帧减一下取绝对值求和就可以了. 实测除了桌面抖动引发的大规模画面边缘以外, 检测效果还不错.

想到一个解决抖动的方法是直接把图象降分辨率仿佛效果也不错.

图像识别

这里要强烈吐槽这个任务太麻烦了. 识别人脸多好.

感觉 dnn 会比较麻烦, 然后发现 opencv 带了一个 haar 的识别和人脸的模型, 但并没有石头剪刀布的模型. 先跑了一个人脸的, 一次 Inference 要 400ms 左右, 正好一秒一次.

然而问题是这个传统方法训练那 64 张图片效果有若见鬼. 于是最后还是用了 pytorch 训一个 MobileNetV2.

训练

本人不会炼丹. 请教了 cv 专家 (室友) 后学习了用 torchvision 做 random rotation 和 color gitter 的办法. 并找了一个 2699 + v100 的集群来做训练.

发现因为原图太大, 所以很多训练时间放在了 cpu 生成数据上, 而 GPU 的利用率低得可怜. 于是想到先用 CPU 生成一些数据, 然后在 GPU 上用生成好的数据迅速地训练几十个 epoch, 达到一定精度后再使用比较慢的新鲜数据来训练. 于是成功地把 GPU 利用率打到比较高. 但神奇的事情是在预生成数据上的精度会很高, 但并不能成功迁移到新鲜数据上. 于是又在新鲜数据上过拟合了一会就放弃了. 最终达到的 training acc 在 72 左右 (4分类).

模型转换

pytorch 训练出来的模型并不能很方便地被 opencv 读出来 (其实是我的 opencv 版本太老). 而我并不想再装一个 pytorch 在树莓派上. 然而看到 opencv dnn 有一个 readNetFromONNX 的功能, 于是导出了一个 ONNX 模型.

然而发现它不支持 Clip 操作. 经过尝试发现新 2 个小版本的 opencv (3.4.8) 可以支持 Clip, 但新的问题是即使升到 opencv 4 也不支持自定义维度的 reduce mean (很奇怪啊? numpy 支持这个事的) 检查模型后发现 reduce mean 的作用是在 feature 的最后一步, 将任意大小的图片压缩成 1280 维的 feature map. 于是把 features 和 classifier 分开导出, 中间的压缩用 numpy 在 inference 代码里进行处理, 加上手工在 buildroot 里把 opencv 的版本升了一下, 解决了读模型的问题, 并成功地做了 Inference.

需要注意的一点是 opencv 读进来的图片格式和 pytorch 并不一致. opencv 是 NHWC 而 pytorch 是 NCHW, 而且 opencv 的 3 个 channel 的顺序是 BGR. 再有就是 torch 输入颜色是 0 到 1, 而 opencv 读进来是 0 到 255 的 uint8. 解决了这些问题之后模型跑得稍微正常了一些.

DNN Inference Accelerator Adaption

幸运的是我自己还有一个同款的 rpi3b+. 在 raspbian 的系统里可以方便地对 oepnvino 有初步的认识. 发现做一个 mobilenetv2 的 inference 只需要 50ms 左右. 这样的话可以比较大地提高 fps.

然而

在 aarch64 上装 openvino 是个天坑 --- laekov.

openvino 只提供了 32 位 raspbian 的 binary 版本驱动. 所幸 inference-engine 是一个开源项目而且仿佛没有内核的东西, 而是直接用 libusb 来驱动 movidus 加速棒.

DLDT

inference-engine 的代码在 opencv/dldt 仓库里. (很神奇居然是 opencv 名下的仓库) 拉下来之后就可以愉快地写 buildroot 的配置文件来 build 了. 然而需要一些 configure 和 patch. 已知的第一个坑是 cmake 里面 find_file 需要加上 CMAKE_FIND_ROOT_PATH_BOTH 才能正确找到一些 firmware. (折腾了一上午)

接下来的坑是有 git sub repo 而 call git 的方式下载下来的是 release tarball 所以 third party 的东西要手工下载. 下载下来之后发现 CLDNN 有奇怪的 link error 于是 disable 之. 发现居然需要一个奇怪的 opencv 头文件于是 disable 之. 发现居然要 build samples 于是 disable 之. 最后发现 pugixml 打死 link 不上又没法 disable. 谷歌之后发现可以 uncomment pugixml 的头文件里的某一行, 使它变成一个纯头文件的库, 于是终于解决了这个问题并成功地 install 了 dldt. (然而怀疑它并不能正确运行)

事实证明 inference-engine 是能正确运行的, 只是它没有 install (大雾). 然而把 opencv 的 DInferenceEngine_DIR 给指到 inference-engine 的 build 目录里就可以了. 然后 opencv 居然也能成功 build 了???

再然后把几个 .so 扔进 /usr/lib 里面之后就能跑了. 开熏.

内核裁剪

栽不动. sad.

把有对应 pyc 的 .py 都删掉可以得到一些空间.

手工删了一些认识的不需要的 opencv data.

剩下的都是各种 .so 和 .ko 不太认识就不敢删了.

做不动了. 就这样吧.