震惊!一男子逃课两天只为了这件事......
功能要求:人脸检测和识别,口罩检测
时间期限两天
由于之前一直以为嵌入式类的比赛是不会允许使用树莓派这种平台,但是之后得知可以用,便毫不犹豫的选择了树莓派。本以为在有强大开源算法的加持下,做一个小项目应该问题不大,但遇到的几个问题还是严重爆破了我的心态。主要是下面几个
-
树莓派显示图像帧率低
-
开机启动脚本
-
opencv-contrib配置
-
人脸识别算法选择
一 树莓派显示图像帧率低
首先是第一个问题。确切一点说,树莓派单独读取图像的耗时还是比较短的。问题主要出现在以下两点。一是opencv读取图像的缓冲机制,导致图像无法实时更新,树莓派显示图像总会有滞后。二是算法执行效率低,某些算法未经优化直接再树莓派上跑还是有些费劲。其实还有一点就可能涉及到树莓派显示图像本身的耗时,但其影响较小,暂时不考虑。接下来说一下我的解决方案。
首先是图像缓冲导致的延时。这篇博客给出了两种解决方案。一是每次读取图像之前先重启相机,从而保证获取的每一帧图像都是当前图像。这的确是一个投机取巧的方法,但是在我用的时候却出现了问题。当我尝试关闭相机再重新启动的时候,发现要么相机打开失败,要么读取的图像为None,始终无法顺利获取当前图像。于是我采用了第二种方法,即单开一个线程采集图像。这里贴一下核心的代码部分。
class Camera: def __init__(self, camera): self.frame = [] self.ret = False self.cap = object self.camera = camera self.openflag = False def open(self): # if self.cap == object: self.cap = cv2.VideoCapture(self.camera) self.ret = self.cap.set(3, 320) self.ret = self.cap.set(4, 240) self.ret = False self.openflag = True threading.Thread(target=self.queryframe, args=()).start() def queryframe(self): # self.openflag = True # while True: while self.openflag: self.ret, self.frame = self.cap.read() # pass def getframe(self): return self.ret, self.frame def close(self): self.openflag = False self.cap.release() def cameratest(): camera = Camera(0) camera.open() while True: ret,image = camera.getframe() if ret: print(type(image)) cv2.imshow('img',image) cv2.waitKey(10) else: print("read faild") camera.close() cv2.destroyAllWindows()
通过这种方法能看到当前采集图像的延迟确实减小了,但是效果可能不是很明显。这里我的理解是毕竟是多线程,获取图像和处理,显示任务同一时间只能完成一个,只是效率提高了(之前是每次代码执行到的时候从缓冲区读取,现在是一直从缓冲区读取,可见缓冲区还是存在的)。因此这种方法在降低延迟的同时势必会造成图像的卡顿,因为当前显示的图片和从缓冲区读取的图片并不是连续的。但这种卡顿不影响实际效果。
其次是算法的问题。这里我拿常用的opencv人脸级联分类器举例。目前来看,在离线的人脸识别方式中,opencv的级联分类器是相对速度比较快的,在一般的cpu上跑20帧/s左右应该没有问题。但一旦移植到树莓派,其速度便大幅降低,毕竟树莓派的算力有限。这也是为什么很多人都开始倾向于旋转K210这种有AI计算加成的芯片而不是选择树莓派。总而言之,如果你想用树莓派实现实时的人脸识别(神经棒加速除外),基本上不太可能实现。如果你可以接受10帧/s以下的速度,树莓派也许可以。
二 开机启动脚本
其次是第二个问题,开机启动的脚本。树莓派开机启动方式和linux系统基本一致,具体不再赘述,请参考这篇博客。这里我选择的方式为开机后自动打开终端Terminal并执行python脚本。原因是我需要将imshow的整个画面全屏显示在lcd上,我需要判断我的程序是否运行,或者有没有出现错误。具体方法如下:
创建文件加入autostart 文件夹(如果不存在就创建一个)
mkdir ~/.config/autostart
sudo vim ~/.config/autostart/myprogram.desktop
然后输入以下内容
[Desktop Entry] Encoding=UTF-8 Type=Application Name=myprogram Exec=lxterminal -e bash -c 'python3 /home/pi/Desktop/test.py;$SHELL' Terminal=true
其中python3 /home/pi/Desktop/test.py为需要执行的代码,使用时替换成自己需要的执行的命令即可,注意不要删掉前面的'和后面的;,$SHELL是为了保证打开的终端会一直显示,如果删掉的话执行该句代码后就会kill掉这个进程,等于程序没有执行。
最后设置权限:
sudo chmod a+r ~/.config/autostart/myprogram.desktop
注意事项:
- 这个代码执行的速度过快,如果需要连网发送数据的话,会出现网络错误的提示,因此我在python3的代码起始部分添加了
time.sleep(30)命令,让程序等待30s再执行,确保可以连接到网络。 - 执行自己输入的代码时,系统处于根目录,相当于新打开了一个终端,没有进行cd操作,所以代码里面使用相对路径的话可能会出错,比如打开
Desktop/test.png的图片,这个时候最好使用绝对路径/home/pi/Desktop/test.png。
三 opencv-contrib配置
然后就是最重要的一步了,需要安装opencv-python以及opencv-contrib-python.这里是我遇到坑最多的地方,以下内容如果各位也遇到了相同的问题,那恭喜你可以顺利解决。若未曾遇到过这些问题,请在评论区打我的脸。接下来步入正题。
可能是我conda用习惯了,只要是需要用pip install我都会习惯性的选择换源。于是用树莓派我也是这么做的。我这里使用的是树莓派官方的最新镜像,一般默认的python版本是2.7,python3版本是3.7,然后我就用pip开始安装。(如果你想获得较好的体验,这边建议您直接使用源码进行编译,这样c++和python都可以用,但是过程嘛,懂得都懂,我当时是不想在这上面浪费太多时间)。输入:
pip install opencv-python
问题出现了,报错
Could not find a version that satisfies the requirement opencv (from versions: ) No matching distribution found for opencv
意思就是找不到当前python版本对应的opencv版本,查阅了几篇网上的资料,都说是要安装python3,但是众所周知,目前树莓派的镜像都是默认安装python3.7的。没办法,于是我尝试用pip3 install安装opencv-python,但还是同样的错误。最后折腾了好久,都没有成功。这时我忽然想起来,当时安装树莓派安装ros的时候,有一个问题是因为国内的源没有源码包或者没有更新,必须使用pip的默认源,于是我又把pip的源改了回去,即重新修改 ~/.pip/pip.conf文件为以下内容:
[global] index-url = https://www.piwheels.org/simple/
这里告诉我们,换源之前千万记得要备份默认源,千万不要直接删掉!!
换源之后我重新尝试pip install opencv-python,结果还真没有报错,而且已经开始下载。但速度是极其慢,于是我参考网上的方法,直接从终端中找到源码包的地址,手动下载,例如下面这样
下载地址: https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv7l.whl
opencv_python-3.4.3.18-cp37-cp37m-linux_armv7l.whl (7.5MB)
下载完成后,在树莓派上离线安装:
sudo pip3/pip install opencv_python-3.4.3.18-cp37-cp37m-linux_armv7l.whl
到这里基本上就可以安装成功了。接下来是opencv-contrib-python包,和上面采用一样的方式,不再重复了。
分别安装完成之后,我开始进行验证。首先是python2
>>import cv2 >>
没有任何问题,但当我在python3中测试时,却出现了下面这样的错误:
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.7/dist-packages/cv2/__init__.py", line 3, in <module> from .cv2 import * ImportError: /usr/local/lib/python3.7/dist-packages/cv2/cv2.cpython-37m-arm-linux-gnueabihf.so: undefined symbol: __atomic_fetch_add_8`
当时我还很奇怪,又反反复复重新装了好多次,但一直报这个错误,直到我看到了这篇博客。他说这是opencv的一个小bug。(但是后来我仔细想了一下,应该还是我当时安装的opencv-python版本和opencv-contrib-python版本不对应的问题,建议大家可以根据官网提供的对应关系,直接安装对应版本的包。)于是我就按照他的方法试了一下,果然解决了,于是我就以为已经大功告成了,但是事实证明我太天真了。问题出在了自启动上,根据上面的步骤,自启动的终端是在bash执行之前的,换言之,开机启动的Terminal中是没有过export LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1
即当前终端下python3无法运行opencv。虽然每次开机之后手动打开终端都可以运行成功,但那时bash已经执行过了。于是我只能继续找资料,此时我意识到,百度解决不了问题,或许google可以,于是我直接将我的报错信息复制到google进行搜索,第一个结果就是这篇文章。凭借我的高中英语水平,我最后还是艰难的找到了问题的答案。修改如下:
[Desktop Entry] Encoding=UTF-8 Type=Application Name=myprogram Exec=lxterminal -e bash -c 'export LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1 python3 /home/pi/Desktop/test.py;$SHELL' Terminal=true
没错,就是在执行之前export 一下,就这一句话浪费了我好长的时间。然后重启,等待终端自动打开,运行,成功。
四 人脸识别算法选择
最后一个就是人脸识别算法的选择问题。解决了上面的问题之后,剩下的时间不多了。于是我也不考虑任何其他神经网络的算法,就直接拿了opencv的识别分类算法来用。opencv支持3种人脸识别的算法,分别是:
1. Eigen Faces PCA(特征脸方法)
2. Fisher Faces LDA(线性判别分析)
3. Local Binary Pattern Histograms(LBP 局部二值模式直方图)
当时首先考虑到算法的速度,最后选择了第三种方法,即LBP方法。刚开始觉得准确率还可以,但是后来测试的时候发现,当检测时光照和训练时光照不一样时,准确率大大降低。误识别率大大上升。总之效果很差,但是最后还是硬着头皮上了,最后发现评委都不太懂,就糊弄过去了。比赛结束之后,又试了另外两种方法,发现效果最好的还是PCA特征脸检测,其实速度上来说也还可以接受,并且相对于LBP其准确率也大大上升了,虽然还是会有误识别,但是结果也在接受范围之内。至此基本的功能都已实现,待我整理一下,过两天把代码开源出来供各位参考。效果图如下:

附录
写在最后
鉴于这是一个比赛而不是一个完整的项目。每当深夜自己一个人敲代码屏幕一片暴红的时候,那种孤独和绝望,估计只有经历过的人才懂吧。还是那句话,怕什么真理无穷,进一步有进一步的欢喜。

浙公网安备 33010602011771号