• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

单片机开发---ESP32-CAM的人脸识别应用

武飞扬头像
胖哥王老师
帮助1

背景

学习了一下OpenCV,熟悉了一点基础概念,就寻找了一下单片机上能否支持人脸识别,用来做一些小玩意。
结果还真发现了一个模块叫ESP32-CAM。ESP32-CAM算得上是一款最便宜的支持人脸识别的单片机开发板了,性能算是单片机里相当不错的了,虽然也只是勉强支持了人脸识别。但是它的优势也是巨大的,就是价格,太便宜了!!
学新通

并且在B站上看到了这位大佬的作品:

基于esp32cam人脸识别开锁完整教程&独家教程

于是也来学一下这个模块的使用。顺便问一下
几十块钱的人脸识别门锁,你敢不敢用。
学新通
不过可以拿来给孩子做个玩具。

软硬件准备

  • 硬件方面

淘宝了一个带底座的ESP32-CAM模块,这个底座主要就是解决了供电和烧录,直接插上就能连接电脑了。

  • 软件方面

不过官方的范例,代码有些混乱,还是大佬开源的这版代码看着逻辑比较清晰。
学新通

详解人脸识别门锁

视频中的大佬将他的代码都奉献出来了,在此表达感谢,大家可以去支持一下。
学新通
代码可以下载下来,自己进行一下理解。

不过大佬的代码在我这里运行的时候,会报错,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站观看一下,然后在评论区即可看到下载链接。
我的代码下载
改这个存储用了一天的时间,所以还是用了点小心机。
学新通

结束语

你们知道人生的意义是什么吗?分享给你们一张图吧
学新通
其实我觉得人生有意义,我们的意义,存在于我们对别人的意义。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgefhif
系列文章
更多 icon
同类精品
更多 icon
继续加载