学习了一下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中没有成功存储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();
}
}
可以先录入人脸
输入名字,点击ADD USER,我这里用一个图片代替一下。
然后点击ACCESS CONTROL,进行人脸识别
识别成功,会提示DOOR OPEN FOR test。
重启之后,信息不会再丢失。
有人问,这个美女是谁啊,那有点不好意思了,这是个男的。
这个模块的信号可能不太好,距离路由器远了之后,就会很卡,很奇怪的是,我家用了一个路由器进行了信号的增强覆盖,这个模块接到扩展的路由器上,一样也会变的卡顿。
所以我就单独用了一个路由器,将摄像头模块和电脑组成了一个局域网,而且得把模块放在路由器附近,这样就不再卡顿了。
后续考虑是不是改一下用外置天线,看看是不是天线的问题导致的。
这个模块RST按钮旁边有一个板载红色LED。该LED内部连接到GPIO 33。您可以使用此LED指示正在发生的事情。例如,如果连接了Wi-Fi,则LED为红色,反之亦然。
该LED具有反向逻辑,因此您发送了一个 LOW 信号打开它 HIGH 信号将其关闭。
所以可能模拟红灯亮起,表示开锁,同理这个GPIO可以让你用来触发一些别的操作而不仅限于开锁,开灯,开水龙头,想开啥开啥。
大佬的代码,还请去B站观看一下,然后在评论区即可看到下载链接。
我的代码下载
改这个存储用了一天的时间,所以还是用了点小心机。
你们知道人生的意义是什么吗?分享给你们一张图吧
其实我觉得人生有意义,我们的意义,存在于我们对别人的意义。