Micropython实现表情识别控制小车运行

一.写在前面

1.本实验是表情识别在工程应用中的先验实验,简单的说就是检验训练好的表情识别模型在PC端的输出结果能否远程控制无人小车进行简单前后左右的移动。(老实说我并不知道这玩意的实际价值是什么...)

2.由于表情识别模型涉及在投论文及毕设,本文所依赖的模型只提供训练好的.h5文件,想自己训练的朋友们可以参考我另一篇基础博客或网上开源的基础代码,复杂的模型训练不适合其它方向的研究者们。

基础训练方法:
(1条消息) 基于卷积神经网络的人脸表情识别(JAFFE篇)_物理系的计算机选手的博客-CSDN博客

同组的小伙伴也整理了一篇基于openvino的表情识别,感兴趣的可以参考:

(8条消息) 【python】利用opencv+openvino实现人脸检测和表情识别_通过图灵测试的人类的博客-CSDN博客_opencv表情识别

h5文件及人脸检测器下载地址:

链接:https://pan.baidu.com/s/1pnfYwQTiMbTiRtvCD6NP1A?pwd=d3fv 
提取码:d3fv 

二. 硬软件及理论知识准备

1.带有 ESP32、树莓派等支持python语言芯片的小车(新手无脑推荐01Studio)

2.PC端:类似Thonny可以变成micropython的编程平台,pycharm(深度学习开发必备,esp32不建议使用ipython)

PC端pycharm还需额外安装:

(1) tensorflow2.4以上版本,如果要使用GPU请自行查看对应的cuda等(最简单的方式就是最新版本,显卡的驱动可以用官网升级到最高,当然网上各种安装方法都是可以的。)

pip install tensorflow(这个是安装到最新版本的,直接在pycharm的工作台里安装,就是下面这个,不要去外面按!!! 低版本则==对应版本号)

c1e4d9ab3ca643308f93eb067459b62d.png

(2) pip install opencv

3.需要简单的了解小车的编程,简单的了解socket中的通信协议。

三.无人小车编程部分

    小车的前进后退代码是根据不同主控的小车预先编程实现的,在这里我们使用01Studio家的代码,当然你也可以自己编写,十分简单。我们主要来看一下,小车如何通过wifi接受pc端的指令。很明显,首先建立一个wifi连接,在连接成功的时候使用socket包接受电脑端信号并作出反馈。

    下面的代码段中已经是很详细的解释了,要注意的是进行wifi连接的时候需要改成自己的网络,一般为ipv4,2.4Ghz较容易实现。另外,初始化模块不一定需要像我一样,简单的说就是啥车啥样。

'''
实验名称:表情识别控制小车
版本:v1.0
日期:2022.6
作者:渣文
说明:通过Socket编程实现pyWiFi-ESP32与电脑服务器助手建立TCP连接,通过电脑端预训练好的表情识别模型进行远程控制小车。
'''

#导入相关模块
import network,socket,time
from machine import SoftI2C,Pin,Timer
from ssd1306 import SSD1306_I2C
from car import CAR

#初始化相关模块
Car = CAR()
i2c = SoftI2C(sda=Pin(25), scl=Pin(23))
oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)

#WIFI连接函数
def WIFI_Connect():

    WIFI_LED=Pin(2, Pin.OUT) #初始化WIFI指示灯

    wlan = network.WLAN(network.STA_IF) #STA模式
    wlan.active(True)                   #激活接口
    start_time=time.time()              #记录时间做超时判断

    if not wlan.isconnected():
        print('Connecting to network...')
        wlan.connect('603', '853853853') #输入WIFI账号密码

        while not wlan.isconnected():

            #LED闪烁提示
            WIFI_LED.value(1)
            time.sleep_ms(300)
            WIFI_LED.value(0)
            time.sleep_ms(300)

            #超时判断,15秒没连接成功判定为超时
            if time.time()-start_time > 15 :
                print('WIFI Connected Timeout!')
                break

    if wlan.isconnected():
        #LED点亮
        WIFI_LED.value(1)

        #串口打印信息
        print('network information:', wlan.ifconfig())

        #OLED数据显示
        oled.fill(0)   #清屏背景黑色
        oled.text('IP/Subnet/GW:',0,0)
        oled.text(wlan.ifconfig()[0], 0, 20)
        oled.text(wlan.ifconfig()[1],0,38)
        oled.text(wlan.ifconfig()[2],0,56)
        oled.show()
        return True

    else:
        return False

def Socket_fun(tim):

    text=s.recv(128) #单次最多接收128字节
    if text == '':
        pass

    else: #打印接收到的信息为字节,可以通过decode('utf-8')转成字符串
        print(text)
        s.send('I got:'+text.decode('utf-8'))



#如果WIFI链接成功
if WIFI_Connect():
    print('已在WiFi模式')
    addr=('192.168.101.41',8080)
    s=socket.socket()
    s.bind(addr)
    s.listen(10)
