基于OpenCV和Tensorflow的深蹲检测器

时间:2022-07-22
本文章向大家介绍基于OpenCV和Tensorflow的深蹲检测器,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

本期我们将介绍如和使用OpenCV以及Tensorflow实现深蹲检测

在检疫期间,我们的体育活动非常有限,这样并不好。在进行一些居家运动时,我们必须时刻保持高度的注意力集中,以便记录自己每天的运动量。因此我们希望建立一个自动化的系统来实现运动量计算。考虑到我们在深蹲时,有明确阶段和大幅度变化的基本运动,实现对深蹲的计数会相对比较简单。

下面我们就一起尝试实现它吧!

数据采集

使用带相机的Raspberry Pi来获取图片是非常方便的,完成图像的拍摄后再利用OpenCV即可将获取的图像写入文件系统。

运动识别

最初,我们打算使用图像分割完成人物的提取工作。但是我们都知道图像分割是一项非常繁琐的操作,尤其是在Raspberry资源有限的情况下。

除此之外,图像分割忽略了一个事实。当前我们所拥有的是一系列图像帧,而不是单个图片。该图像序列具有明显功能,并且我们后续将要使用到它。

因此,我们从OpenCV 着手进行背景去除,以提供了可靠的结果。

背景扣除

首先,创建一个背景减法器:

backSub = cv.createBackgroundSubtractorMOG2()

向其中添加图像帧:

mask = backSub.apply(frame)

最后我们可以得到一张带有身体轮廓的图片:

然后扩大图像以突出轮廓。

mask = cv.dilate(mask, None, 3)

将此算法应用于所有图像帧可以得出每一幅图像中的姿势。之后,我们将它们分类为站立,下蹲以及无三种情况。

接下来我们要把图像中的人提取出来,OpenCV可以帮助我们找到相应的找到轮廓:

cnts, _ = cv.findContours(img, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE)

这种方法或多或少适用于人物的最大轮廓的提取,但不幸的是,这样处理的结果并不稳定。例如,检测得到最大的轮廓只能包括人的身体,而不包括他的脚。

但不管怎么说,拥有一系列图像对我很有帮助。通常情况下我们做深蹲运动都发生在同一地点,因此我们可以假设所有动作都在某个区域内进行并且该区域是稳定的。为此我们可以迭代构建边界矩形,如果需要,可以以最大轮廓增加边界矩形。

有一个例子:

• 最大的轮廓是红色

• 轮廓边界矩形为蓝色

• 图边界矩形为绿色

通过以上的边缘提取以及轮廓绘制,可以为进一步处理做好充足准备。

分类

接下来我们将从图像中提取出边界矩形,并将其转化为按尺寸64x64正方形。

以下Mask用作分类器输入:

站立姿势:

下蹲姿势:

接下来我们将使用Keras 与Tensorflow进行分类。

最初,我们使用了经典的Lenet-5模型,运行结果良好。随后由于阅读了一些有关Lenet-5变体的文章后,我们决定尝试简化架构。

事实证明,简化后的CNN在当前示例中的精度几乎相同:

model = Sequential([
       Convolution2D(8,(5,5), activation='relu', input_shape=input_shape),
       MaxPooling2D(),
       Flatten(),
       Dense(512, activation='relu'),
       Dense(3, activation='softmax')
     ])
model.compile(loss="categorical_crossentropy", optimizer=SGD(lr=0.01), metrics=["accuracy"])

10个纪元的准确度为86%,20个的准确度为94%,而30个的准确度为96%。训练如果在增加的话可能会导致过拟合引起准确度的下降,因此接下来我们将把这个模型运用到生活中去。

模型运用

我们将在Raspberry上运行。

加载模型:

with open(MODEL_JSON, 'r') as f:
model_data = f.read()
model = tf.keras.models.model_from_json(model_data)
model.load_weights(MODEL_H5)
graph = tf.get_default_graph()

并以此对下蹲Mask进行分类:

img = cv.imread(path + f, cv.IMREAD_GRAYSCALE)
img = np.reshape(img,[1,64,64,1])
with graph.as_default():
c = model.predict_classes(img)
return c[0] if c else None

在Raspberry上,输入为64x64的分类调用大约需要60-70毫秒,几乎接近实时。

最后让我们将以上所有部分整合到一个应用程序中:

• GET / —一个应用页面(下面有更多信息)

• GET / status-获取当前状态,下蹲次数和帧数

• POST / start —开始练习

• POST / stop —完成练习

• GET / stream —来自摄像机的视频流