• 【毕业设计】stm32机器视觉的人脸识别系统 - 单片机 物联网 嵌入式



    0 前言

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

    为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

    🚩 基于stm32机器视觉的人脸识别系统

    🥇学长这里给一个题目综合评分(每项满分5分)

    • 难度系数:4分
    • 工作量:4分
    • 创新点:3分

    🧿 选题指导, 项目分享:

    https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md


    1 简介

    本系统是由stm32f103c8t6单片机最小系统电路+k210人脸识别模块实现的人脸智能门禁系统,可实现人脸录入,人脸识别等功能。

    2 主要器件

    • STM32F103C8T6
    • K210人脸识别电路
    • 语音播报电路
    • 自动开关门电路(舵机驱动)
    • oled屏幕显示电路
    • 按键电路
    • 电源电路组成

    3 实现效果

    整体展示
    在这里插入图片描述
    人脸识别后,舵机会旋转打开
    在这里插入图片描述

    4 设计原理

    K210实现人脸识别

    人脸识别模型源码获取

    去官网获取人脸识别源码和模型
    在这里插入图片描述
    注:先获取机器码(https://cn.bbs.sipeed.com/d/541-maixhub)在下载。
    所需要的文件有:模型文件,程序运行的脚本文件,ide需要的bin文件。
    用MaixPy连接开发板运行代码,至此就完成了基本的人脸识别功能,上述代码含有中文注释,方便大家理解代码。
    根据官网提供的源码,我们可以知道人脸识别大概是怎么个流程。
    根据代码来看,主要分为以下几个过程:
    1、加载各种模型
    2、运行人脸检测模型,在图片中找到人脸位置并框出人脸
    3、将裁出的人脸图片转换成kpu接收的格式
    4、运行人脸5点关键点模型,获取到左眼、右眼、鼻子、左嘴角、右嘴角的位置
    5、对原始图片人脸图片进行仿射变换,变换为正脸图像,将正脸图像转为kpu格式
    6、使用人脸196维特征值模型计算正脸图片的196维特征值,计算得到最终的人脸特征feature
    再将得到的人脸特征与之前保存过的人脸特征进行对比得到一组分数,选择其中最大的一个分数,且该分数超过85分(可以自己设置)就认为识别出该人,并根据对应下标从names列表中得到该人的姓名。
    烧录
    下载好后,接下来实现的就是固件烧录,我们需要准备一个软件kflash_gui,是专门用来烧录固件的,在sipeed官网会提供一个GitHub网址,里面不仅有该软件,还有大伙所需要的例程,在例程中可以找到已经训练好的人脸模型。如下图,按照这样配置即可,连接好板子就可以烧录了。
    在这里插入图片描述 终于,现在就可以点击ide打开了,这就是ide的界面,点左下角的连接板子,就可以愉快的跑程序了。
    在这里插入图片描述

    5 部分核心代码

    STM32代码

    #include "HLK210.h"
    
    #include "bsp_i2c_gpio.h"
    #include "bsp_i2c_OLED.h"
    #include "delay.h"
    #include "usart3.h" 
    
    unsigned char HLK210_FaceStorageCmd[18] ={0x5A, 0x2C, 0x00, 0x00, 0x00, 0x12, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xE5};//人脸录入指令
    unsigned char HLK210_FaceCheckCmd[18] = {0x5A, 0x2C, 0x00, 0x00, 0x00, 0x12, 0x02, 0x0C, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xA8};  //人脸识别指令
    unsigned char HLK210_FaceDelete[18] = {0x5A, 0x2C, 0x00, 0x00, 0x00, 0x12, 0x0D, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x1E};  //人脸清空指令
    unsigned char HLK210_LightOn[18] = {0x5A, 0x2C, 0x00, 0x00, 0x00, 0x12, 0x12, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xAA, 0x15};  //开补光灯指令
    
    unsigned char HLK210_RECEICV_BUFFER[HLK210_USART_REC_LEN];
    
    _HLK210_Data HLK210_Data;
    u8 HLK210_point=0;
    void HLK210_Receive_Byte(u8 res) //将此函数放到串口中断函数里,res形参放串口接收值。
    {
    	HLK210_RECEICV_BUFFER[HLK210_point++] = res;
    	if(HLK210_point>1 && (HLK210_RECEICV_BUFFER[HLK210_point-2]==0x5a && HLK210_RECEICV_BUFFER[HLK210_point-1]==0x2c))
    	{
    		HLK210_RECEICV_BUFFER[0]=HLK210_RECEICV_BUFFER[HLK210_point-2];
    		HLK210_RECEICV_BUFFER[1]=HLK210_RECEICV_BUFFER[HLK210_point-1];
    		HLK210_point=2;
    	}
    	if(HLK210_point>=HLK210_Data.CmdDataLen)
    	{
    		HLK210_Data.isGetData = TRUE;
    		memset(HLK210_Data.HLK210_Buffer, 0, HLK210_USART_REC_LEN);      //清空
    		memcpy(HLK210_Data.HLK210_Buffer,HLK210_RECEICV_BUFFER, HLK210_point); 	//保存数据
    	}
    	if(HLK210_point>HLK210_USART_REC_LEN)
    	{
    		HLK210_point=HLK210_USART_REC_LEN-1;
    	}
    }
    void HLK210_ClearDate(void)
    {
    	memset(HLK210_RECEICV_BUFFER, 0, HLK210_USART_REC_LEN);      //清空
    	HLK210_point=0;
    	
    	HLK210_Data.CmdDataLen = HLK210_NORMALANSWERLEN;
    	memset(HLK210_Data.HLK210_Buffer, 0, HLK210_USART_REC_LEN);      //清空
    	HLK210_Data.isGetData = FALSE;
    	HLK210_Data.isParseData = FALSE;
    	HLK210_Data.isUsefull =  FALSE;
    	HLK210_Data.NowMode = NORMAL;
    	HLK210_Data.TimeCount = 0;
    }
    
    void HLK210_Send_Cmd(u8 *cmd)
    {
    	u8 i=0;
    	for(i=0;i<18;i++)
    	{
    		HLK210_SendByte(*cmd++);  // == *(cmd++) 取cmd所指单元的值,cmd指向下一单元,即cmd自加1
    	}
    }
    void HLK210_InputFaceID(void)
    {
    	u8 i=0;
    	HLK210_ClearDate();
    	HLK210_Data.CmdDataLen = HLK210_NORMALANSWERLEN;
    	HLK210_Data.NowMode = INPUTFACEID;
    	
    	HLK210_Send_Cmd(HLK210_LightOn);  //开补光灯
    	while(HLK210_Data.isGetData == FALSE); //等待接收完应答数据
    	HLK210_Data.isGetData = FALSE;
    	for(i=0;i<HLK210_Data.CmdDataLen;i++)
    	{
    		USART3_SendByte(HLK210_Data.HLK210_Buffer[i]);
    	}
    	
    	HLK210_Send_Cmd(HLK210_FaceStorageCmd);
    	while(HLK210_Data.isGetData == FALSE); //等待接收完应答数据
    	HLK210_Data.isGetData = FALSE;
    	for(i=0;i<HLK210_Data.CmdDataLen;i++)
    	{
    		USART3_SendByte(HLK210_Data.HLK210_Buffer[i]);
    	}
    	HLK210_Data.CmdDataLen = HLK210_FACEANSWERLEN;
    //	while(HLK210_Data.isGetData == FALSE); //等待接收完应答数据
    //	HLK210_Data.isGetData = FALSE;
    //	for(i=0;i
    //	{
    //		USART3_SendByte(HLK210_Data.HLK210_Buffer[i]);
    //	}
    }
    void HLK210_CheckFaceID(void)
    {
    	u8 i=0;
    	HLK210_ClearDate();
    	HLK210_Data.CmdDataLen = HLK210_NORMALANSWERLEN;
    	HLK210_Data.NowMode = CHECKFACEID;
    	
    	HLK210_Send_Cmd(HLK210_LightOn);  //开补光灯
    	while(HLK210_Data.isGetData == FALSE); //等待接收完应答数据
    	HLK210_Data.isGetData = FALSE;
    	for(i=0;i<HLK210_Data.CmdDataLen;i++)
    	{
    		USART3_SendByte(HLK210_Data.HLK210_Buffer[i]);
    	}
    	
    	HLK210_Send_Cmd(HLK210_FaceCheckCmd);
    	while(HLK210_Data.isGetData == FALSE); //等待接收完应答数据
    	HLK210_Data.isGetData = FALSE;
    	for(i=0;i<HLK210_Data.CmdDataLen;i++)
    	{
    		USART3_SendByte(HLK210_Data.HLK210_Buffer[i]);
    	}
    	HLK210_Data.CmdDataLen = HLK210_RECOANSWERLEN;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    k210代码

    import sensor,image,lcd  # import 相关库
    import KPU as kpu
    import time
    import os
    from Maix import FPIOA,GPIO
    from fpioa_manager import fm
    import ubinascii
    from machine import UART
    from machine import Timer
    import time
    
    fm.register(23,fm.fpioa.UART1_TX)#串口引脚映射
    fm.register(19,fm.fpioa.UART1_RX)#这两个引脚是可以任意修改的
    
    com = UART(UART.UART1, 115200, timeout=50, read_buf_len=4096)#构建串口对象
    
    #定时器中断回调函数
    tem = ''
    b = []
    check = 0  #存储
    save = 0   #临时存储
    switch = 0 #切换
    def on_timer(timer):  #回调函数
        global check
        global save
        data = []
        data = com.read(2)
        if data!=None:
            print(data)
            check = 1#代表存储人脸特征
            if data == b'A':
                save = 0  #不存到SD卡中
            elif data == b'C':
                save = 1  #存到SD卡中
            print(data)
    #定时器中断初始化
    tim = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_ONE_SHOT, period=500,
                 unit=Timer.UNIT_MS,callback=on_timer, arg=on_timer,start=False)
    
    feature_file_exists = 0
    for v in os.ilistdir('/sd'):#to check key directorys or files in sd card.sd card should be formated to fat32
        if v[0] == 'features.txt' and v[1] == 0x8000:#0x8000 is file
            feature_file_exists = 1
    
    record_ftr=[] #空列表 用于存储当前196维特征
    record_ftrs=[] #空列表 用于存储按键记录下人脸特征, 可以将特征以txt等文件形式保存到sd卡后,读取到此列表,即可实现人脸断电存储。
    names = ['LX', 'HLF', 'MR.3', 'Mr.4', 'Mr.5', 'Mr.6', 'Mr.7', 'Mr.8', 'Mr.9' , 'Mr.10'] # 人名标签,与上面列表特征值一一对应。
    reco = ''
    record = []
    def save_feature(feat):
        with open('/sd/features.txt','a') as f:
            record =ubinascii.b2a_base64(feat)
            f.write(record)
    
    st = ''
    if(feature_file_exists):
        print("start")
        with open('/sd/features.txt','rb') as f:
            s = f.readlines()
            print(len(s))
            #print(s)
            for line in s:
                #print(ubinascii.a2b_base64(line))
                record_ftrs.append(bytearray(ubinascii.a2b_base64(line)))
    
    print(record_ftr,names)
    
    print(len(record_ftr))
    print("end")
    
    task_fd = kpu.load(0x300000) # 从flash 0x200000 加载人脸检测模型
    task_ld = kpu.load(0x400000) # 从flash 0x300000 加载人脸五点关键点检测模型
    task_fe = kpu.load(0x500000) # 从flash 0x400000 加载人脸196维特征值模型
    clock = time.clock()  # 初始化系统时钟,计算帧率
    
    lcd.init() # 初始化lcd
    sensor.reset() #初始化sensor 摄像头
    sensor.set_pixformat(sensor.RGB565)
    sensor.set_framesize(sensor.QVGA)
    sensor.set_hmirror(1) #设置摄像头镜像
    sensor.set_vflip(1)   #设置摄像头翻转
    lcd.rotation()
    sensor.run(1) #使能摄像头
    
    anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025) #anchor for face detect 用于人脸检测的Anchor
    dst_point = [(44,59),(84,59),(64,82),(47,105),(81,105)] #standard face key point position 标准正脸的5关键点坐标 分别为 左眼 右眼 鼻子 左嘴角 右嘴角
    a = kpu.init_yolo2(task_fd, 0.5, 0.3, 5, anchor) #初始化人脸检测模型
    img_lcd=image.Image() # 设置显示buf
    img_face=image.Image(size=(128,128)) #设置 128 * 128 人脸图片buf
    a=img_face.pix_to_ai() # 将图片转为kpu接受的格式
    
    while(1): # 主循环
        check = 0
        save = 0
        tim.start()  #定时器中断开始
        img = sensor.snapshot() #从摄像头获取一张图片
        clock.tick() #记录时刻,用于计算帧率
        code = kpu.run_yolo2(task_fd, img) # 运行人脸检测模型,获取人脸坐标位置
        #b = img.draw_string(0,0, ("tem"), color=(0,255,0),scale=2)
        if code: # 如果检测到人脸
            for i in code: # 迭代坐标框
                # Cut face and resize to 128x128
                a = img.draw_rectangle(i.rect()) # 在屏幕显示人脸方框
                face_cut=img.cut(i.x(),i.y(),i.w(),i.h()) # 裁剪人脸部分图片到 face_cut
                face_cut_128=face_cut.resize(128,128) # 将裁出的人脸图片 缩放到128 * 128像素
                a=face_cut_128.pix_to_ai() # 将猜出图片转换为kpu接受的格式
                #a = img.draw_image(face_cut_128, (0,0))
                # Landmark for face 5 points
                fmap = kpu.forward(task_ld, face_cut_128) # 运行人脸5点关键点检测模型
                plist=fmap[:] # 获取关键点预测结果
                le=(i.x()+int(plist[0]*i.w() - 10), i.y()+int(plist[1]*i.h())) # 计算左眼位置, 这里在w方向-10 用来补偿模型转换带来的精度损失
                re=(i.x()+int(plist[2]*i.w()), i.y()+int(plist[3]*i.h())) # 计算右眼位置
                nose=(i.x()+int(plist[4]*i.w()), i.y()+int(plist[5]*i.h())) #计算鼻子位置
                lm=(i.x()+int(plist[6]*i.w()), i.y()+int(plist[7]*i.h())) #计算左嘴角位置
                rm=(i.x()+int(plist[8]*i.w()), i.y()+int(plist[9]*i.h())) #右嘴角位置
                a = img.draw_circle(le[0], le[1], 4)
                a = img.draw_circle(re[0], re[1], 4)
                a = img.draw_circle(nose[0], nose[1], 4)
                a = img.draw_circle(lm[0], lm[1], 4)
                a = img.draw_circle(rm[0], rm[1], 4) # 在相应位置处画小圆圈
                # align face to standard position
                src_point = [le, re, nose, lm, rm] # 图片中 5 坐标的位置
                T=image.get_affine_transform(src_point, dst_point) # 根据获得的5点坐标与标准正脸坐标获取仿射变换矩阵
                a=image.warp_affine_ai(img, img_face, T) #对原始图片人脸图片进行仿射变换,变换为正脸图像
                a=img_face.ai_to_pix() # 将正脸图像转为kpu格式
                #a = img.draw_image(img_face, (128,0))
                del(face_cut_128) # 释放裁剪人脸部分图片
                # calculate face feature vector
                fmap = kpu.forward(task_fe, img_face) # 计算正脸图片的196维特征值
                feature=kpu.face_encode(fmap[:]) #获取计算结果
                reg_flag = False
                scores = [] # 存储特征比对分数
                for j in range(len(record_ftrs)): #迭代已存特征值
                    score = kpu.face_compare(record_ftrs[j], feature) #计算当前人脸特征值与已存特征值的分数
                    scores.append(score) #添加分数总表
                max_score = 0
                index = 0
                for k in range(len(scores)): #迭代所有比对分数,找到最大分数和索引值
                    if max_score < scores[k]:
                        max_score = scores[k]
                        index = k
                if max_score > 80: # 如果最大分数大于85, 可以被认定为同一个人
                    a = img.draw_string(i.x(),i.y(), ("%s :%2.1f" % (names[index], max_score)), color=(0,255,0),scale=2) # 显示人名 与 分数
                else:
                    a = img.draw_string(i.x(),i.y(), ("X :%2.1f" % (max_score)), color=(255,0,0),scale=2) #显示未知 与 分数
                    if check ==1: #如果检测到串口数据
                       check = 0
                       record_ftr = feature
                       record_ftrs.append(record_ftr) #将当前特征添加到已知特征列表
                       if save== 1:  #如果存储
                          save = 0
                          a = img.draw_string(100,100, "Stor successful", color=(0,255,0),scale=2)
                          print("stor successful")
                          save_feature(record_ftr) #存到SD卡
                       break
        a = img.draw_string(0,220,"face", color=(255,0,0),scale=2) #显示未知 与 分数
        a = lcd.display(img) #刷屏显示
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159

    🧿 选题指导, 项目分享:

    https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md


    6 最后

  • 相关阅读:
    Vue指令
    VGG16 实现图像分类的 pytorch 实现、步骤精讲
    Linux进程
    用简单例子讲清楚webgl模板测试
    关于App质量把控,我的复盘与思考(上)
    基于Advisor实现AOP
    PTA | Wifi密码
    ubuntu 22 某 clash 某 verge 软件界面无法正常显示
    ZZNUOJ_用C语言编写程序实现1342:支配值数目(附完整源码)
    黑马笔记---常见算法与Lambda表达式
  • 原文地址:https://blog.csdn.net/m0_71572576/article/details/126358763