#     print('listening on:', addr)
    
    while True:
        print('等待电脑连接.....')
        cl, addr = s.accept()
        print('新连接')
        print('IP is %s'% addr[0])
        print('port is %d\n'% addr[1])
        
        
        while True:
            msg = cl.recv(1024)
            print(msg.decode("utf-8"))
            print("读取完成")
            
            if msg == b"1":
                print("前进!")
                Car.forward()
                time.sleep(1)
                Car.stop()
            
            if msg == b"2":
                print("后退!")
                Car.backward()
                time.sleep(1)
                Car.stop()
            
            if msg == b"3":
                print("左转!")
                Car.turn_left(mode=1)
                time.sleep_ms(250)
                Car.stop()
            
            if msg == b"4":
                print("右转!")
                Car.turn_right(mode=1)
                time.sleep_ms(250)
                Car.stop()

    另外,WiFi的地址是小车的IP,不是本机的IP!小车的ip在前面建立wifi的过程中显示了,你可以在编程平台Thonny或者小车的显示平上查看(没有显示屏要删除对应的代码)

四.PC端代码实现

    PC端的代码分为两部分,一部分是通过socket协议实现wifi控制小车,一部分是对表情识别模型进行界面输出。首先,我们来实现wifi控制,这部分是很简单的,代码如下:

import socket

print('客户端开启')
# socket.AF_INET, socket.SOCK_STREAM
mysocket = socket.socket()


try:
    mysocket.connect(('192.168.101.41', 8080))
    print("连接小车")
    while True:
        # 发送消息
        msg = input("客户端发送:")
        # 编码发送
        mysocket.send(msg.encode("utf-8"))


except:
    print("连接失败,请检查网络配置!")

    其实到这里我们就可以直接运行了,对小车有所了解的朋友可能已经看出,这就是常规的wifi控制小车的设计思路,只不过在常规的情况下大家会在PC端做一个界面,为了方便后续连接表情识别的结果,我们这里以数字的形式进行控制。数字输出暂定为:

数字 功能
0  
1 前进
2 后退
3 左转
4 右转
5  
6  

【注1】 socket.AF_INET表示的是小车与电脑进行热点直连,但这样的话你的电脑有可能连不上wifi(部分),为了后续进行花样操作,还是建议wifi连接而不是热点。

【注2】对于不了解深度学习训练的童鞋,建议按照顺序逐步进行实验(特别是没装各种包的,cuda没配置的,当然cpu也是可以跑的就是慢呗)

    言归正传,我们来对已经训练好的表情模型进行调用,这里要说的是尺寸大小是无法修改的,因为我与训练好的模型就是那个尺寸大小,如果你想使用其它大小的或者使用彩色通道,只能重新训练模型!如果你想得到一个预测效果较好的模型,需要准备更多的理论知识和一个12GB显存以上的电脑或服务器。

from keras.models import load_model
import cv2
import numpy as np

# 加载模型
model = load_model("./ferNet.h5")
# 查看模型结构
# model.summary()
# 调入图像
file = cv2.imread('test_happy.jpg')
# file = cv2.resize(file, (48, 48))
# file = cv2.cvtColor(file, cv2.COLOR_BGR2GRAY)
# print(file.shape)


# 构建预测模型
def face_predict(image):
    image_resize = cv2.resize(image, (48, 48))
    image_resize = cv2.cvtColor(image_resize, cv2.COLOR_RGB2GRAY)
    image_resize = image_resize.reshape((1, 48, 48, 1))

    image_test = image_resize.astype('float32')
    image_test /= 255

    prediction = model.predict(image_test)
    predict = np.argmax(prediction)

    return predict


a = face_predict(file)
print(a)

【注3】模型文件放在根目录下即可,同样测试的表情图片也放在那。

2af50a03ebfe4f009a9920b6f3d9cf8c.png

这个就是单独运行上述程序输出的结果,具体数字对应的表情如下表所示:

数字 表情
0 angry
1 disgust
2 fear
3 happy
4 neutral
5 sad
6 surprise

    做好了这些准备工作之后,我们就可以进行直接的编程了,下面的代码是直接用表情控制小车前进,由于时间的问题,下面的代码只设置了happy=小车前进,运行代码后若你的pc端设备配置不够、wifi较卡,实时控制的效果较为卡顿。

"""
实验名称:表情控制无人车PC端
版本:v1.0
日期:2022.11
作者:皮皮强
说明:PC端的代码,注意一定要在运行小车端之后在运行本文件
"""


# 导入包
import socket
import sys

from keras.models import load_model
import cv2
import numpy as np

# 加载模型
model = load_model("./ferNet.h5")
# 查看模型结构
# model.summary()


# 构建预测模型
def face_predict(image):
    image_resize = cv2.resize(image, (48, 48))
    image_resize = cv2.cvtColor(image_resize, cv2.COLOR_RGB2GRAY)
    image_resize = image_resize.reshape((1, 48, 48, 1))

    image_test = image_resize.astype('float32')
    image_test /= 255

    prediction = model.predict(image_test)
    predict = np.argmax(prediction)

    return predict


