HAN

7 minute read

Thật bất ngờ phải không, nhưng đây là sự thật. Giờ đây với Arduino và ESP32 chúng ta đã có khả năng nhận diện khuôn mặt. Mình đã làm thành thành công với module ESP-EYE. Còn dưới đây là cách làm mình dịch từ https://robotzero.one/esp32-face-door-entry/

Sử dụng nhận dạng khuôn mặt để mở cửa hoặc điều khiển các thiết bị tự động hóa gia đình khác

Trong hướng dẫn này sẽ giải thích cách lưu hình ảnh đã đăng ký (eroll face) lên bộ nhớ, và sau đó chúng ta có thể sử dụng các nhận dạng đã lưu này để so sánh với hình ảnh thu được từ ESP32. Có ba bước:

  • Tạo sơ đồ phân vùng mới để cho phép lưu trữ liên tục
  • Sửa đổi bản phác thảo ví dụ CameraWebServer để lưu dữ liệu khuôn mặt vào phân vùng mới
  • Sử dụng các nhận dạng đã lưu này để điều khiển các thiết bị được kết nối với ESP32

Trước khi làm theo hướng dẫn này, hãy đảm bảo bạn đã chạy thành công Example: CameraWebServer

Phân vùng lại vùng nhớ của ESP32

Phải phân vùng lại ESP32 để tạo ra vùng lưu trữ khuôn mặt đã nhận dạng. Tải xuống file phân vùng của tác giả: https://robotzero.one/wp-content/uploads/2019/04/rzo_partitions.csv

Thêm tệp này vào thư mục chứa các lược đồ phân vùng khác. Điều này được tìm thấy ở một trong hai nơi, tùy thuộc vào cách bạn cài đặt Arduino IDE.

Arduino IDE được cài đặt từ Windows Store:

C > Users > your-user-name > Documents > ArduinoData > packages > esp32 > hardware > esp32 > 1.0.4 > tools > partitions

Arduino IDE được cài đặt từ trang web Arduino:

C > Users > your-user-name > AppData > Local > Arduino15 > packages > esp32 > hardware > esp32 > 1.0.4 > tools > partitions

Lược đồ mới phải được thêm vào thiết bị ESP của bạn trong tệp cấu hình trình quản lý bảng - board.txt. Một lần nữa điều này được tìm thấy ở một trong hai nơi.

Arduino IDE được cài đặt từ Windows Store:

C > Users > your-user-name > Documents > ArduinoData > packages > esp32 > hardware > esp32 > 1.0.4

Arduino IDE được cài đặt từ trang web Arduino:

C > Users > your-user-name > AppData > Local > Arduino15 > packages > esp32 > hardware > esp32 > 1.0.4

Thêm ba dòng sau bên dưới các tùy chọn phân vùng hiện có cho bảng Esp32wrover trong tệp board.txt này.

esp32wrover.menu.PartitionScheme.rzo_partition=Face Recognition (2621440 bytes with OTA)
esp32wrover.menu.PartitionScheme.rzo_partition.build.partitions=rzo_partitions
esp32wrover.menu.PartitionScheme.rzo_partition.upload.maximum_size=2621440
Đóng và mở lại IDE để xác nhận lược đồ phân vùng ‘Face Recognition’ mới có sẵn trong menu Công cụ.

Thu thập dữ liệu khuôn mặt để lưu trữ

Ví dụ về CameraWebServer trong IDE không lưu các khuôn mặt đã đăng ký khi mất điện. Để sửa đổi nó để sử dụng phân vùng mới, một vài thay đổi cần được thực hiện.

Trong Arduino IDE, tạo một bản sao CameraWebServer. Ta sẽ sử dụng sketch này để học và lưu lại các khuôn mặt.

Bạn sẽ thấy ba tab trong Arduino IDE tương tự như hình ảnh bên dưới:

Arduino IDE Tab

Trong tab thứ hai (app_httpd.cpp) thực hiện các thay đổi sau.

Sau #include “fr_forward.h” (khoảng dòng 24) thêm:

#include “fr_flash.h”;

Thay đổi int8_t left_sample_face = enroll_face(&id_list, aligned_face);(&id_list, aligned_face); (khoảng dòng 178) thành:

int8_t left_sample_face = enroll_face_id_to_flash(&id_list, aligned_face);

Sau face_id_init(&id_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES); (khoảng dòng 636) thêm:

read_face_id_from_flash(&id_list);

Flash và chạy Phác thảo này theo cách tương tự như trước đây. Dữ liệu khuôn mặt đã đăng ký sẽ được lưu vào phân vùng mới trên bộ nhớ flash.

Sự kiện kích hoạt nhận dạng khuôn mặt

Khi nhận dạng đúng khuôn mặt đã được học, chân RelayPin sẽ được kéo lên mức HIGH. Sau 5 giây trôi qua, nếu không có mặt nhận dạng, pin được đặt ở mức LOW. Mã trong hàm rzoCheckForFace() có thể được thay đổi thành bất kỳ chức năng nào bạn yêu cầu khi nhận diện khuôn mặt.

(Đoạn code dưới đây không phải nguyên gốc trong bài viết, đã được sửa đổi để phù hợp với board ESP-EYE và chân relay đã được đổi qua chân 21)

#include "esp_camera.h"
#include "fd_forward.h"
#include "fr_forward.h"
#include "fr_flash.h"

#define relayPin 21 // pin 12 can also be used
unsigned long currentMillis = 0;
unsigned long openedMillis = 0;
long interval = 5000;           // open lock for ... milliseconds

