【毕业设计】三合一智慧门禁系统 单片机 嵌入式 stm32 k210人脸识别
【毕业设计】三合一智慧门禁系统 单片机 嵌入式 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用于人脸识别可通过刷脸解锁。成功解锁后通过控制舵机转动来模拟门禁打开。并做了断电存储的功能,可保证断电后录入的卡、指纹、人脸不丢失。
此为:三合一智慧门禁系统
大家可用于 课程设计 或 毕业设计
这一套下来,成本在三百元以内。
功能演示
B站演示视频连接: https://www.bilibili.com/video/BV1eM4m197vr/
三合一智慧门禁系统演示视频
关键模块简介
1.RC522模块简介
RC522是一种非接触式读写卡芯片,底层采用SPI模拟时序,可以应用于校园一卡通、水卡充值消费、公交卡充值消费设计、门禁卡等。具体的工作原理我就不讲了,若有需要请查阅其他博主的文章。
2.AS608指纹识别模块简介
AS608模块是一种指纹识别模块,它可以用于身份验证、门禁系统、考勤系统等应用场景。采用串口UART通讯,模块提供了很多接口,且自带存储可存储指纹库,便于用户调用开发。具体的工作原理我就不讲了,若有需要请查阅其他博主的文章。
3.k210人脸识别模块简介
K210是一款由中国芯片设计公司寒武纪(Kendryte)推出的低功耗、高性能的人工智能处理器。它采用了RISC-V架构,拥有双核心的处理器和硬件加速器,能够实现图像识别、语音识别、物体检测等多种人工智能应用。由此可见k210比stm32f103单片机强太多了,所以这里我用MaixPy(一个用python开发的开发环境,其提供了许多库能快速地开发智能应用)来开发k210用于人脸识别,并采用串口UART与stm32通讯。
我买的是以下的套件,其中包含了配套的屏幕,摄像头,到手插上就能用。还需有一张小于等于32G的SD卡与读卡器。
架构设计
硬件设计
以上是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输出口
硬件连接原理图
连接方式:
由于各模块相对独立简单且没有大功率模块,所以我没有画PCB连接板而是采用洞洞板相互连接,洞洞板背面用飞线相连。然后stm32的5V得连接K210的5V引脚,3.3V就可以不用相连接了,记住5V引脚一定要相互连接,不然仅依靠3.3V是驱动不了K210的。
软件设计
STM32F103部分
我几乎没用cubemx生成初始化代码,cubemx我一般当做图形化引脚分配的工具使用。
关于系统设计框图,我有点懒,暂时先不画框图了,代码开源了你们自己看吧。后续可能会补上。
我讲讲stm32代码大致执行思路:
- 首先初始化各种模块。
- 然后根据全局变量判断OLED显示哪一页,这一页能执行什么功能。
- 通过按键改变全局变量来改变页面显示与操作。
- 在接受到k210通过串口发来的数据后在中断里改变对应的全局变量,并在之后的循环里执行相应的代码。
K210人脸识别部分
我用的是MaixHub的现成模型,这模型需自行下载,因为是加密模型需填写机器码且每个板子的机器码不一样,如果机器码错误则会无法运行。所以得获取板子的机器码,下载模型的网址有获取机器码的教程。这里我遇到了个坑,详情请看 踩坑记录。
以下是MaixHub中人脸识别模型的网址:https://maixhub.com/model/zoo/60
以下是使用K210人脸识别的几个步骤。默认你已经会了基本的k210使用方法。如果不会请自行搜索。
- K210加密模型获取。
- 烧录支持 kmodelv4 的固件。
- 使用读卡器插入SD至电脑,用下载的模型替换掉我代码仓库里的K210SD中的三个模型。
- 然后保存至SD卡中,电脑弹出SD,将其插入K210里,上电运行即可。
- 以下是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 这对我真的很重(大声哀求)。