print("客户端开启...")
mysocket = socket.socket()

if __name__ == '__main__':
    try:
        mysocket.connect(('192.168.101.41', 8080))
        print("小车连接成功!")

        while True:
            # 调用摄像头
            if len(sys.argv) != 1:
                print("Usage:%s camera_id\r\n" % (sys.argv[0]))
                sys.exit(0)

            # 设置颜色、引入人脸识别分类器
            color = (0, 255, 0)
            cv2.namedWindow('bad')
            cap = cv2.VideoCapture(0)
            cascade_path = "./haarcascade_frontalface_alt2.xml"

            while True:
                ret, fra = cap.read()

                if ret is True:
                    frame_gray = cv2.cvtColor(fra, cv2.COLOR_RGB2GRAY)
                else:
                    continue

                cascade = cv2.CascadeClassifier(cascade_path)

                faceRects = cascade.detectMultiScale(frame_gray, scaleFactor=1.2, minNeighbors=3, minSize=(32, 32))

                if len(faceRects) > 0:
                    for faceRect in faceRects:
                        x1, y, w, h = faceRect
                        print(x1, y, w, h)
                        # test_image = []
                        image = fra[y - 10: y + h + 10, x1 - 10: x1 + w + 10]

                        # 输出表情预测值
                        predict = face_predict(image)
                        print(predict)

                        if predict == 0:
                            cv2.rectangle(fra, (x1, y), (x1 + w + 10, y + h + 10), color, 2)

                            cv2.putText(fra, 'angry',
                                        (x1 + 30, y + 30),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        1,
                                        (255, 0, 255),
                                        2)

                            # 设定传输
                            # msg = 2
                            # mysocket.send(msg)

                        if predict == 1:
                            cv2.rectangle(fra, (x1, y), (x1 + w + 10, y + h + 10), color, 2)

                            cv2.putText(fra, 'disgust',
                                        (x1 + 30, y + 30),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        1,
                                        (255, 0, 255),
                                        2)

                        if predict == 2:
                            cv2.rectangle(fra, (x1, y), (x1 + w + 10, y + h + 10), color, 2)

                            cv2.putText(fra, 'fear',
                                        (x1 + 30, y + 30),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        1,
                                        (255, 0, 255),
                                        2)

                        if predict == 3:
                            cv2.rectangle(fra, (x1, y), (x1 + w + 10, y + h + 10), color, 2)

                            cv2.putText(fra, 'happy',
                                        (x1 + 30, y + 30),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        1,
                                        (255, 0, 255),
                                        2)

                            msg = str(predict-2)
                            mysocket.send(msg.encode("utf-8"))

                        if predict == 4:
                            cv2.rectangle(fra, (x1, y), (x1 + w + 10, y + h + 10), color, 2)

                            cv2.putText(fra, 'neutral',
                                        (x1 + 30, y + 30),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        1,
                                        (255, 0, 255),
                                        2)

                        if predict == 5:
                            cv2.rectangle(fra, (x1, y), (x1 + w + 10, y + h + 10), color, 2)

                            cv2.putText(fra, 'sad',
                                        (x1 + 30, y + 30),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        1,
                                        (255, 0, 255),
                                        2)

                        if predict == 6:
                            cv2.rectangle(fra, (x1, y), (x1 + w + 10, y + h + 10), color, 2)

                            cv2.putText(fra, 'surprise',
                                        (x1 + 30, y + 30),
                                        cv2.FONT_HERSHEY_SIMPLEX,
                                        1,
                                        (255, 0, 255),
                                        2)

                cv2.imshow('bad', fra)
                c = cv2.waitKey(10)


    except:
        print("小车连接失败,请检查网络IP、端口是否输入正确!")

【注4】输出的结果转换为传输给小车的语句为

 msg = str(predict-2)
 mysocket.send(msg.encode("utf-8"))

一定要这么写,狗日的.send()只接受字节...

实验视频展示见下:

表情控制无人小车实验展示及代码

五.问题及解决思路

1.每次运行都要先用Thonny激活小车,再用pc端运行表情代码。这个解决办法可以将小车的代码做成离线模式,pc端通过优化编程方式来实现,防止程序崩溃。

2.表情控制有个bug,你除了高兴和平常两个表情比较好做,余下的只有生气和难过变化较大,但是我们知道表情识别模型的精度一直是个问题。openvino中开源的效果极好的只有五种基本表情,但对于本模型(7-8)个表情中,复合表情的识别精度较低,且容易干扰普通的表情。(这个问题的解决办法就是一篇小论文了,你可以在顶会等各路高手的论文中找到思路,但考虑到实际运行程序的可实行、姿态、光线等各种问题,暂时没得很好的办法,这也是我在研究的问题。)当然最粗暴的解决办法是,实时扫描表情的时候将你的脸换成数据集中的人脸....

3.彩蛋:各位朋友可以猜得出这是啥实验的先验实验么<(*^_^*)>,欢迎评论区留言。

猜你喜欢

转载自blog.csdn.net/qq_40981869/article/details/128001819