#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM    4
#define SIOD_GPIO_NUM    18
#define SIOC_GPIO_NUM    23

#define Y9_GPIO_NUM      36
#define Y8_GPIO_NUM      37
#define Y7_GPIO_NUM      38
#define Y6_GPIO_NUM      39
#define Y5_GPIO_NUM      35
#define Y4_GPIO_NUM      14
#define Y3_GPIO_NUM      13
#define Y2_GPIO_NUM      34
#define VSYNC_GPIO_NUM   5
#define HREF_GPIO_NUM    27
#define PCLK_GPIO_NUM    25

#define ENROLL_CONFIRM_TIMES 5
#define FACE_ID_SAVE_NUMBER 7

static inline mtmn_config_t app_mtmn_config()
{
  mtmn_config_t mtmn_config = {0};
  mtmn_config.type = FAST;
  mtmn_config.min_face = 80;
  mtmn_config.pyramid = 0.707;
  mtmn_config.pyramid_times = 4;
  mtmn_config.p_threshold.score = 0.6;
  mtmn_config.p_threshold.nms = 0.7;
  mtmn_config.p_threshold.candidate_number = 20;
  mtmn_config.r_threshold.score = 0.7;
  mtmn_config.r_threshold.nms = 0.7;
  mtmn_config.r_threshold.candidate_number = 10;
  mtmn_config.o_threshold.score = 0.7;
  mtmn_config.o_threshold.nms = 0.7;
  mtmn_config.o_threshold.candidate_number = 1;
  return mtmn_config;
}
mtmn_config_t mtmn_config = app_mtmn_config();

static face_id_list id_list = {0};
dl_matrix3du_t *image_matrix =  NULL;
camera_fb_t * fb = NULL;

dl_matrix3du_t *aligned_face = dl_matrix3du_alloc(1, FACE_WIDTH, FACE_HEIGHT, 3);

void setup() {
  Serial.begin(115200);

  digitalWrite(relayPin, LOW);
  pinMode(relayPin, OUTPUT);

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_UXGA;
  config.jpeg_quality = 10;
  config.fb_count = 2;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  //drop down frame size for higher initial frame rate
  sensor_t * s = esp_camera_sensor_get();
  s->set_framesize(s, FRAMESIZE_QVGA);
  s->set_vflip(s, 1);

  face_id_init(&id_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES);
  read_face_id_from_flash(&id_list);// Read current face data from on-board flash
}

void rzoCheckForFace() {
  currentMillis = millis();
  if (run_face_recognition()) { // face recognition function has returned true
    Serial.println("Face recognised");
    digitalWrite(relayPin, HIGH); //close (energise) relay
    openedMillis = millis(); //time relay closed
  }
  if (currentMillis - interval > openedMillis) { // current time - face recognised time > 5 secs
    digitalWrite(relayPin, LOW); //open relay
  }
}

bool run_face_recognition() {
  bool faceRecognised = false; // default
  int64_t start_time = esp_timer_get_time();
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return false;
  }

  int64_t fb_get_time = esp_timer_get_time();
  Serial.printf("Get one frame in %u ms.\n", (fb_get_time - start_time) / 1000); // this line can be commented out

  image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3);
  uint32_t res = fmt2rgb888(fb->buf, fb->len, fb->format, image_matrix->item);
  if (!res) {
    Serial.println("to rgb888 failed");
    dl_matrix3du_free(image_matrix);
  }

  esp_camera_fb_return(fb);

  box_array_t *net_boxes = face_detect(image_matrix, &mtmn_config);

  if (net_boxes) {
    if (align_face(net_boxes, image_matrix, aligned_face) == ESP_OK) {

      int matched_id = recognize_face(&id_list, aligned_face);
      if (matched_id >= 0) {
        Serial.printf("Match Face ID: %u\n", matched_id);
        faceRecognised = true; // function will now return true
      } else {
        Serial.println("No Match Found");
        matched_id = -1;
      }
    } else {
      Serial.println("Face Not Aligned");
    }

    free(net_boxes->box);
    free(net_boxes->landmark);
    free(net_boxes);
  }

  dl_matrix3du_free(image_matrix);
  return faceRecognised;
}

void loop() {
  rzoCheckForFace();
}

Sau khi flash và chạy sketch mới này thì khi nhận được khuôn mặt đã được lưu ở phần thu thập dữ liệu đèn LED trên board ESP-EYE sẽ sáng lên.

Mở cửa

Bây giờ tôi chỉ cần câu chân 21 qua module relay. Tiếp tục sử dụng relay để điều khiển chốt cửa bằng điện là chúng ta đã hòa thành dự án.

Xóa khuôn mặt khỏi bộ nhớ

Nếu bạn cần xóa các mặt được lưu trữ, hãy dán và tải lên sketch bên dưới

#include "esp_camera.h"
#include "fd_forward.h"
#include "fr_forward.h"
#include "fr_flash.h"

#define ENROLL_CONFIRM_TIMES 5
#define FACE_ID_SAVE_NUMBER 7

static face_id_list id_list = {0};

void setup() {
  Serial.begin(115200);
  face_id_init(&id_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES);
  read_face_id_from_flash(&id_list);// Read current face data from on-board flash
  Serial.println("Faces Read"); 
  while ( delete_face_id_in_flash(&id_list) > -1 ){
        Serial.println("Deleting Face");
  }
        Serial.println("All Deleted");  
}

void loop() {
}