当前位置:网站首页>单片机开发---基于ESP32-CAM的人脸识别应用
单片机开发---基于ESP32-CAM的人脸识别应用
2022-06-25 09:45:00 【胖哥王老师】
背景
学习了一下OpenCV,熟悉了一点基础概念,就寻找了一下单片机上能否支持人脸识别,用来做一些小玩意。
结果还真发现了一个模块叫ESP32-CAM。ESP32-CAM算得上是一款最便宜的支持人脸识别的单片机开发板了,性能算是单片机里相当不错的了,虽然也只是勉强支持了人脸识别。但是它的优势也是巨大的,就是价格,太便宜了!!
并且在B站上看到了这位大佬的作品:
基于esp32cam人脸识别开锁完整教程&独家教程
于是也来学一下这个模块的使用。顺便问一下
几十块钱的人脸识别门锁,你敢不敢用。
不过可以拿来给孩子做个玩具。
软硬件准备
- 硬件方面
淘宝了一个带底座的ESP32-CAM模块,这个底座主要就是解决了供电和烧录,直接插上就能连接电脑了。
- 软件方面
参照使用手册,搭建了arduino的开发环境,安装了最新的ESP32支持包,可以跑一下官方的范例,能够进行人脸识别。买开发板都会赠送这个教程,这里就不细说了。
不过官方的范例,代码有些混乱,还是大佬开源的这版代码看着逻辑比较清晰。
详解人脸识别门锁
视频中的大佬将他的代码都奉献出来了,在此表达感谢,大家可以去支持一下。
代码可以下载下来,自己进行一下理解。
不过大佬的代码在我这里运行的时候,会报错,fr_flash的错误,这个我还没找到问题原因,可能就是无法将识别出来的人脸数据,存储起来,导致,每次重启都需要重新录入人脸。
先讲一下人脸识别的关键点。
首先是获取视频,使用的是下面的接口
fb = esp_camera_fb_get();
然后将视频转化为RGB888,使用的是下面的接口
fmt2rgb888(fb->buf, fb->len, fb->format, out_res.image);
人脸的操作,分为了检测(查看有没有人脸),录入(将人脸的特征值存储下来),和识别(识别摄像头拍摄到的人脸是不是在库中)。三种操作都需要进行人脸检测。使用到的接口是
out_res.net_boxes = face_detect(image_matrix, &mtmn_config);
然后将人脸对齐
align_face(out_res.net_boxes, image_matrix, aligned_face)
再提取特征值
out_res.face_id = get_face_id(aligned_face);
注意,这个face_id就是最最核心的数据,是你人脸的特征值。有了这个数据,你就可以进行人脸的判断了。
如果是录入,就需要将这个数据进行存储;
如果是识别,就需要将这个数据与已经存储的特征进行对比,符合就说明拍摄到了对的人。
特征对比的核心函数如下
face_id_node *f = recognize_face_with_name(&st_face_list, out_res.face_id);
其中st_face_list就是一个链表,里面的数据就是已经存储好的face_id。
整个代码思路理清之后,就容易修改了。我们来看一下这个face_id的数据是什么结构。
typedef struct tag_face_id_node
{
struct tag_face_id_node *next; /*!< next face id node */
char id_name[ENROLL_NAME_LEN]; /*!< name corresponding to the face id */
dl_matrix3d_t *id_vec; /*!< face id */
} face_id_node;
typedef struct
{
int w; /*!< Width */
int h; /*!< Height */
int c; /*!< Channel */
int n; /*!< Number of filter, input and output must be 1 */
int stride; /*!< Step between lines */
fptp_t *item; /*!< Data */
} dl_matrix3d_t;
可以看出来这个特征值,就是下面那个结构。
我尝试着打印了一个组数据,发现item为一个512长度的float数组。这就是你人脸的特征,应该也算是你的生物指纹之一吧,这么轻松就能获取到了。
flash问题解决
那么就来解决一下flash中没有成功存储face_id的问题吧,
启动的时候,系统调用了这个函数
read_face_id_from_flash_with_name(&st_face_list);
应该就是报错的原因,那么我们不用这个flash存储了,改用数组或者文件直接存储即可。
正好有内存卡可以使用。参考的代码就是SD_MMC这个范例代码。
大概原理就是将用户数据建成目录,每个用户录入一组特征值,即faceID,录入的时候,分别存储到对应目录中。
开机启动的时候,通过读取每个目录,形成核心对比数据,放在st_face_list变量中。
两个核心函数,保存ID
void SD_save_face_id(char* name,int number,dl_matrix3d_t *face_id)
{
int num=0;
char filename[32]={
0};
unsigned char* data=NULL;
num=(face_id->w)*(face_id->h)*(face_id->c)*(face_id->n);
sprintf(filename,"/%s",name);
SD_createDir(SD_MMC,filename);
sprintf(filename,"/%s/1.txt",name);
data=(unsigned char*)face_id->item;
SD_writeFile(SD_MMC, filename, data,num*4);
}
读出ID
void SD_read_all_face_id(fs::FS &fs)
{
st_face_list.count=0;
st_face_list.head=NULL;
st_face_list.tail=NULL;
st_face_list.confirm_times=5;
face_id_node* faceidnode_now=NULL;
File root = fs.open("/");
if(!root)
{
Serial.println("Failed to open directory");
return;
}
if(!root.isDirectory())
{
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while(file)
{
if(file.isDirectory())
{
face_id_node* faceidnode=NULL;
char fullname[64]={
0};
Serial.printf("find user:[%s]\n",file.name());
sprintf(fullname,"%s/1.txt",file.name());
File tryopen = fs.open(fullname);
if(!tryopen)
{
Serial.printf("user has no face id[%s]\n",fullname);
file = root.openNextFile();
continue;
}
else
{
faceidnode = (face_id_node*)dl_lib_calloc(1, sizeof(face_id_node), 0);
if(faceidnode)
{
dl_matrix3d_t* id_vec;
strcpy(faceidnode->id_name,file.name()+1);
id_vec= (dl_matrix3d_t*)dl_lib_calloc(1, sizeof(dl_matrix3d_t), 0);
if(id_vec)
{
float* face=NULL;
faceidnode->id_vec=id_vec;
faceidnode->next=NULL;
id_vec->w=1;
id_vec->h=1;
id_vec->c=512;
id_vec->n=1;
id_vec->stride=512;
face= (float*)dl_lib_calloc(512, sizeof(float), 0);
tryopen.read((unsigned char*)face,512*4);
id_vec->item=face;
if(st_face_list.head==NULL)
{
st_face_list.head=faceidnode;
st_face_list.tail=faceidnode;
faceidnode_now = faceidnode;
}
else
{
st_face_list.tail=faceidnode;
faceidnode_now->next=faceidnode;
faceidnode_now=faceidnode;
}
st_face_list.count++;
}
}
}
}
file = root.openNextFile();
}
}
其他操作函数最终见下载,都放上来就太多了。
来吧 展示
烧录好之后,访问web。
可以先录入人脸
输入名字,点击ADD USER,我这里用一个图片代替一下。
然后点击ACCESS CONTROL,进行人脸识别
识别成功,会提示DOOR OPEN FOR test。
重启之后,信息不会再丢失。
有人问,这个美女是谁啊,那有点不好意思了,这是个男的。
卡顿问题
这个模块的信号可能不太好,距离路由器远了之后,就会很卡,很奇怪的是,我家用了一个路由器进行了信号的增强覆盖,这个模块接到扩展的路由器上,一样也会变的卡顿。
所以我就单独用了一个路由器,将摄像头模块和电脑组成了一个局域网,而且得把模块放在路由器附近,这样就不再卡顿了。
后续考虑是不是改一下用外置天线,看看是不是天线的问题导致的。
用灯代替门锁
这个模块RST按钮旁边有一个板载红色LED。该LED内部连接到GPIO 33。您可以使用此LED指示正在发生的事情。例如,如果连接了Wi-Fi,则LED为红色,反之亦然。
该LED具有反向逻辑,因此您发送了一个 LOW 信号打开它 HIGH 信号将其关闭。
所以可能模拟红灯亮起,表示开锁,同理这个GPIO可以让你用来触发一些别的操作而不仅限于开锁,开灯,开水龙头,想开啥开啥。
代码下载
大佬的代码,还请去B站观看一下,然后在评论区即可看到下载链接。
我的代码下载
改这个存储用了一天的时间,所以还是用了点小心机。
结束语
你们知道人生的意义是什么吗?分享给你们一张图吧
其实我觉得人生有意义,我们的意义,存在于我们对别人的意义。
边栏推荐
- Principle of distribution: understanding the gossip protocol
- How much does a small program cost? How much does a small program cost? It's clear at a glance
- Kotlin Foundation
- MySQL创建给出语句
- Is GF Securities reliable? Is it legal? Is it safe to open a stock account?
- 【论文阅读|深读】DRNE:Deep Recursive Network Embedding with Regular Equivalence
- WPF Prism框架
- Webapi performance optimization
- Redis(二)分布式锁与Redis集群搭建
- Puzzle (019.2) hexagonal lock
猜你喜欢
How to build a wechat applet? How to open an applet?
Houdini图文笔记:Could not create OpenCL device of type (HOUDINI_OCL_DEVICETYPE)问题的解决
Redis (II) distributed locks and redis cluster construction
I hope to explain the basics of canvas as clearly as possible according to my ideas
独步武林,架构选型手册(包含 PDF)
Jetpack compose layout (III) - custom layout
How to make small programs on wechat? How to make small programs on wechat
Mqtt beginner level chapter
Linked list delete nodes in the linked list
Jetpack compose layout (I) - basic knowledge of layout
随机推荐
使用EVO
The way that flutter makes the keyboard disappear (forwarding from the dependent window)
Android database security: after the user exits, the transaction rollback log still stores relevant data information
Experience in writing C
Jetpack compose layout (IV) - constraintlayout
Notes on writing questions in C language -- monkeys eat peaches
Best producer consumer code
Shuttle JSON, list, map inter transfer
Webapi performance optimization
Mongodb's principle, basic use, clustering and partitioned clustering
Kotlin Foundation
8. Intelligent transportation project (1)
Deep understanding of JVM - JVM memory model
‘Flutter/Flutter. h‘ file not found
独步武林,架构选型手册(包含 PDF)
i++ 和 ++i的真正区别
申请多域名SSL证书的要求及注意事项
What are the PMP scores?
Byte interview: under what scenario will syn packets be discarded?
Is GF Securities reliable? Is it legal? Is it safe to open a stock account?