【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

05-28 1985阅读

【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

  • 简介
  • 功能演示
  • 关键模块简介
    • 1.RC522模块简介
    • 2.AS608指纹识别模块简介
    • 3.k210人脸识别模块简介
    • 架构设计
      • 硬件设计
      • 软件设计
        • STM32F103部分
        • K210人脸识别部分
        • 关键代码
        • 踩坑记录
        • 代码仓库
            • github代码仓库链接:[https://github.com/qinikat/Smart_access_control_system](https://github.com/qinikat/Smart_access_control_system)
            • 百度网盘链接:[https://pan.baidu.com/s/1gw_Z79trMuGoiuCdtWPoYg?pwd=5zro](https://pan.baidu.com/s/1gw_Z79trMuGoiuCdtWPoYg?pwd=5zro) 提取码:5zro
            • **如果对你有帮助,请给作者一个小小的 `star` 这对我真的很重(大声哀求)。**

              简介

              Hi,大家好,这里是ikat易卡,最近花了几天的时间做了一个三合一的智慧门禁系统,可通过刷卡指纹扫脸解锁。其以STM32F103C8T6为主控,其中包含了MFRC522射频识别模块可通过刷卡解锁,AS608指纹模块可通过指纹解锁,K210用于人脸识别可通过刷脸解锁。成功解锁后通过控制舵机转动来模拟门禁打开。并做了断电存储的功能,可保证断电后录入的卡、指纹、人脸不丢失。

              此为:三合一智慧门禁系统

              大家可用于 课程设计 或 毕业设计

              这一套下来,成本在三百元以内。

              【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

              功能演示

              B站演示视频连接: https://www.bilibili.com/video/BV1eM4m197vr/

              三合一智慧门禁系统演示视频

              关键模块简介

              1.RC522模块简介

              RC522是一种非接触式读写卡芯片,底层采用SPI模拟时序,可以应用于校园一卡通、水卡充值消费、公交卡充值消费设计、门禁卡等。具体的工作原理我就不讲了,若有需要请查阅其他博主的文章。

              【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

              2.AS608指纹识别模块简介

              AS608模块是一种指纹识别模块,它可以用于身份验证、门禁系统、考勤系统等应用场景。采用串口UART通讯,模块提供了很多接口,且自带存储可存储指纹库,便于用户调用开发。具体的工作原理我就不讲了,若有需要请查阅其他博主的文章。

              【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

              3.k210人脸识别模块简介

              K210是一款由中国芯片设计公司寒武纪(Kendryte)推出的低功耗、高性能的人工智能处理器。它采用了RISC-V架构,拥有双核心的处理器和硬件加速器,能够实现图像识别、语音识别、物体检测等多种人工智能应用。由此可见k210比stm32f103单片机强太多了,所以这里我用MaixPy(一个用python开发的开发环境,其提供了许多库能快速地开发智能应用)来开发k210用于人脸识别,并采用串口UART与stm32通讯。

              我买的是以下的套件,其中包含了配套的屏幕,摄像头,到手插上就能用。还需有一张小于等于32G的SD卡与读卡器。

              【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

              【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

              架构设计

              硬件设计

              【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

              以上是STM32F103的引脚分配图,如图所见一共使用了3个串口,5个GPIO分配给RC522,4个GPIO分配给按键,2个GPIO分配给0.96寸OLED屏幕,1个GPIO用于控制舵机。

              以下是具体功能列表:

              • 串口1用于与AS608指纹解锁模块通讯,详细接法看硬件连接原理图
              • 串口2用于与K210通讯,K210的GPIO9连接stm32的PA3
              • 串口3用作调试串口,打印信息至电脑上位机
              • PA5,6,7,PB0,1连接RC522,用GPIO口模拟SPI时序与其通讯
              • PB6,7连接0.96寸OLED屏幕,用GPIO口模拟I2C时序与其通讯
              • PB12-15为按键输入口
              • PB9为舵机PWM输出口

                硬件连接原理图

                【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

                【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

                连接方式:

                由于各模块相对独立简单且没有大功率模块,所以我没有画PCB连接板而是采用洞洞板相互连接,洞洞板背面用飞线相连。然后stm32的5V得连接K210的5V引脚,3.3V就可以不用相连接了,记住5V引脚一定要相互连接,不然仅依靠3.3V是驱动不了K210的。

                【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

                软件设计

                STM32F103部分

                我几乎没用cubemx生成初始化代码,cubemx我一般当做图形化引脚分配的工具使用。

                关于系统设计框图,我有点懒,暂时先不画框图了,代码开源了你们自己看吧。后续可能会补上。

                我讲讲stm32代码大致执行思路:

                1. 首先初始化各种模块。
                2. 然后根据全局变量判断OLED显示哪一页,这一页能执行什么功能。
                3. 通过按键改变全局变量来改变页面显示与操作。
                4. 在接受到k210通过串口发来的数据后在中断里改变对应的全局变量,并在之后的循环里执行相应的代码。

                K210人脸识别部分

                我用的是MaixHub的现成模型,这模型需自行下载,因为是加密模型需填写机器码且每个板子的机器码不一样,如果机器码错误则会无法运行。所以得获取板子的机器码,下载模型的网址有获取机器码的教程。这里我遇到了个坑,详情请看 踩坑记录。

                以下是MaixHub中人脸识别模型的网址:https://maixhub.com/model/zoo/60

                【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别

                以下是使用K210人脸识别的几个步骤。默认你已经会了基本的k210使用方法。如果不会请自行搜索。

                1. K210加密模型获取。
                2. 烧录支持 kmodelv4 的固件。
                3. 使用读卡器插入SD至电脑,用下载的模型替换掉我代码仓库里的K210SD中的三个模型。
                4. 然后保存至SD卡中,电脑弹出SD,将其插入K210里,上电运行即可。
                5. 以下是K210全部代码。
                import sensor,image,lcd  # import 相关库
                import KPU as kpu   #模型库
                import time         #定时库
                import ubinascii    #二进制库
                import uos          #文件库
                from Maix import FPIOA,GPIO     #GPIO库
                from fpioa_manager import fm    #标准库
                from machine import UART        #串口库
                from machine import Timer       #定时器库
                from machine import WDT         #看门狗库
                fm.register(9,fm.fpioa.UART1_TX)#串口引脚映射
                fm.register(10,fm.fpioa.UART1_RX)
                fm.register(15, fm.fpioa.GPIO0)
                #fm.register(9, fm.fpioa.GPIOHS9)
                com = UART(UART.UART1, 115200, timeout=50, read_buf_len=4096)#构建串口对象
                #GPIO9 = GPIO(GPIO.GPIOHS9, GPIO.OUT)
                check = 0
                save = 0
                #看门狗回调函数
                def on_wdt(self):
                    return
                def on_timer(timer):  #回调函数
                    global check
                    global save
                    data = []
                    #data = com.read(2)
                    if data!=None:
                        if data == b'A':
                            check = 1#代表存储人脸特征
                        elif data == b'B':
                            check = 1
                            save = 1  #存到SD卡中
                        elif data == b'C':
                            save = 2  #清除人脸
                #定时器中断初始化
                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)
                #从SD卡中加载模型
                task_fd = kpu.load("/sd/FaceDetection.smodel") # 加载人脸检测模型
                task_ld = kpu.load("/sd/FaceLandmarkDetection.smodel") # 加载人脸五点关键点检测模型
                task_fe = kpu.load("/sd/FeatureExtraction.smodel") # 加载人脸196维特征值模型
                clock = time.clock()  # 初始化系统时钟,计算帧率
                key_pin=16 # 设置按键引脚 FPIO16
                fpioa = FPIOA()
                fpioa.set_function(key_pin,FPIOA.GPIO7)
                key_gpio=GPIO(GPIO.GPIO7,GPIO.IN)
                last_key_state=1
                key_pressed=0 # 初始化按键引脚 分配GPIO7 到 FPIO16
                def check_key(): # 按键检测函数,用于在循环中检测按键是否按下,下降沿有效
                    global last_key_state
                    global key_pressed
                    val=key_gpio.value()
                    if last_key_state == 1 and val == 0:
                        key_pressed=1
                    else:
                        key_pressed=0
                    last_key_state = val
                lcd.init()                          # 初始化lcd
                lcd.rotation(0)
                sensor.reset()                      #初始化sensor 摄像头
                sensor.set_pixformat(sensor.RGB565) #设置摄像头像素
                sensor.set_framesize(sensor.QVGA)   #设置窗口为配套屏幕大小
                sensor.set_hmirror(0)               #设置摄像头镜像
                sensor.set_vflip(2)                 #设置摄像头翻转
                sensor.set_auto_gain(1,0)           #摄像头自动增益
                sensor.run(1)                       #使能摄像头
                #使用官方库人脸检测算法
                #anchor for face detect 用于人脸检测的Anchor
                anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025)
                #standard face key point position 标准正脸的5关键点坐标 分别为 左眼 右眼 鼻子 左嘴角 右嘴角
                dst_point = [(44,59),(84,59),(64,82),(47,105),(81,105)]
                #初始化人脸检测模型
                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接受的格式
                record_ftr=[] #空列表 用于存储当前196维特征
                record_ftrs=[] #空列表 用于存储按键记录下人脸特征, 可以将特征以txt等文件形式保存到sd卡后,读取到此列表,即可实现人脸断电存储。
                names = ['Mr.1', 'Mr.2', 'Mr.3', 'Mr.4', 'Mr.5', 'Mr.6', 'Mr.7', 'Mr.8', 'Mr.9' , 'Mr.10'] # 人名标签,与上面列表特征值一一对应。
                #写入特征点到SD卡(转换为二进制)
                def save_feature(feat):
                    with open('/sd/data.txt','a') as f:
                        record =ubinascii.b2a_base64(feat)
                        f.write(record)
                #清除人脸
                def save_clear():
                    record_ftr = []
                    record_ftrs = []
                    with open("/sd/data.txt","w") as f:
                        f.write("")
                        f.close()
                #打开文件进行读取,如果有特征点信息,将其导入存储数组中
                with open('/sd/data.txt','rb') as f:
                    s = f.readlines()
                    for line in s:
                            #print(ubinascii.a2b_base64(line))
                        record_ftrs.append(bytearray(ubinascii.a2b_base64(line)))
                # check = 0
                # save = 0
                while(1): # 主循环
                    #GPIO9.value(1)
                    check_key() #按键检测
                    tim.start()
                    img = sensor.snapshot() #从摄像头获取一张图片
                    clock.tick() #记录时刻,用于计算帧率
                    code = kpu.run_yolo2(task_fd, img) # 运行人脸检测模型,获取人脸坐标位置
                    # if save == 2:
                    #     save = 0
                    #     save_clear()
                    #     #使用看门狗进行软件复位
                    #     wdt0 = WDT(id=1, timeout=1000, callback=on_wdt, context={})
                    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  80: # 如果最大分数大于80, 可以被认定为同一个人
                                a = img.draw_string(i.x(),i.y(), ("%s :%2.1f" % (names[index], max_score)), color=(0,255,0),scale=2) # 显示人名 与 分数
                                #串口发送数据,用于控制舵机
                                com.write("1")
                                #拉低GPIO9
                                #GPIO9.value(0)  
                                #屏幕显示文字识别成功
                                a = img.draw_string(10,50, ("successly identify"), color=(0,255,0),scale=3)
                                #刷新屏幕
                                a = lcd.display(img)
                                #延迟1s
                                time.sleep(1)
                                #拉高GPIO9
                                #GPIO9.value(1)
                            else:
                                a = img.draw_string(i.x(),i.y(), ("X :%2.1f" % (max_score)), color=(255,0,0),scale=2) #显示未知 与 分数
                            if key_pressed == 1:
                                key_pressed = 0
                                record_ftr = feature
                                record_ftrs.append(record_ftr)  #将特征点添加到比较数组中
                                save_feature(record_ftr)        #存到SD卡
                            break
                    a = lcd.display(img)    #刷屏显示
                    #kpu.memtest()
                #a = kpu.deinit(task_fe)
                #a = kpu.deinit(task_ld)
                #a = kpu.deinit(task_fd)
                

                关键代码

                1.STM32初始化代码

                int main(void)
                {
                	HAL_Init();
                	SystemClock_Config(); // 8M外部晶振,72M主频
                	MX_GPIO_Init();		   // 初始化输入引脚
                	MX_TIM4_Init();		   // 定时器4初始输出pwm控制舵机
                	MX_USART1_UART_Init(); // 串口1配置,PA9-> USART1_TX,PA10-> USART1_RX ,57600波特率,8位数据,1位停止位,无校验
                	MX_USART2_UART_Init(); // 与k210通讯串口,串口通讯.
                	MX_USART3_UART_Init(); // 调试串口
                	printf("Demo1");
                	OLED_Init(); // OLED初始化
                	OLED_ShowString(1, 1, "  Welcome Home! ");
                	OLED_ShowString(2, 1, "   loading...   ");
                	RFID_Init();  // RFID初始化
                	Key_Init();	  // 按键初始化
                	Servo_Init(); // 舵机初始化
                	while (GZ_HandShake(&AS608Addr)) // 初始化指纹模块
                	{
                		HAL_Delay(100); // 等待1秒
                		printf("finger init again\r\n");
                	}
                	Read_Card_Flash(); // 读取flash中的卡片ID
                	HAL_Delay(1000);
                	printf("所有模块已初始化成功\r\n");
                	OLED_ShowString(2, 1, "  INIT SUCCESS! ");
                	HAL_Delay(1000);
                	//接下来进入while(1)主循环
                

                2.cardID 4字节数据存入单片机flash,做到断电保存。

                // 将cardID 4字节 存入flash,判断ID 0-3
                void Add_Card_Flash()
                {
                	uint16_t cardID[2];
                	cardID[0] = UID[0] 
                		stmflash_write(0X08009000, cardID, 2);
                	}
                	else if (ID_select == 1)
                	{
                		stmflash_write(0X08009004, cardID, 2);
                	}
                	else if (ID_select == 2)
                	{
                		stmflash_write(0X08009008, cardID, 2);
                	}
                	else if (ID_select == 3)
                	{
                		stmflash_write(0X0800900C, cardID, 2);
                	}
                }
                
                					ensure = GZ_GenChar(CharBuffer1); // 生成特征
                					if (ensure == 0x00)
                					{
                						ensure = GZ_GetImage();
                						if (ensure == 0x00)
                						{
                							ensure = GZ_GenChar(CharBuffer2); // 生成特征
                							if (ensure == 0x00)
                							{
                								ensure = GZ_Match();
                								if (ensure == 0x00)
                								{
                									ensure = GZ_RegModel();
                									if (ensure == 0x00)
                									{
                										ensure = GZ_StoreChar(CharBuffer2, ID_select); // 储存模板
                										if (ensure == 0x00)
                										{
                											printf("录入成功");
                											GZ_ValidTempleteNum(&ValidN); // 读库指纹个数
                											printf("指纹库中有%d个指纹", ValidN);
                											OLED_Clear();
                											OLED_ShowString(2, 1, "  Add finger");
                											OLED_ShowString(3, 1, "   success");
                											HAL_Delay(3000); //
                											menu_page = 1;
                											ID_select = 0;
                											break;
                										}
                									}
                								}
                							}
                						}
                					}
                				}
                
                  if (huart-Instance == USART1)
                  {
                  }
                  else if (huart-Instance == USART2)
                  {
                    //多了以下代码就不会出现中断一次卡死的问题,
                    HAL_UART_Transmit(&huart2, (uint8_t *)"OK", 2, 0xffff);
                    //  清楚接收中断标志位
                    __HAL_UART_CLEAR_IDLEFLAG(&huart2);
                    // 打印接收到的数据是否为'1',若是则解锁成功
                    if (g_rx2_buffer[0] == 0x31)
                    {
                      face_flag = 1;
                      // HAL_UART_Transmit(&huart2, (uint8_t *)"unlock success!\r\n", 17, 0xffff);
                    }
                    while (HAL_UART_Receive_IT(&huart2, (uint8_t *)g_rx2_buffer, RXBUFFERSIZE_UART2) == HAL_OK) // 重新打开中断
                      ;
                    __HAL_UART_CLEAR_IDLEFLAG(&huart2);
                  }
                }
                

                3.关于K210机器码的获取问题,由于我买的K210的型号是Sipeed Maix Bit ( with Mic ) 不像其他的k210一样插上USB能出现两个串口,我手上的k210开发板只会出现一个串口,这个串口是用来下载与连接MaixPy的。烧录key_gen_v1.2.bin固件复位后不会从USB的串口中打印机器码,而是从GPIO9你没听错是从GPIO9打印出k210的机器码,所以说我还得连接杜邦线到TTL,很坑爹。而且网上的教程全是通过USB的串口打印的,根本没有提到还得用杜邦线单独引出,我当时是用杜邦线插上k210GPIO口的排针一个一个试出来的(悲)。

                代码仓库

                github代码仓库链接:https://github.com/qinikat/Smart_access_control_system

                百度网盘链接:https://pan.baidu.com/s/1gw_Z79trMuGoiuCdtWPoYg?pwd=5zro 提取码:5zro

                如果对你有帮助,请给作者一个小小的 star 这对我真的很重(大声哀求)。

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]