完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
前言
这篇记录一下我自己DIY人脸识别门锁的经验。 为什么用esp8266,因为比esp32便宜几块钱,批发甚至只要6块,哈哈 由于micropython我也刚上手,也是学一点记录一点,当帮大家提前踩坑了~ 废话不多说,直接上例子! 千万不要被标题骗了,光单片机是达不到人脸识别的,性能肯定不够。那么我们需要一个服务器来对人脸进行比对,我用的是自己的一个arm服务器。80多买的n1盒子,刷的centos,1.5G 4线程处理器对于我个人还是够用。如果有同学需要,我也可以单独出一篇我的服务器搭建踩坑经历。当然你也可以用你的pc来当服务器,性能绝对够用。如何使用wsl刷入Ubuntu,我前面也有说,就不废话了。 一、服务器环境搭建 我用的是centos 7,经过实验,Ubuntu的操作差不多。由于是使用centos,gcc版本肯定不够。那么需要升级gcc版本,可以升级到7或9,我就是这个坑浪费了一个多小时。 python3安装(都使用micropython了,肯定都有py3,不废话了) centos升级: # yum安装gcc7 $ yum install devtoolset-7-gcc* # 用gcc7自带的脚本添加到环境变量 $ scl enable devtoolset-7 bash # 查看gcc版本 $ gcc -v Ubuntu不用升级gcc,自带最新的 安装boost、cmake、git centos: $ yum install -y boost,cmake,git Ubuntu $ sudo apt-get install -y git,cmake,libboost-all-dev 编译dlib(我们用的人脸识别依赖这个库) # 克隆dlib源代码 $ git clone https://github.com/davisking/dlib.git $ cd dlib $ mkdir build $ cd build #这一部分是使用硬件加速的,如果硬件支持,人脸识别是很快的 $ cmake .. -DDLIB_USE_CUDA=1 -DUSE_AVX_INSTRUCTIONS=1 $ cmake --build .(注意中间有个空格) $ cd .. #python安装dlib $ python3 setup.py install 重点!!!!! 编译dlib库建议空闲内存4G以上,不够可以临时使用swap,不然会编译失败 最后安装face_recognition # 安装 face_recognition $ pip3 install face_recognition 至此,我们的人脸识别环境就搭建好了 可以用一下代码测试是否安装成功 # 准备两个文件夹,一个是参照图片,一个是要识别的图片 $ face_recognition ./file1/ ./file2/ 能识别出就会显示第一个文件夹内的照片的名字。 二、创建人脸识别接口 我们先使用以下代码生成自己的人脸数组 import face_recognition # 打开你的图片文件 img = face_recognition.load_image_file('face.jpg') # 将人脸编码成素组 face_encodings = face_recognition.face_encodings(img) # 打印出的就是你的人脸数组,复制到下面的代码中,也可以保存到数据库(只需要list,不要将整个tuple都复制) print(face_encodings) 使用python 的socket库来监听端口,当文件传入的时候进行识别 from face_recognition import load_image_file, face_encodings, compare_faces import time import json import hashlib import binascii import os import socket import io def detect_faces_in_image(file_stream): # 人脸数组 face_list = [[[-0.09634063, 0.12095481, -0.00436332, -0.07643753, 0.0080383, 0.01902981, -0.07184699, -0.09383309, 0.18518871, -0.09588896, 0.23951106, 0.0986533 , -0.22114635, -0.1363683 , 0.04405268, 0.11574756, -0.19899382, -0.09597053, -0.11969153, -0.12277931, 0.03416885, -0.00267565, 0.09203379, 0.04713435, -0.12731361, -0.35371891, -0.0503444 , -0.17841317, -0.00310897, -0.09844551, -0.06910533, -0.00503746, -0.18466514, -0.09851682, 0.02903969, -0.02174894, 0.02261871, 0.0032102 , 0.20312519, 0.02999607, -0.11646006, 0.09432904, 0.02774341, 0.22102901, 0.26725179, 0.06896867, -0.00490024, -0.09441824, 0.11115381, -0.22592428, 0.06230862, 0.16559327, 0.06232892, 0.03458837, 0.09459756, -0.18777156, 0.00654241, 0.08582542, -0.13578284, 0.0150229 , 0.00670836, -0.08195844, -0.04346499, 0.03347827, 0.20310158, 0.09987706, -0.12370517, -0.06683611, 0.12704916, -0.02160804, 0.00984683, 0.00766284, -0.18980607, -0.19641446, -0.22800779, 0.09010898, 0.39178532, 0.18818057, -0.20875394, 0.03097027, -0.21300618, 0.02532415, 0.07938635, 0.01000703, -0.07719778, -0.12651891, -0.04318593, 0.06219772, 0.09163868, 0.05039065, -0.04922386, 0.21839413, -0.02394437, 0.06173781, 0.0292527 , 0.06160797, -0.15553983, -0.02440624, -0.17509389, -0.0630486 , 0.01428208, -0.03637431, 0.03971229, 0.13983178, -0.23006812, 0.04999552, 0.0108454 , -0.03970895, 0.02501768, 0.08157793, -0.03224047, -0.04502571, 0.0556995 , -0.24374914, 0.25514284, 0.24795187, 0.04060191, 0.17597422, 0.07966681, 0.01920104, -0.01194376, -0.02300822, -0.17204897, -0.0596558 , 0.05307484, 0.07417042, 0.07126575, 0.00209804],'奥巴马']] # 加载上载的图像文件 img = load_image_file(file_stream) # 获取上传图像中任何人脸的人脸编码 unknown_face_encodings = face_encodings(img) is_name = 'unknow' # 图片编码成功 if len(unknown_face_encodings) > 0: # 查看上传图像中的第一张面孔是否与已知面孔匹配 for face_who in face_list: match_results = compare_faces( [face_who[0]], unknown_face_encodings[0]) if match_results[0]: is_name = face_who[1] else: print('图片编码失败') return is_name def socket_conn(conn, addr): print('新连接:{0}'.format(addr)) data = str(conn.recv(1024), 'utf-8') # 认证字符串 if data == 'face0831': conn.send(bytes('认证成功!', 'utf-8')) # 文件信息dic filedic = json.loads(str(conn.recv(1024), 'utf-8')) conn.send(bytes('文件信息接收成功!', 'utf-8')) filename = filedic['filename'] filesize = filedic['filesize'] filesha1 = filedic['sha1'] # hash sha1加密验证(因为micropython没有md5) m = hashlib.sha1() print('文件名称:', filename) print('文件大小:', filesize) print('文件特征码:', filesha1) # 循环接收文件分包 print('正在接收文件...') # 使用io将文件保存在内存中 imgIO = io.BytesIO() while filesize: data = conn.recv(1024) m.update(data) imgIO.write(data) filesize -= len(data) # 比对文件特征码 if str(binascii.hexlify(m.digest()), 'utf-8') == filesha1: print('文件验证成功,正在识别...') conn.send(bytes('正在识别...', 'utf-8')) # 识别图片 face = detect_faces_in_image(imgIO) print('识别结果->', face) conn.send(bytes(face, 'utf-8')) else: print('文件验证失败') conn.send(bytes('文件验证失败', 'utf-8')) else: conn.send(bytes('认证失败!', 'utf-8')) if __name__ == "__main__": # 进入指定文件夹 os.chdir('/root/face/') ADDR = ('0.0.0.0', 10086) # 设置套接字 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定套接字 s.bind(ADDR) # 允许一个连接 s.listen(1) while True: print('等待连接...') # 阻塞连接 conn, addr = s.accept() try: socket_conn(conn, addr) except Exception as e: print(e) finally: print('关闭连接') conn.close() 三、人脸识别和开锁代码 esp32cam 负责拍照和上传到服务器识别,识别成功后将开门指令发送给esp8266开门 为什么要弄两个单片机,不直接用esp32cam开门? 因为我们使用的场景一般esp32cam会放置到室外,如果用esp32cam直接控制电机会有安全问题,所以加了一块8266放置到室内来开门。 esp32cam代码: import urequests,time,socket # 开门函数 def socket_client(): try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(20) s.connect(('esp8266地址', 10086)) data = b'open' s.send(data) while 1: msg1 = str(s.recv(1024),'utf-8') print(msg1 ) if msg1 == 'over': return True # break except socket.error as msg: print(msg) finally: s.close() # 14号引脚中断回调函数 def handle_interrupt(pin): global motion motion = True global interrupt_pin interrupt_pin = pin # 上传函数 def get_face(data): print('打开socket') try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('你的服务器地址', 10086)) # 发送认证字符串,简单认证 s.send(bytes('face0831','utf-8')) print(str(s.recv(1024),'utf-8')) # 使用sha1获得文件特征码,为什么不用md5?因为micropython的hashlib中没有... m = hashlib.sha1() m.update(data) filename = str(int(time.time()))+'.jpg' filesize = len(data) # 文件信息 dic = {"filename": filename, "filesize": filesize,"sha1":str(binascii.hexlify(m.digest()),'utf-8')} fileinfo = bytes(json.dumps(dic),'utf-8') s.send(fileinfo) print(str(s.recv(1024),'utf-8')) # 文件分片 start = 0 num = 1 while filesize: ends = num * 1024 updatafile = data[start:ends] s.send(updatafile) num =num + 1 start = ends filesize -= len(updatafile) print(str(s.recv(1024),'utf-8')) face = str(s.recv(1024),'utf-8') print ('识别结果->',face) return face except Exception as e: print(e) finally: s.close() # 中断后拍照解锁 def do_camera(led): #拍照动作 buf = camera.capture() try: print ("正在识别...") # 调用上传函数 face = get_face(buf) # 判断返回结果 if face == '奥巴马': print ('识别成功->',face) led.value(0) # 开门 suo = jiesuo.socket_client() if suo ==True: print('开门成功!') return True else: print('开门失败!') return False except Exception as e: print(e) #运行 print('开始工作...') print('初始化相机配置...') import camera camera.init(0, format=camera.JPEG) #上翻下翻 camera.flip(0) #左/右 camera.mirror(1) # 框架 camera.framesize(camera.FRAME_SVGA) #特效 camera.speffect(camera.EFFECT_NONE) #白平衡 camera.whitebalance(camera.WB_NONE) #饱和 camera.saturation(0) #亮度 camera.brightness(0) #对比度 camera.contrast(0) #质量 camera.quality(10) print('初始化引脚...') from machine import Pin motion = False # 控制led led = Pin(4, Pin.OUT) # 信号引脚,接按钮或pir红外模块 p14 = Pin(14, Pin.IN) print('正在监控画面移动...') p14.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt) # 识别次数 photoNum = 1 while True: # 14号引脚收到电平信号 if motion: if photoNum ==1: # 打开单片机自带的闪光灯,esp32cam是4号引脚 led.value(1) print('检测到移动:来自引脚', interrupt_pin ,',上传图片...') print('开始第{0}次识别'.format(str(photoNum))) # 拍照并上传 can_camera = do_camera(led) # 识别成功 if can_camera: motion = False photoNum = 1 # 识别不成功重复执行5次,每次间隔2s,防止拍照模糊识别失败,我们用次数来弥补摄像头的不足 else: photoNum = photoNum + 1 if photoNum == 6: print ('识别失败') motion = False # 关闭闪光灯 led.value(0) photoNum = 1 time.sleep(2) |
|
|
|
esp8266代码:
import machine,socket,urequests,time # 重启函数 def do_reset(data): global work if work == False: print('暂无任务运行,正在重启--',data) machine.reset() else: print('正在开门,取消重启') # 操控引脚函数 def de_pin(oc,conn): global pin14 global pin12 global pin4 # 收到开门指令 if oc == 'open': # 开门 pin12.value(0) print('pin12 : 1') # 引脚4的当前电压(当引脚4发生电压变化,则电机开始转动,开始检测动作是否到位) pin4sta = pin4.value() print('pin4:',pin4sta) # 检测电机是否开始转动 while 1: # 不断检测引脚4的电压 ss = pin4.value() print('pin4:',ss) # 引脚4发生电压变化,电机开始转动,停止当前循环 if ss != pin4sta: break time.sleep(0.1) # 检测开门动作是否到位 while 1: time.sleep(0.1) ss = pin4.value() print('pin4:',ss) # 如果触碰到限位器,则动作到位,电机停止转动 if ss == 0: pin12.value(1) print('pin12 : 0') break # 发送开门完成的指令'isopen' conn.send(bytes('isopen','utf8')) # 等待10s,你可以推门了 time.sleep(10) # 开始关门 pin14.value(0) print('pin14 : 1') # 引脚4的当前电压(当引脚4发生电压变化,则电机开始转动,开始检测动作是否到位) pin4sta = pin4.value() print('pin4:',pin4sta) # 检测电机是否开始转动 while 1: # 不断检测引脚4的电压 ss = pin4.value() print('pin4:',ss) # 引脚4发生电压变化,点机开始转动,停止当前循环 if ss != pin4sta: break time.sleep(0.1) # 检测关门动作是否到位 while 1: time.sleep(0.1) ss = pin4.value() print('pin4:',ss) # 如果触碰到限位器,则动作到位,电机停止转动 if ss == 0: time.sleep(0.5) pin14.value(1) print('pin14 : 0') break # 完成后通知esp32cam conn.send(bytes('over','utf8')) # 处理套接字的函数 def deal_data(conn, addr): print('新连接:{0}'.format(addr)) conn.send(bytes('连接成功!','utf8')) # 监听 data = conn.recv(1024) # 收到的数据 data = str(data,'utf-8') print(data) # 如果收到的为'open'指令,那么开门 if data == 'open': # 开门 de_pin(data,conn) # 关闭连接 conn.close() # 设置一个定时器,定时重启,因为8266不稳定,经常连不上 from machine import Timer tim = Timer(-1) # tim.init(period=600000, mode=Timer.ONE_SHOT, callback=do_reset) # 设定工作状态,防止开锁期间系统自动重启 work = False # 定时器,定时重启,30min tim.init(period=1800000, mode=Timer.PERIODIC, callback=do_reset) # 监听的地址和端口,0.0.0.0为所有的来源 ADDR = ('0.0.0.0',10086) # 使用tcp s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定套接字 s.bind(ADDR) s.listen(1) #关门引脚 pin14 = machine.Pin(14,machine.Pin.OUT) #开门引脚 pin12 = machine.Pin(12,machine.Pin.OUT) #初始化,由于我使用的是低电平触发的继电器,常闭口接的是负极,所以要设置为高电平,让电机电路不带电压,防止短路和漏电(我使用的12V 3A的电源手碰到还是会麻的) pin14.value(1) pin12.value(1) # 限位器引脚 内部上拉 防止出现不确定的情况 pin4 = machine.Pin(4,machine.Pin.IN,machine.Pin.PULL_UP) # led灯,用于显示单片机是否正常工作 pin2 = machine.Pin(2,machine.Pin.OUT) while True: print('等待连接...') # led灯,注意,这个led灯是低电平打开,高电平关闭 pin2.value(0) # 开始监听 conn, addr = s.accept() try: # 处理套接字 deal_data(conn, addr) except: pass finally: conn.close() 附:开门的材料和引脚接线 材料 开门采用两路5v低电平继电器、12v减速电机(30转/分,可以拉40斤,卖家说的,不知道真假)、限位器(自制,门把手转动到位后停止电机转动,防止电机或把手损坏,下附图)、12v电源、12v降5v降压板 引脚: esp32cam
控制端 vcc接3.3v(这个要看你引脚的电平,因为esp系列引脚都是3.3v,那么继电器的电源一定要接3.3v,不然无法触发) GND接GND IN接控制引脚12、14 被控端 将12v电源线正极负极分别一分二分成两跟,共四根线接两路继电器。 常开接口(一般是第一个)接12v正,中间接电机,常闭接口(一般第三个)接12v负(两个继电器的接线一定要一样,比如第一个接线口接正,那么另一个继电器的第一个引脚也必须接正,不然无法控制,要不你就自己改代码。另外常闭和常开必须严格按前面说的接正、接负和做好绝缘,否则漏电烧坏设备和不小心短路起火概不负责) 限位器长下面这样子: 上下两个接触片为GND,中间把手上的接触针为gpio4(已经内部上拉,为3.3v)。电机收到开门指令后转动,当针离开下接触片的时候,开始监测gpio4 的电压,此时gpio4为高电平。当接触到上接触片的时候,门锁已打开,gpio4为低电平,电机停转。关门同理。 完成后测试: diy人脸识别开门门锁,使用esp32cam和esp8266单片机 最后: 打算加个扬声器,用于播报识别结果。目前还在啃DAC的文档,先上这些经验为敬~ |
|
|
|
esp8266代码:
import machine,socket,urequests,time # 重启函数 def do_reset(data): global work if work == False: print('暂无任务运行,正在重启--',data) machine.reset() else: print('正在开门,取消重启') # 操控引脚函数 def de_pin(oc,conn): global pin14 global pin12 global pin4 # 收到开门指令 if oc == 'open': # 开门 pin12.value(0) print('pin12 : 1') # 引脚4的当前电压(当引脚4发生电压变化,则电机开始转动,开始检测动作是否到位) pin4sta = pin4.value() print('pin4:',pin4sta) # 检测电机是否开始转动 while 1: # 不断检测引脚4的电压 ss = pin4.value() print('pin4:',ss) # 引脚4发生电压变化,电机开始转动,停止当前循环 if ss != pin4sta: break time.sleep(0.1) # 检测开门动作是否到位 while 1: time.sleep(0.1) ss = pin4.value() print('pin4:',ss) # 如果触碰到限位器,则动作到位,电机停止转动 if ss == 0: pin12.value(1) print('pin12 : 0') break # 发送开门完成的指令'isopen' conn.send(bytes('isopen','utf8')) # 等待10s,你可以推门了 time.sleep(10) # 开始关门 pin14.value(0) print('pin14 : 1') # 引脚4的当前电压(当引脚4发生电压变化,则电机开始转动,开始检测动作是否到位) pin4sta = pin4.value() print('pin4:',pin4sta) # 检测电机是否开始转动 while 1: # 不断检测引脚4的电压 ss = pin4.value() print('pin4:',ss) # 引脚4发生电压变化,点机开始转动,停止当前循环 if ss != pin4sta: break time.sleep(0.1) # 检测关门动作是否到位 while 1: time.sleep(0.1) ss = pin4.value() print('pin4:',ss) # 如果触碰到限位器,则动作到位,电机停止转动 if ss == 0: time.sleep(0.5) pin14.value(1) print('pin14 : 0') break # 完成后通知esp32cam conn.send(bytes('over','utf8')) # 处理套接字的函数 def deal_data(conn, addr): print('新连接:{0}'.format(addr)) conn.send(bytes('连接成功!','utf8')) # 监听 data = conn.recv(1024) # 收到的数据 data = str(data,'utf-8') print(data) # 如果收到的为'open'指令,那么开门 if data == 'open': # 开门 de_pin(data,conn) # 关闭连接 conn.close() # 设置一个定时器,定时重启,因为8266不稳定,经常连不上 from machine import Timer tim = Timer(-1) # tim.init(period=600000, mode=Timer.ONE_SHOT, callback=do_reset) # 设定工作状态,防止开锁期间系统自动重启 work = False # 定时器,定时重启,30min tim.init(period=1800000, mode=Timer.PERIODIC, callback=do_reset) # 监听的地址和端口,0.0.0.0为所有的来源 ADDR = ('0.0.0.0',10086) # 使用tcp s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定套接字 s.bind(ADDR) s.listen(1) #关门引脚 pin14 = machine.Pin(14,machine.Pin.OUT) #开门引脚 pin12 = machine.Pin(12,machine.Pin.OUT) #初始化,由于我使用的是低电平触发的继电器,常闭口接的是负极,所以要设置为高电平,让电机电路不带电压,防止短路和漏电(我使用的12V 3A的电源手碰到还是会麻的) pin14.value(1) pin12.value(1) # 限位器引脚 内部上拉 防止出现不确定的情况 pin4 = machine.Pin(4,machine.Pin.IN,machine.Pin.PULL_UP) # led灯,用于显示单片机是否正常工作 pin2 = machine.Pin(2,machine.Pin.OUT) while True: print('等待连接...') # led灯,注意,这个led灯是低电平打开,高电平关闭 pin2.value(0) # 开始监听 conn, addr = s.accept() try: # 处理套接字 deal_data(conn, addr) except: pass finally: conn.close() 附:开门的材料和引脚接线 材料 开门采用两路5v低电平继电器、12v减速电机(30转/分,可以拉40斤,卖家说的,不知道真假)、限位器(自制,门把手转动到位后停止电机转动,防止电机或把手损坏,下附图)、12v电源、12v降5v降压板 引脚: esp32cam
控制端 vcc接3.3v(这个要看你引脚的电平,因为esp系列引脚都是3.3v,那么继电器的电源一定要接3.3v,不然无法触发) GND接GND IN接控制引脚12、14 被控端 将12v电源线正极负极分别一分二分成两跟,共四根线接两路继电器。 常开接口(一般是第一个)接12v正,中间接电机,常闭接口(一般第三个)接12v负(两个继电器的接线一定要一样,比如第一个接线口接正,那么另一个继电器的第一个引脚也必须接正,不然无法控制,要不你就自己改代码。另外常闭和常开必须严格按前面说的接正、接负和做好绝缘,否则漏电烧坏设备和不小心短路起火概不负责) 限位器长下面这样子: 上下两个接触片为GND,中间把手上的接触针为gpio4(已经内部上拉,为3.3v)。电机收到开门指令后转动,当针离开下接触片的时候,开始监测gpio4 的电压,此时gpio4为高电平。当接触到上接触片的时候,门锁已打开,gpio4为低电平,电机停转。关门同理。 完成后测试: diy人脸识别开门门锁,使用esp32cam和esp8266单片机 最后: 打算加个扬声器,用于播报识别结果。目前还在啃DAC的文档,先上这些经验为敬~ |
|
|
|
只有小组成员才能发言,加入小组>>
imx6ull 和 lan8742 工作起来不正常, ping 老是丢包
3406 浏览 0 评论
3381 浏览 9 评论
3067 浏览 16 评论
3556 浏览 1 评论
9198 浏览 16 评论
1314浏览 3评论
677浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
670浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2421浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1983浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-3-5 05:16 , Processed in 1.032249 second(s), Total 48, Slave 40 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191