超级版主
 
- 积分
- 5157
- 金钱
- 5157
- 注册时间
- 2019-5-8
- 在线时间
- 1286 小时
|
|
第五十九章 人脸识别实验
1)实验平台:正点原子DNESP32S3开发板
2)章节摘自【正点原子】ESP32-S3使用指南—IDF版 V1.6
3)购买链接:https://detail.tmall.com/item.htm?&id=768499342659
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32S3.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
人脸识别是一种基于人的脸部特征信息进行身份识别的一种生物识别技术。它使用摄像机或摄像头采集含有人脸的图像或视频流,并自动在图像中检测和跟踪人脸,进而对检测到的人脸进行脸部识别的一系列相关技术。本章,我们使用乐鑫AI库来实现人脸识别功能。
本章分为如下几个部分:
59.1 硬件设计
59.2 软件设计
59.3 下载验证
59.1 硬件设计
1. 例程功能
本章实验功能简介:使用乐鑫官方的ESP32-WHO AI库对OV2640和OV5640摄像头输出的数据进行人脸识别。当长按BOOT按键时,录入当前对焦的人脸;当单击BOOT按键时,识别当前人脸,识别时需和人脸仓库中的人脸匹配;当双击BOOT按键时,删除当前人脸,但前提是这个张人脸之前已经在人脸仓库当中。
2. 硬件资源
1)LED灯
LED-IO1
2)XL9555
IIC_INT-IO0(需在P5连接IO0)
IIC_SDA-IO41
IIC_SCL-IO42
3)SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
4)CAMERA
OV_SCL-IO38
OV_SDA- IO39
VSYNC- IO47
HREF- IO48
PCLK- IO45
D0- IO4
D1- IO5
D2- IO6
D3- IO7
D4- IO15
D5- IO16
D6- IO17
D7- IO18
RESET-IO0_5(XL9555)
PWDN-IO0_4(XL9555)
3. 原理图
本章实验使用的KPU为ESP32-S3的内部资源,因此并没有相应的连接原理图。
59.2 软件设计
59.2.1 程序流程图
程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图:
图59.2.1.1 程序流程图
59.2.2 程序解析
在本章节中,我们将重点关注两个文件:esp_face_recognition.cpp和esp_face_ recognition.hpp。其中,esp_face_recognition.hpp主要声明了esp_face_recognition函数,其内容相对简单,因此我们暂时不作详细解释。本章节的核心关注点是esp_face_recognition.cpp文件中的函数。
接下来,我们将详细解析esp_face_ recognition_ai_strat函数的工作原理。
- <font size="3">/**</font>
- <font size="3"> * @brief AI图像数据开启</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] 无</font>
- <font size="3"> * @retval 1:创建失败;0:创建成功</font>
- <font size="3"> */</font>
- <font size="3">uint8_t esp_face_recognition_ai_strat(void)</font>
- <font size="3">{</font>
- <font size="3"> /* 创建队列及任务 */</font>
- <font size="3"> xQueueFrameO = xQueueCreate(5, sizeof(camera_fb_t *));</font>
- <font size="3"> xQueueAIFrameO = xQueueCreate(5, sizeof(camera_fb_t *));</font>
- <font size="3"> xQueueKeyState = xQueueCreate(1, sizeof(int *));</font>
- <font size="3"> xQueueEventLogic = xQueueCreate(1, sizeof(int *));</font>
- <font size="3"> xMutex = xSemaphoreCreateMutex();</font>
- <font size="3"> /* 初始化按键 */</font>
- <font size="3"> esp_key_init(GPIO_NUM_0);</font>
- <font size="3"> /* 创建任务 */</font>
- <font size="3"> xTaskCreatePinnedToCore(esp_key_trigger,"esp_key_scan",1024,NULL,5, NULL,0);</font>
- <font size="3">xTaskCreatePinnedToCore(esp_event_generate, "event_logic",1024,NULL,</font>
- <font size="3">5, NULL,0);</font>
- <font size="3">xTaskCreatePinnedToCore(esp_camera_process_handler, "esp_camera_process ", </font>
- <font size="3">4 * 1024, NULL, 5, &camera_task_handle, 1);</font>
- <font size="3">xTaskCreatePinnedToCore(esp_ai_process_handler, "esp_ai_process_handler", </font>
- <font size="3">6 * 1024, NULL, 5, &ai_task_handle, 1);</font>
- <font size="3">xTaskCreatePinnedToCore(esp_task_event_handler, "esp_task_event_handler", </font>
- <font size="3">4 * 1024, NULL, 5, NULL, 1);</font>
- <font size="3"> </font>
- <font size="3"> if (xQueueFrameO != NULL </font>
- <font size="3"> || xQueueAIFrameO != NULL </font>
- <font size="3"> || xQueueEventLogic != NULL</font>
- <font size="3"> || camera_task_handle != NULL </font>
- <font size="3"> || ai_task_handle != NULL)</font>
- <font size="3"> {</font>
- <font size="3"> return 0;</font>
- <font size="3"> }</font>
- <font size="3">return 1;</font>
- <font size="3">}</font>
复制代码 在上述函数中,我们创建了四个消息队列以递交消息,并设置了一个互斥锁来防止任务优先级翻转。此外,还定义了五个任务:检测KEY按键状态(用于判断按键处于哪种状态,如长按、点击和双击等)、事件任务、摄像头获取任务、AI处理任务和按键扫描任务任务。
下面作者分为介绍这些任务的实现流程,如下:
1,检测KEY按键状态任务
- <font size="3">/**</font>
- <font size="3"> * @brief 按键扫描(判断短按、长按及双击状态)</font>
- <font size="3"> * @param ticks_to_wait:等待时间</font>
- <font size="3"> * @retval 返回按键状态</font>
- <font size="3"> */</font>
- <font size="3">int esp_key_scan(TickType_t ticks_to_wait)</font>
- <font size="3">{</font>
- <font size="3"> gpio_num_t io_num;</font>
- <font size="3"> BaseType_t press_key = pdFALSE;</font>
- <font size="3"> BaseType_t lift_key = pdFALSE;</font>
- <font size="3"> int64_t backup_time = 0;</font>
- <font size="3"> int64_t interval_time = 0;</font>
- <font size="3"> static int64_t last_time = 0;</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> xQueueReceive(gpio_evt_queue, &io_num, ticks_to_wait);</font>
- <font size="3"> if (gpio_get_level(io_num) == 0)</font>
- <font size="3"> {</font>
- <font size="3"> press_key = pdTRUE;</font>
- <font size="3"> backup_time = esp_timer_get_time();</font>
- <font size="3"> interval_time = backup_time - last_time;</font>
- <font size="3"> }</font>
- <font size="3"> else if (press_key)</font>
- <font size="3"> {</font>
- <font size="3"> lift_key = pdTRUE;</font>
- <font size="3"> last_time = esp_timer_get_time();</font>
- <font size="3"> backup_time = last_time - backup_time;</font>
- <font size="3"> }</font>
- <font size="3"> if (press_key & lift_key)</font>
- <font size="3"> {</font>
- <font size="3"> press_key = pdFALSE;</font>
- <font size="3"> lift_key = pdFALSE;</font>
- <font size="3"> if (backup_time > LONG_PRESS_THRESH)</font>
- <font size="3"> {</font>
- <font size="3"> return KEY_LONG_PRESS;</font>
- <font size="3"> }</font>
- <font size="3"> else</font>
- <font size="3"> {</font>
- <font size="3"> if ((interval_time < DOUBLE_CLICK_THRESH)&&(interval_time > 0))</font>
- <font size="3"> return KEY_DOUBLE_CLICK;</font>
- <font size="3"> else</font>
- <font size="3"> return KEY_SHORT_PRESS;</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3">}</font>
- <font size="3">/**</font>
- <font size="3"> * @brief 按键任务</font>
- <font size="3"> * @param arg:未使用</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">static void esp_key_trigger(void *arg)</font>
- <font size="3">{</font>
- <font size="3"> arg = arg;</font>
- <font size="3"> int ret = 0;</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> ret = esp_key_scan(portMAX_DELAY);</font>
- <font size="3"> xQueueOverwrite(xQueueKeyState, &ret);</font>
- <font size="3"> }</font>
- <font size="3"> vTaskDelete(NULL);</font>
- <font size="3">}</font>
复制代码 在上述任务函数中,首先通过调用esp_key_scan函数获取按键的状态,包括长按、双击或点击等。然后,使用FreeRTOS API函数xQueueOverwrite将消息发送到事件任务中。
2,事件任务
- <font size="3">/**</font>
- <font size="3"> * @brief 事件生成任务</font>
- <font size="3"> * @param arg:未使用</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void esp_event_generate(void *arg)</font>
- <font size="3">{</font>
- <font size="3"> arg = arg;</font>
- <font size="3"> static key_state_t key_state;</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> /* 接收状态 */</font>
- <font size="3"> xQueueReceive(xQueueKeyState, &key_state, portMAX_DELAY);</font>
- <font size="3"> /* 判断状态 */</font>
- <font size="3"> switch (key_state)</font>
- <font size="3"> {</font>
- <font size="3"> case KEY_SHORT_PRESS: /* 短按状态 */</font>
- <font size="3"> recognizer_state = RECOGNIZE;</font>
- <font size="3"> break;</font>
- <font size="3"> case KEY_LONG_PRESS: /* 长按状态 */</font>
- <font size="3"> recognizer_state = ENROLL;</font>
- <font size="3"> break;</font>
- <font size="3"> case KEY_DOUBLE_CLICK: /* 双击状态 */</font>
- <font size="3"> recognizer_state = DELETE;</font>
- <font size="3"> break;</font>
- <font size="3"> default:</font>
- <font size="3"> recognizer_state = DETECT;</font>
- <font size="3"> break;</font>
- <font size="3"> }</font>
- <font size="3"> /* 发送状态 */</font>
- <font size="3"> xQueueSend(xQueueEventLogic, &recognizer_state, portMAX_DELAY);</font>
- <font size="3">}</font>
- <font size="3">}</font>
复制代码 上述任务函数调用xQueueReceive函数接收KEY的消息,然后根据消息判断处于哪个事件状态,最后调用xQueueSend函数发送事件消息至按键扫描任务处理。
3,按键扫描任务
- <font size="3">/**</font>
- <font size="3"> * @brief 按键扫描任务</font>
- <font size="3"> * @param arg:未使用</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">static void esp_task_event_handler(void *arg)</font>
- <font size="3">{</font>
- <font size="3"> arg = arg;</font>
- <font size="3"> recognizer_state_t _gEvent;</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> xQueueReceive(xQueueEventLogic, &(_gEvent), portMAX_DELAY);</font>
- <font size="3"> xSemaphoreTake(xMutex, portMAX_DELAY);</font>
- <font size="3"> gEvent = _gEvent;</font>
- <font size="3"> xSemaphoreGive(xMutex);</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 该任务函数就是为了防止优先级翻转问题。
4,摄像头任务
- <font size="3">/**</font>
- <font size="3"> * @brief 摄像头图像数据获取任务</font>
- <font size="3"> * @param arg:未使用</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">static void esp_camera_process_handler(void *arg)</font>
- <font size="3">{</font>
- <font size="3"> arg = arg;</font>
- <font size="3"> camera_fb_t *camera_frame = NULL;</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> /* 获取摄像头图像 */</font>
- <font size="3"> camera_frame = esp_camera_fb_get();</font>
- <font size="3"> if (camera_frame)</font>
- <font size="3"> {</font>
- <font size="3"> /* 以队列的形式发送 */</font>
- <font size="3"> xQueueSend(xQueueFrameO, &camera_frame, portMAX_DELAY);</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 该任务函数最主要的作用是获取摄像头的图像数据,并发送图像数据至AI处理任务。
5,AI处理任务函数
- <font size="3">/**</font>
- <font size="3"> * @brief 摄像头图像数据传入AI处理任务</font>
- <font size="3"> * @param arg:未使用</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">static void esp_ai_process_handler(void *arg)</font>
- <font size="3">{</font>
- <font size="3"> arg = arg;</font>
- <font size="3"> camera_fb_t *frame = NULL;</font>
- <font size="3"> HumanFaceDetectMSR01 detector(0.3F, 0.3F, 10, 0.3F);</font>
- <font size="3"> HumanFaceDetectMNP01 detector2(0.4F, 0.3F, 10);</font>
- <font size="3"> FaceRecognition112V1S16 *recognizer = new FaceRecognition112V1S16();</font>
- <font size="3"> show_state_t frame_show_state = SHOW_STATE_IDLE;</font>
- <font size="3"> recognizer_state_t _gEvent;</font>
- <font size="3">recognizer->set_partition(ESP_PARTITION_TYPE_DATA, </font>
- <font size="3">ESP_PARTITION_SUBTYPE_ANY, "fr");</font>
- <font size="3"> recognizer->set_ids_from_flash();</font>
- <font size="3"> while(1)</font>
- <font size="3"> {</font>
- <font size="3"> xSemaphoreTake(xMutex, portMAX_DELAY);</font>
- <font size="3"> _gEvent = gEvent;</font>
- <font size="3"> gEvent = DETECT;</font>
- <font size="3"> xSemaphoreGive(xMutex);</font>
- <font size="3"> if (_gEvent)</font>
- <font size="3"> {</font>
- <font size="3"> bool is_detected = false;</font>
- <font size="3"> if (xQueueReceive(xQueueFrameO, &frame, portMAX_DELAY))</font>
- <font size="3"> {</font>
- <font size="3"> std::list<dl::detect::result_t> &detect_candidates = </font>
- <font size="3">detector.infer((uint16_t *)frame->buf, {(int)frame->height, </font>
- <font size="3">(int)frame->width, 3});</font>
- <font size="3"> std::list<dl::detect::result_t> &detect_results = </font>
- <font size="3">detector2.infer((uint16_t *)frame->buf, </font>
- <font size="3">{(int)frame->height, </font>
- <font size="3">(int)frame->width, 3}, </font>
- <font size="3">detect_candidates);</font>
- <font size="3"> if (detect_results.size() == 1)</font>
- <font size="3"> is_detected = true;</font>
- <font size="3"> if (is_detected)</font>
- <font size="3"> {</font>
- <font size="3"> switch (_gEvent)</font>
- <font size="3"> {</font>
- <font size="3"> /* 注册 */</font>
- <font size="3"> case ENROLL:</font>
- <font size="3"> recognizer->enroll_id((uint16_t *)frame->buf, </font>
- <font size="3">{(int)frame->height, </font>
- <font size="3">(int)frame->width, 3}, </font>
- <font size="3">detect_results.front().keypoint, </font>
- <font size="3">"", true);</font>
- <font size="3"> ESP_LOGW("ENROLL", "ID %d is enrolled", </font>
- <font size="3">recognizer->get_enrolled_ids().back().id);</font>
- <font size="3"> frame_show_state = SHOW_STATE_ENROLL;</font>
- <font size="3"> break;</font>
- <font size="3"> /* 识别 */</font>
- <font size="3"> case RECOGNIZE:</font>
- <font size="3"> recognize_result = recognizer->recognize(</font>
- <font size="3">(uint16_t *)frame->buf, </font>
- <font size="3">{(int)frame->height, </font>
- <font size="3">(int)frame->width, 3}, </font>
- <font size="3">detect_results.front().keypoint);</font>
- <font size="3"> print_detection_result(detect_results);</font>
- <font size="3"> if (recognize_result.id > 0)</font>
- <font size="3"> ESP_LOGI("RECOGNIZE", "Similarity: %f, Match ID: %d", </font>
- <font size="3">recognize_result.similarity, recognize_result.id);</font>
- <font size="3"> else</font>
- <font size="3"> ESP_LOGE("RECOGNIZE", "Similarity: %f, Match ID: %d", </font>
- <font size="3">recognize_result.similarity, recognize_result.id);</font>
- <font size="3"> frame_show_state = SHOW_STATE_RECOGNIZE;</font>
- <font size="3"> break;</font>
- <font size="3"> /* 删除 */</font>
- <font size="3"> case DELETE:</font>
- <font size="3"> vTaskDelay(10);</font>
- <font size="3"> recognizer->delete_id(true);</font>
- <font size="3"> ESP_LOGE("DELETE", "% d IDs left", </font>
- <font size="3">recognizer->get_enrolled_id_num());</font>
- <font size="3"> frame_show_state = SHOW_STATE_DELETE;</font>
- <font size="3"> break;</font>
- <font size="3"> default:</font>
- <font size="3"> break;</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> if (frame_show_state != SHOW_STATE_IDLE)</font>
- <font size="3"> {</font>
- <font size="3"> static int frame_count = 0;</font>
- <font size="3"> switch (frame_show_state)</font>
- <font size="3"> {</font>
- <font size="3"> /* 删除图像 */</font>
- <font size="3"> case SHOW_STATE_DELETE:</font>
- <font size="3"> esp_rgb_printf(frame, RGB565_MASK_RED, </font>
- <font size="3">"%d IDs left", </font>
- <font size="3">recognizer->get_enrolled_id_num());</font>
- <font size="3"> break;</font>
- <font size="3"> /* 图像识别 */</font>
- <font size="3"> case SHOW_STATE_RECOGNIZE:</font>
- <font size="3"> if (recognize_result.id > 0)</font>
- <font size="3"> esp_rgb_printf(frame, RGB565_MASK_GREEN,</font>
- <font size="3"> "ID %d", </font>
- <font size="3">recognize_result.id);</font>
- <font size="3"> else</font>
- <font size="3"> esp_rgb_print(frame, RGB565_MASK_RED, "who ?");</font>
- <font size="3"> break;</font>
- <font size="3"> /* 图像注册 */</font>
- <font size="3"> case SHOW_STATE_ENROLL:</font>
- <font size="3"> esp_rgb_printf(frame, RGB565_MASK_BLUE, </font>
- <font size="3">"Enroll: ID %d", </font>
- <font size="3">recognizer->get_enrolled_ids().back().id);</font>
- <font size="3"> break;</font>
- <font size="3"> default:</font>
- <font size="3"> break;</font>
- <font size="3"> }</font>
- <font size="3"> if (++frame_count > FRAME_DELAY_NUM)</font>
- <font size="3"> {</font>
- <font size="3"> frame_count = 0;</font>
- <font size="3"> frame_show_state = SHOW_STATE_IDLE;</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> if (detect_results.size())</font>
- <font size="3"> {</font>
- <font size="3"> draw_detection_result((uint16_t *)frame->buf, </font>
- <font size="3">frame->height, frame->width, detect_results);</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> if (xQueueAIFrameO)</font>
- <font size="3"> {</font>
- <font size="3"> xQueueSend(xQueueAIFrameO, &frame, portMAX_DELAY);</font>
- <font size="3"> }</font>
- <font size="3"> else if (gReturnFB)</font>
- <font size="3"> {</font>
- <font size="3"> esp_camera_fb_return(frame);</font>
- <font size="3"> }</font>
- <font size="3"> else</font>
- <font size="3"> {</font>
- <font size="3"> free(frame);</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 上述任务函数主要负责处理图像数据,并将其提交给AI库进行处理。根据按键的不同状态,系统会执行不同的操作:
1,如果长按按键,系统会将人脸数据传入分区表。
2,如果点击按键,系统将当前识别的人脸与人脸库中的人脸进行对比。如果匹配成功,系统将提示人脸识别成功的信息。
3,如果双击按键,系统同样会将当前识别的人脸与人脸库中的人脸进行对比。如果匹配成功,系统不仅会提示人脸识别成功的信息,还会从人脸库中删除匹配的人脸图像数据。
59.3 下载验证
如果在检测过程中发现人脸,需要长按BOOT按键,将人脸信息录入人脸存储区。当再次检测到人脸时,只需短按BOOT按键,系统就会识别当前的人脸(与存储区的人脸数据进行匹配)。如果识别成功,该系统会将人脸识别处理的图像数据显示在LCD上,否则,提示人脸识别失败,如下图所示。
图59.3.1 人脸识别效果图 |
|