Add Face Recognition functions and example
1. modify esp-face as a submodule 2. add documentation 3. improve detection performance
|
@ -1,5 +1,6 @@
|
|||
EXTRA_COMPONENT_DIRS += $(SOLUTION_PATH)/components
|
||||
EXTRA_COMPONENT_DIRS += $(SOLUTION_PATH)/components/esp-face/lib
|
||||
EXTRA_COMPONENT_DIRS += $(SOLUTION_PATH)/components/esp-face/image_util
|
||||
EXTRA_COMPONENT_DIRS += $(SOLUTION_PATH)/components/esp-face/face_detection/fd_coefficients
|
||||
EXTRA_COMPONENT_DIRS += $(SOLUTION_PATH)/components/esp-face/face_detection/mtmn
|
||||
EXTRA_COMPONENT_DIRS += $(SOLUTION_PATH)/components/esp-face/face_recognition/fr_coefficients
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 84acf59b23afafafc5c72f2e31dddbb54d56aaa1
|
||||
Subproject commit 4ed00b5f61bb59b54ff56fba14597eed87c7b36a
|
|
@ -1,404 +0,0 @@
|
|||
/*
|
||||
* ESPRESSIF MIT License
|
||||
*
|
||||
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
|
||||
*
|
||||
* Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case,
|
||||
* it is free of charge, to any person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "dl_lib.h"
|
||||
#include "image_util.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
void image_resize_linear(uint8_t *dst_image, uint8_t *src_image, int dst_w, int dst_h, int dst_c, int src_w, int src_h)
|
||||
{ /*{{{*/
|
||||
float scale_x = (float)src_w / dst_w;
|
||||
float scale_y = (float)src_h / dst_h;
|
||||
|
||||
int dst_stride = dst_c * dst_w;
|
||||
int src_stride = dst_c * src_w;
|
||||
for (int y = 0; y < dst_h; y++)
|
||||
{
|
||||
float fy[2];
|
||||
fy[0] = (float)((y + 0.5) * scale_y - 0.5); // y
|
||||
int src_y = (int)fy[0]; // y1
|
||||
fy[0] -= src_y; // y - y1
|
||||
fy[1] = 1 - fy[0]; // y2 - y
|
||||
src_y = DL_IMAGE_MAX(0, src_y);
|
||||
src_y = DL_IMAGE_MIN(src_y, src_h - 2);
|
||||
|
||||
for (int x = 0; x < dst_w; x++)
|
||||
{
|
||||
float fx[2];
|
||||
fx[0] = (float)((x + 0.5) * scale_x - 0.5); // x
|
||||
int src_x = (int)fx[0]; // x1
|
||||
fx[0] -= src_x; // x - x1
|
||||
if (src_x < 0)
|
||||
{
|
||||
fx[0] = 0;
|
||||
src_x = 0;
|
||||
}
|
||||
if (src_x > src_w - 2)
|
||||
{
|
||||
fx[0] = 0;
|
||||
src_x = src_w - 2;
|
||||
}
|
||||
fx[1] = 1 - fx[0]; // x2 - x
|
||||
|
||||
for (int c = 0; c < dst_c; c++)
|
||||
{
|
||||
dst_image[y * dst_stride + x * dst_c + c] = round(src_image[src_y * src_stride + src_x * dst_c + c] * fx[1] * fy[1] + src_image[src_y * src_stride + (src_x + 1) * dst_c + c] * fx[0] * fy[1] + src_image[(src_y + 1) * src_stride + src_x * dst_c + c] * fx[1] * fy[0] + src_image[(src_y + 1) * src_stride + (src_x + 1) * dst_c + c] * fx[0] * fy[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} /*}}}*/
|
||||
|
||||
void image_cropper(dl_matrix3du_t *corp_image, dl_matrix3du_t *src_image, float rotate_angle, float ratio, float *center)
|
||||
{
|
||||
int rot_w = (int)corp_image->w;
|
||||
int rot_h = (int)corp_image->h;
|
||||
int rot_c = src_image->c;
|
||||
int rot_stride = rot_w * rot_c;
|
||||
uint8_t *rot_data = corp_image->item;
|
||||
float rot_w_start = 0.5f - (float)rot_w / 2;
|
||||
float rot_h_start = 0.5f - (float)rot_h / 2;
|
||||
|
||||
//rotate_angle must be radius
|
||||
float si = sin(rotate_angle);
|
||||
float co = cos(rotate_angle);
|
||||
|
||||
uint8_t *src_data = src_image->item;
|
||||
int src_w = src_image->w;
|
||||
int src_h = src_image->h;
|
||||
int src_c = src_image->c;
|
||||
int src_stride = src_image->stride;
|
||||
|
||||
for (int y = 0; y < rot_h; y++)
|
||||
{
|
||||
for (int x = 0; x < rot_w; x++)
|
||||
{
|
||||
float xs, ys, xr, yr;
|
||||
xs = ratio * (rot_w_start + x);
|
||||
ys = ratio * (rot_h_start + y);
|
||||
|
||||
xr = xs * co + ys * si;
|
||||
yr = -xs * si + ys * co;
|
||||
|
||||
float fy[2];
|
||||
fy[0] = center[1] + yr; // y
|
||||
int src_y = (int)fy[0]; // y1
|
||||
fy[0] -= src_y; // y - y1
|
||||
fy[1] = 1 - fy[0]; // y2 - y
|
||||
src_y = DL_IMAGE_MAX(0, src_y);
|
||||
src_y = DL_IMAGE_MIN(src_y, src_h - 2);
|
||||
|
||||
float fx[2];
|
||||
fx[0] = center[0] + xr; // x
|
||||
int src_x = (int)fx[0]; // x1
|
||||
fx[0] -= src_x; // x - x1
|
||||
if (src_x < 0)
|
||||
{
|
||||
fx[0] = 0;
|
||||
src_x = 0;
|
||||
}
|
||||
if (src_x > src_w - 2)
|
||||
{
|
||||
fx[0] = 0;
|
||||
src_x = src_w - 2;
|
||||
}
|
||||
fx[1] = 1 - fx[0]; // x2 - x
|
||||
|
||||
for (int c = 0; c < rot_c; c++)
|
||||
{
|
||||
rot_data[y * rot_stride + x * rot_c + c] = round(src_data[src_y * src_stride + src_x * src_c + c] * fx[1] * fy[1] + src_data[src_y * src_stride + (src_x + 1) * src_c + c] * fx[0] * fy[1] + src_data[(src_y + 1) * src_stride + src_x * src_c + c] * fx[1] * fy[0] + src_data[(src_y + 1) * src_stride + (src_x + 1) * src_c + c] * fx[0] * fy[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void image_sort_insert_by_score(image_list_t *image_sorted_list, const image_list_t *insert_list)
|
||||
{ /*{{{*/
|
||||
if (insert_list == NULL || insert_list->head == NULL)
|
||||
return;
|
||||
image_box_t *box = insert_list->head;
|
||||
if (NULL == image_sorted_list->head)
|
||||
{
|
||||
image_sorted_list->head = insert_list->head;
|
||||
box = insert_list->head->next;
|
||||
image_sorted_list->head->next = NULL;
|
||||
}
|
||||
image_box_t *head = image_sorted_list->head;
|
||||
|
||||
while (box)
|
||||
{
|
||||
// insert in head
|
||||
if (box->score > head->score)
|
||||
{
|
||||
image_box_t *tmp = box;
|
||||
box = box->next;
|
||||
tmp->next = head;
|
||||
head = tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
image_box_t *curr = head->next;
|
||||
image_box_t *prev = head;
|
||||
while (curr)
|
||||
{
|
||||
if (box->score > curr->score)
|
||||
{
|
||||
image_box_t *tmp = box;
|
||||
box = box->next;
|
||||
tmp->next = curr;
|
||||
prev->next = tmp;
|
||||
break;
|
||||
}
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
// insert in tail
|
||||
if (NULL == curr)
|
||||
{
|
||||
image_box_t *tmp = box;
|
||||
box = box->next;
|
||||
tmp->next = NULL;
|
||||
prev->next = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
image_sorted_list->head = head;
|
||||
image_sorted_list->len += insert_list->len;
|
||||
} /*}}}*/
|
||||
|
||||
image_list_t *image_get_valid_boxes(fptp_t *score, fptp_t *offset, int width, int height, fptp_t score_threshold, fptp_t scale)
|
||||
{ /*{{{*/
|
||||
typedef struct
|
||||
{
|
||||
short valid_x;
|
||||
short valid_y;
|
||||
int valid_idx;
|
||||
} valid_index_t;
|
||||
valid_index_t *valid_indexes = (valid_index_t *)calloc(width * height, sizeof(valid_index_t));
|
||||
int valid_count = 0;
|
||||
int index = 0;
|
||||
for (int y = 0; y < height; y++)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
if (score[2 * index + 1] > score_threshold)
|
||||
{
|
||||
valid_indexes[valid_count].valid_x = x;
|
||||
valid_indexes[valid_count].valid_y = y;
|
||||
valid_indexes[valid_count].valid_idx = index;
|
||||
valid_count++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if (0 == valid_count)
|
||||
{
|
||||
free(valid_indexes);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
image_box_t *valid_box = (image_box_t *)calloc(valid_count, sizeof(image_box_t));
|
||||
image_list_t *valid_list = (image_list_t *)calloc(1, sizeof(image_list_t));
|
||||
valid_list->head = valid_box;
|
||||
valid_list->origin_head = valid_box;
|
||||
valid_list->len = valid_count;
|
||||
|
||||
for (int i = 0; i < valid_count; i++)
|
||||
{
|
||||
fptp_t x1 = valid_indexes[i].valid_x * scale * 2.0;
|
||||
fptp_t y1 = valid_indexes[i].valid_y * scale * 2.0;
|
||||
int valid_i = valid_indexes[i].valid_idx;
|
||||
valid_box[i].score = score[2 * valid_i + 1];
|
||||
valid_box[i].box.box_p[0] = x1;
|
||||
valid_box[i].box.box_p[1] = y1;
|
||||
valid_box[i].box.box_p[2] = x1 + MIN_FACE * scale;
|
||||
valid_box[i].box.box_p[3] = y1 + MIN_FACE * scale;
|
||||
valid_box[i].offset.box_p[0] = offset[valid_i * 4 + 0];
|
||||
valid_box[i].offset.box_p[1] = offset[valid_i * 4 + 1];
|
||||
valid_box[i].offset.box_p[2] = offset[valid_i * 4 + 2];
|
||||
valid_box[i].offset.box_p[3] = offset[valid_i * 4 + 3];
|
||||
valid_box[i].next = &(valid_box[i + 1]);
|
||||
}
|
||||
valid_box[valid_count - 1].next = NULL;
|
||||
|
||||
free(valid_indexes);
|
||||
|
||||
return valid_list;
|
||||
} /*}}}*/
|
||||
|
||||
void image_nms_process(image_list_t *image_list, fptp_t nms_threshold, int same_area)
|
||||
{ /*{{{*/
|
||||
/**** Init ****/
|
||||
int num_supressed = 0;
|
||||
image_box_t *head = image_list->head;
|
||||
|
||||
/**** Compute Box Area ****/
|
||||
fptp_t kept_box_area = 0;
|
||||
fptp_t other_box_area = 0;
|
||||
if (same_area)
|
||||
{
|
||||
image_get_area(&(head->box), &kept_box_area);
|
||||
other_box_area = kept_box_area;
|
||||
}
|
||||
|
||||
/**** Compare IOU ****/
|
||||
image_box_t *kept_box = head;
|
||||
while (kept_box)
|
||||
{
|
||||
image_box_t *other_box = kept_box->next;
|
||||
image_box_t *prev = kept_box;
|
||||
while (other_box)
|
||||
{
|
||||
// kept_box is contained in other_box
|
||||
if (((kept_box->box.box_p[0] > other_box->box.box_p[0]) && (kept_box->box.box_p[1] > other_box->box.box_p[1]) && (kept_box->box.box_p[2] < other_box->box.box_p[2]) && (kept_box->box.box_p[3] < other_box->box.box_p[3]))
|
||||
// kept_box contains other_box
|
||||
|| ((kept_box->box.box_p[0] < other_box->box.box_p[0]) && (kept_box->box.box_p[1] < other_box->box.box_p[1]) && (kept_box->box.box_p[2] > other_box->box.box_p[2]) && (kept_box->box.box_p[3] > other_box->box.box_p[3])))
|
||||
// supress
|
||||
{
|
||||
num_supressed++;
|
||||
prev->next = other_box->next;
|
||||
other_box = other_box->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
box_t inter_box;
|
||||
inter_box.box_p[0] = DL_IMAGE_MAX(kept_box->box.box_p[0], other_box->box.box_p[0]);
|
||||
inter_box.box_p[1] = DL_IMAGE_MAX(kept_box->box.box_p[1], other_box->box.box_p[1]);
|
||||
inter_box.box_p[2] = DL_IMAGE_MIN(kept_box->box.box_p[2], other_box->box.box_p[2]);
|
||||
inter_box.box_p[3] = DL_IMAGE_MIN(kept_box->box.box_p[3], other_box->box.box_p[3]);
|
||||
|
||||
fptp_t inter_w, inter_h;
|
||||
image_get_width_and_height(&inter_box, &inter_w, &inter_h);
|
||||
|
||||
if (inter_w > 0 && inter_h > 0)
|
||||
{
|
||||
if (!same_area)
|
||||
{
|
||||
image_get_area(&(kept_box->box), &kept_box_area);
|
||||
image_get_area(&(other_box->box), &other_box_area);
|
||||
}
|
||||
fptp_t inter_area = inter_w * inter_h;
|
||||
fptp_t iou = inter_area / (kept_box_area + other_box_area - inter_area);
|
||||
if (iou > nms_threshold)
|
||||
{
|
||||
num_supressed++;
|
||||
// Delete duplicated box
|
||||
// Here we cannot free a single box, because these boxes are allocated by calloc, we need to free all the calloced memory together.
|
||||
prev->next = other_box->next;
|
||||
other_box = other_box->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
prev = other_box;
|
||||
other_box = other_box->next;
|
||||
}
|
||||
kept_box = kept_box->next;
|
||||
}
|
||||
|
||||
image_list->len -= num_supressed;
|
||||
} /*}}}*/
|
||||
|
||||
void transform_input_image(uint8_t *m, uint16_t *bmp, int count)
|
||||
{ /*{{{*/
|
||||
uc_t dst[24];
|
||||
for (int x = 0; x < count; x += 8)
|
||||
{
|
||||
rgb565_to_888(*bmp++, dst);
|
||||
rgb565_to_888(*bmp++, dst + 3);
|
||||
rgb565_to_888(*bmp++, dst + 6);
|
||||
rgb565_to_888(*bmp++, dst + 9);
|
||||
rgb565_to_888(*bmp++, dst + 12);
|
||||
rgb565_to_888(*bmp++, dst + 15);
|
||||
rgb565_to_888(*bmp++, dst + 18);
|
||||
rgb565_to_888(*bmp++, dst + 21);
|
||||
memcpy(m + x * 3, dst, 24 * sizeof(uint8_t));
|
||||
}
|
||||
} /*}}}*/
|
||||
|
||||
void transform_output_image(uint16_t *bmp, uint8_t *m, int count)
|
||||
{ /*{{{*/
|
||||
for (int x = 0; x < count; x++)
|
||||
{
|
||||
rgb888_to_565(bmp, m[2], m[1], m[0]);
|
||||
bmp++;
|
||||
m += 3;
|
||||
}
|
||||
} /*}}}*/
|
||||
|
||||
void draw_rectangle(uint16_t *buf, box_array_t *boxes, int width)
|
||||
{ /*{{{*/
|
||||
uint16_t p[14];
|
||||
for (int i = 0; i < boxes->len; i++)
|
||||
{
|
||||
// rectangle box
|
||||
for (int j = 0; j < 4; j++)
|
||||
p[j] = (uint16_t)boxes->box[i].box_p[j];
|
||||
|
||||
// landmark
|
||||
for (int j = 0; j < 10; j++)
|
||||
p[j + 4] = (uint16_t)boxes->landmark[i].landmark_p[j];
|
||||
|
||||
if ((p[2] < p[0]) || (p[3] < p[1]))
|
||||
return;
|
||||
|
||||
#define GREEN RGB565_MASK_GREEN
|
||||
#define RED RGB565_MASK_GREEN
|
||||
// rectangle box
|
||||
for (int w = p[0]; w < p[2] + 1; w++)
|
||||
{
|
||||
int x1 = (p[1] * width + w);
|
||||
int x2 = (p[3] * width + w);
|
||||
buf[x1] = GREEN; // Green
|
||||
buf[x2] = GREEN;
|
||||
}
|
||||
for (int h = p[1]; h < p[3] + 1; h++)
|
||||
{
|
||||
int y1 = (h * width + p[0]);
|
||||
int y2 = (h * width + p[2]);
|
||||
buf[y1] = GREEN; // Green
|
||||
buf[y2] = GREEN;
|
||||
}
|
||||
|
||||
// landmark
|
||||
for (int j = 0; j < 10; j += 2)
|
||||
{
|
||||
int x = p[j + 5] * width + p[j + 4];
|
||||
buf[x] = RED; // Red
|
||||
buf[x + 1] = RED;
|
||||
buf[x + 2] = RED;
|
||||
|
||||
buf[width + x] = RED; // Red
|
||||
buf[width + x + 1] = RED;
|
||||
buf[width + x + 2] = RED;
|
||||
|
||||
buf[2 * width + x] = RED; // Red
|
||||
buf[2 * width + x + 1] = RED;
|
||||
buf[2 * width + x + 2] = RED;
|
||||
}
|
||||
}
|
||||
} /*}}}*/
|
|
@ -1,262 +0,0 @@
|
|||
/*
|
||||
* ESPRESSIF MIT License
|
||||
*
|
||||
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
|
||||
*
|
||||
* Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case,
|
||||
* it is free of charge, to any person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
#include <stdint.h>
|
||||
#include "mtmn.h"
|
||||
|
||||
#define BOX_LEN (80)
|
||||
#define MIN_FACE (12.0)
|
||||
#define MAX_VALID_COUNT_PER_IMAGE (30)
|
||||
|
||||
#define DL_IMAGE_MIN(A, B) ((A) < (B) ? (A) : (B))
|
||||
#define DL_IMAGE_MAX(A, B) ((A) < (B) ? (B) : (A))
|
||||
|
||||
#define IMAGE_WIDTH 320
|
||||
#define IMAGE_HEIGHT 240
|
||||
|
||||
#define RGB565_MASK_RED 0xF800
|
||||
#define RGB565_MASK_GREEN 0x07E0
|
||||
#define RGB565_MASK_BLUE 0x001F
|
||||
|
||||
typedef struct
|
||||
{
|
||||
fptp_t landmark_p[10];
|
||||
} landmark_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
fptp_t box_p[4];
|
||||
} box_t;
|
||||
|
||||
typedef struct tag_box_list
|
||||
{
|
||||
box_t *box;
|
||||
landmark_t *landmark;
|
||||
int len;
|
||||
} box_array_t;
|
||||
|
||||
typedef struct tag_image_box
|
||||
{
|
||||
struct tag_image_box *next;
|
||||
fptp_t score;
|
||||
box_t box;
|
||||
box_t offset;
|
||||
landmark_t landmark;
|
||||
} image_box_t;
|
||||
|
||||
typedef struct tag_image_list
|
||||
{
|
||||
image_box_t *head;
|
||||
image_box_t *origin_head;
|
||||
int len;
|
||||
} image_list_t;
|
||||
|
||||
static inline void image_get_width_and_height(box_t *box, float *w, float *h)
|
||||
{
|
||||
*w = box->box_p[2] - box->box_p[0] + 1;
|
||||
*h = box->box_p[3] - box->box_p[1] + 1;
|
||||
}
|
||||
|
||||
static inline void image_get_area(box_t *box, float *area)
|
||||
{
|
||||
float w, h;
|
||||
image_get_width_and_height(box, &w, &h);
|
||||
*area = w * h;
|
||||
}
|
||||
|
||||
static inline void image_calibrate_by_offset(image_list_t *image_list)
|
||||
{
|
||||
for (image_box_t *head = image_list->head; head; head = head->next)
|
||||
{
|
||||
float w, h;
|
||||
image_get_width_and_height(&(head->box), &w, &h);
|
||||
head->box.box_p[0] = DL_IMAGE_MAX(0, head->box.box_p[0] + head->offset.box_p[0] * w);
|
||||
head->box.box_p[1] = DL_IMAGE_MAX(0, head->box.box_p[1] + head->offset.box_p[1] * w);
|
||||
head->box.box_p[2] += head->offset.box_p[2] * w;
|
||||
if (head->box.box_p[2] > IMAGE_WIDTH)
|
||||
{
|
||||
head->box.box_p[2] = IMAGE_WIDTH - 1;
|
||||
head->box.box_p[0] = IMAGE_WIDTH - w;
|
||||
}
|
||||
head->box.box_p[3] += head->offset.box_p[3] * h;
|
||||
if (head->box.box_p[3] > IMAGE_HEIGHT)
|
||||
{
|
||||
head->box.box_p[3] = IMAGE_HEIGHT - 1;
|
||||
head->box.box_p[1] = IMAGE_HEIGHT - h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void image_landmark_calibrate(image_list_t *image_list)
|
||||
{
|
||||
for (image_box_t *head = image_list->head; head; head = head->next)
|
||||
{
|
||||
float w, h;
|
||||
image_get_width_and_height(&(head->box), &w, &h);
|
||||
head->landmark.landmark_p[0] = head->box.box_p[0] + head->landmark.landmark_p[0] * w;
|
||||
head->landmark.landmark_p[1] = head->box.box_p[1] + head->landmark.landmark_p[1] * h;
|
||||
|
||||
head->landmark.landmark_p[2] = head->box.box_p[0] + head->landmark.landmark_p[2] * w;
|
||||
head->landmark.landmark_p[3] = head->box.box_p[1] + head->landmark.landmark_p[3] * h;
|
||||
|
||||
head->landmark.landmark_p[4] = head->box.box_p[0] + head->landmark.landmark_p[4] * w;
|
||||
head->landmark.landmark_p[5] = head->box.box_p[1] + head->landmark.landmark_p[5] * h;
|
||||
|
||||
head->landmark.landmark_p[6] = head->box.box_p[0] + head->landmark.landmark_p[6] * w;
|
||||
head->landmark.landmark_p[7] = head->box.box_p[1] + head->landmark.landmark_p[7] * h;
|
||||
|
||||
head->landmark.landmark_p[8] = head->box.box_p[0] + head->landmark.landmark_p[8] * w;
|
||||
head->landmark.landmark_p[9] = head->box.box_p[1] + head->landmark.landmark_p[9] * h;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void image_rect2sqr(box_array_t *boxes, int width, int height)
|
||||
{
|
||||
for (int i = 0; i < boxes->len; i++)
|
||||
{
|
||||
box_t *box = &(boxes->box[i]);
|
||||
float w, h;
|
||||
image_get_width_and_height(box, &w, &h);
|
||||
float l = DL_IMAGE_MAX(w, h);
|
||||
|
||||
box->box_p[0] = DL_IMAGE_MAX(0, box->box_p[0] + 0.5 * (w - l));
|
||||
box->box_p[1] = DL_IMAGE_MAX(0, box->box_p[1] + 0.5 * (h - l));
|
||||
box->box_p[2] = box->box_p[0] + l - 1;
|
||||
if (box->box_p[2] > width)
|
||||
{
|
||||
box->box_p[2] = width - 1;
|
||||
box->box_p[0] = width - l;
|
||||
}
|
||||
box->box_p[3] = box->box_p[1] + l - 1;
|
||||
if (box->box_p[3] > height)
|
||||
{
|
||||
box->box_p[3] = height - 1;
|
||||
box->box_p[1] = height - l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void rgb565_to_888(uint16_t in, uint8_t *dst)
|
||||
{ /*{{{*/
|
||||
dst[0] = (in & RGB565_MASK_BLUE) << 3; // blue
|
||||
dst[1] = (in & RGB565_MASK_GREEN) >> 3; // green
|
||||
dst[2] = (in & RGB565_MASK_RED) >> 8; // red
|
||||
} /*}}}*/
|
||||
|
||||
static inline void rgb888_to_565(uint16_t *in, uint8_t r, uint8_t g, uint8_t b)
|
||||
{ /*{{{*/
|
||||
uint16_t rgb565 = 0;
|
||||
rgb565 = ((r >> 3) << 11);
|
||||
rgb565 |= ((g >> 2) << 5);
|
||||
rgb565 |= (b >> 3);
|
||||
*in = rgb565;
|
||||
} /*}}}*/
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param score
|
||||
* @param offset
|
||||
* @param width
|
||||
* @param height
|
||||
* @param score_threshold
|
||||
* @param scale
|
||||
* @return image_list_t*
|
||||
*/
|
||||
image_list_t *image_get_valid_boxes(fptp_t *score, fptp_t *offset, int width, int height, fptp_t score_threshold, fptp_t scale);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param image_sorted_list
|
||||
* @param insert_list
|
||||
*/
|
||||
void image_sort_insert_by_score(image_list_t *image_sorted_list, const image_list_t *insert_list);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param image_list
|
||||
* @param nms_threshold
|
||||
* @param same_area
|
||||
*/
|
||||
void image_nms_process(image_list_t *image_list, fptp_t nms_threshold, int same_area);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param dst_image
|
||||
* @param src_image
|
||||
* @param dst_w
|
||||
* @param dst_h
|
||||
* @param dst_c
|
||||
* @param src_w
|
||||
* @param src_h
|
||||
*/
|
||||
void image_resize_linear(uint8_t *dst_image, uint8_t *src_image, int dst_w, int dst_h, int dst_c, int src_w, int src_h);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param corp_image
|
||||
* @param src_image
|
||||
* @param rotate_angle
|
||||
* @param ratio
|
||||
* @param center
|
||||
*/
|
||||
void image_cropper(dl_matrix3du_t *corp_image, dl_matrix3du_t *src_image, float rotate_angle, float ratio, float *center);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param m
|
||||
* @param bmp
|
||||
* @param count
|
||||
*/
|
||||
void transform_input_image(uint8_t *m, uint16_t *bmp, int count);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param bmp
|
||||
* @param m
|
||||
* @param count
|
||||
*/
|
||||
void transform_output_image(uint16_t *bmp, uint8_t *m, int count);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param buf
|
||||
* @param boxes
|
||||
* @param width
|
||||
*/
|
||||
void draw_rectangle(uint16_t *buf, box_array_t *boxes, int width);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1,6 +1,6 @@
|
|||
# Camera with Command Line in Single Chip
|
||||
# Recognition with Command Line in Single Chip
|
||||
|
||||
This example implements Human Face Detection with a single ESP32 chip and without LCD. ESP32 gets input of image from camera and displays results in the Command Line after recognition.
|
||||
This example implements Human Face Recognition with a single ESP32 chip and without LCD. ESP32 gets input of image from camera and displays results in the Command Line after recognition.
|
||||
|
||||
# Preparation
|
||||
|
||||
|
@ -16,7 +16,7 @@ Any other confusions about preparation, please see general guide in the README.m
|
|||
|
||||
# Quick Start
|
||||
|
||||
If preparations are ready, please follow this section to **connect** the camera to ESP32 module, flash application to ESP32, finally execute human face detection and display the **result**.
|
||||
If preparations are ready, please follow this section to **connect** the camera to ESP32 module, flash application to ESP32, finally execute human face recognition and display the **result**.
|
||||
|
||||
## Connect
|
||||
Specific pins used in this example to connect ESP32 module and camera module are listed in table below.
|
||||
|
@ -50,9 +50,28 @@ In particular, if you have a **ESP-WROVER-KIT**, camera connector is already bro
|
|||
|
||||
## Results
|
||||
|
||||
Open a serial terminal by using `make monitor` at this project, point the camera to a human face with a distance of 0.3m at least, then you will see the following information:
|
||||
Open a serial terminal by using `make monitor` at this project, point the camera to a human face with a distance of 0.3m at least, then face entry will start after two faces are detected. The following information shows the output of the command line before the entry process:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
The key word **DETECTED** comes out when a human face is detected.
|
||||
#### Login
|
||||
|
||||
Then the face entry process will begin, during which the program will input multiple faces to get a person's ID:
|
||||
|
||||

|
||||
|
||||
You can also reset the number of faces entered by one person, which is set to 3 by default.
|
||||
When the last face of ID is entered, the process of face recognition will start:
|
||||
|
||||

|
||||
|
||||
#### Recognition
|
||||
|
||||
When a face is detected, it will be recognized whether the face is the same as the entered ID. If it is the same, the corresponding ID number will be displayed:
|
||||
|
||||

|
||||
|
||||
Otherwise, the command line will display `No Matched ID`:
|
||||
|
||||

|
||||
|
|
|
@ -68,6 +68,8 @@ void task_process(void *arg)
|
|||
int is_logging = 1;
|
||||
int next_logging_index = 0;
|
||||
|
||||
int64_t timestamp = 0;
|
||||
|
||||
do
|
||||
{
|
||||
img_buffer = (uint16_t *)facenet_get_image();
|
||||
|
@ -75,7 +77,9 @@ void task_process(void *arg)
|
|||
img_buffer,
|
||||
gl_input_image_width * gl_input_image_height);
|
||||
|
||||
timestamp = esp_timer_get_time();
|
||||
box_array_t *net_boxes = face_detect(image_matrix);
|
||||
ESP_LOGI(TAG, "Detection time consumption: %lldms", (esp_timer_get_time() - timestamp) / 1000);
|
||||
|
||||
if (net_boxes)
|
||||
{
|
||||
|
@ -129,10 +133,19 @@ void task_process(void *arg)
|
|||
}
|
||||
else
|
||||
{
|
||||
timestamp = esp_timer_get_time();
|
||||
int matched_id = recognize_face(aligned_face,
|
||||
id_list, thresh,
|
||||
next_logging_index);
|
||||
ESP_LOGE(TAG, "Matched ID: %d", matched_id);
|
||||
if (matched_id)
|
||||
{
|
||||
ESP_LOGE(TAG, "Matched ID: %d", matched_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "No Matched ID");
|
||||
}
|
||||
ESP_LOGI(TAG, "Recognition time consumption: %lldms", (esp_timer_get_time() - timestamp) / 1000);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
BIN
img/detected.png
Before Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 30 KiB |