├── GitHub-esp32-cam-webserver-camcontrol ├── GitHub-esp32-cam-webserver-camcontrol.ino └── app_httpd.cpp └── README.md /GitHub-esp32-cam-webserver-camcontrol/GitHub-esp32-cam-webserver-camcontrol.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // 8 | // WARNING!!! Make sure that you have either selected ESP32 Wrover Module, 9 | // or another board which has PSRAM enabled 10 | //this edit code doesn't use a gzipped html source code 11 | //the html -code is open and is easy to update or modify on the other tab page = app_httpd.cpp 12 | 13 | // Select camera model 14 | //#define CAMERA_MODEL_WROVER_KIT 15 | //#define CAMERA_MODEL_M5STACK_PSRAM 16 | #define CAMERA_MODEL_AI_THINKER 17 | 18 | const char* ssid = "YOUR SSID"; 19 | const char* password = "YOUR SSID PASSWORD"; 20 | 21 | 22 | #if defined(CAMERA_MODEL_WROVER_KIT) 23 | #define PWDN_GPIO_NUM -1 24 | #define RESET_GPIO_NUM -1 25 | #define XCLK_GPIO_NUM 21 26 | #define SIOD_GPIO_NUM 26 27 | #define SIOC_GPIO_NUM 27 28 | #define Y9_GPIO_NUM 35 29 | #define Y8_GPIO_NUM 34 30 | #define Y7_GPIO_NUM 39 31 | #define Y6_GPIO_NUM 36 32 | #define Y5_GPIO_NUM 19 33 | #define Y4_GPIO_NUM 18 34 | #define Y3_GPIO_NUM 5 35 | #define Y2_GPIO_NUM 4 36 | #define VSYNC_GPIO_NUM 25 37 | #define HREF_GPIO_NUM 23 38 | #define PCLK_GPIO_NUM 22 39 | 40 | #elif defined(CAMERA_MODEL_M5STACK_PSRAM) 41 | #define PWDN_GPIO_NUM -1 42 | #define RESET_GPIO_NUM 15 43 | #define XCLK_GPIO_NUM 27 44 | #define SIOD_GPIO_NUM 25 45 | #define SIOC_GPIO_NUM 23 46 | 47 | #define Y9_GPIO_NUM 19 48 | #define Y8_GPIO_NUM 36 49 | #define Y7_GPIO_NUM 18 50 | #define Y6_GPIO_NUM 39 51 | #define Y5_GPIO_NUM 5 52 | #define Y4_GPIO_NUM 34 53 | #define Y3_GPIO_NUM 35 54 | #define Y2_GPIO_NUM 32 55 | #define VSYNC_GPIO_NUM 22 56 | #define HREF_GPIO_NUM 26 57 | #define PCLK_GPIO_NUM 21 58 | 59 | #elif defined(CAMERA_MODEL_AI_THINKER) //zie esp32-cam schema v1.6.pdf 60 | #define PWDN_GPIO_NUM 32 61 | #define RESET_GPIO_NUM -1 62 | #define XCLK_GPIO_NUM 0 63 | #define SIOD_GPIO_NUM 26 64 | #define SIOC_GPIO_NUM 27 65 | #define Y9_GPIO_NUM 35 66 | #define Y8_GPIO_NUM 34 67 | #define Y7_GPIO_NUM 39 68 | #define Y6_GPIO_NUM 36 69 | #define Y5_GPIO_NUM 21 70 | #define Y4_GPIO_NUM 19 71 | #define Y3_GPIO_NUM 18 72 | #define Y2_GPIO_NUM 5 73 | #define VSYNC_GPIO_NUM 25 74 | #define HREF_GPIO_NUM 23 75 | #define PCLK_GPIO_NUM 22 76 | #else 77 | #error "Camera model not selected" 78 | #endif 79 | 80 | Servo ServoFake, ServoRotate, ServoTilt; 81 | int servoFakePin = 12; 82 | int servoRotatePin = 13; 83 | int servoTiltPin = 14; 84 | 85 | 86 | extern int ValCamTilt, ValCamRotate, ValCamFake; 87 | 88 | void startCameraServer(); 89 | 90 | void setup() { 91 | 92 | pinMode(servoFakePin, OUTPUT); 93 | pinMode(servoRotatePin, OUTPUT); 94 | pinMode(servoTiltPin, OUTPUT); 95 | //Servo déclarations 96 | ServoFake.attach(servoFakePin, 500, 2400); 97 | ServoRotate.attach(servoRotatePin, 500, 2400); 98 | ServoTilt.attach(servoTiltPin, 500, 2400); 99 | 100 | 101 | WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector 102 | Serial.begin(115200); 103 | Serial.setDebugOutput(true); 104 | Serial.println(); 105 | 106 | camera_config_t config; 107 | config.ledc_channel = LEDC_CHANNEL_0; 108 | config.ledc_timer = LEDC_TIMER_0; 109 | config.pin_d0 = Y2_GPIO_NUM; 110 | config.pin_d1 = Y3_GPIO_NUM; 111 | config.pin_d2 = Y4_GPIO_NUM; 112 | config.pin_d3 = Y5_GPIO_NUM; 113 | config.pin_d4 = Y6_GPIO_NUM; 114 | config.pin_d5 = Y7_GPIO_NUM; 115 | config.pin_d6 = Y8_GPIO_NUM; 116 | config.pin_d7 = Y9_GPIO_NUM; 117 | config.pin_xclk = XCLK_GPIO_NUM; 118 | config.pin_pclk = PCLK_GPIO_NUM; 119 | config.pin_vsync = VSYNC_GPIO_NUM; 120 | config.pin_href = HREF_GPIO_NUM; 121 | config.pin_sscb_sda = SIOD_GPIO_NUM; 122 | config.pin_sscb_scl = SIOC_GPIO_NUM; 123 | config.pin_pwdn = PWDN_GPIO_NUM; 124 | config.pin_reset = RESET_GPIO_NUM; 125 | config.xclk_freq_hz = 20000000; 126 | config.pixel_format = PIXFORMAT_JPEG; 127 | //init with high specs to pre-allocate larger buffers 128 | if(psramFound()){ 129 | config.frame_size = FRAMESIZE_UXGA; 130 | config.jpeg_quality = 10; 131 | config.fb_count = 2; 132 | } else { 133 | config.frame_size = FRAMESIZE_SVGA; 134 | config.jpeg_quality = 12; 135 | config.fb_count = 1; 136 | } 137 | 138 | // camera init 139 | esp_err_t err = esp_camera_init(&config); 140 | if (err != ESP_OK) { 141 | Serial.printf("Camera init failed with error 0x%x", err); 142 | return; 143 | } 144 | 145 | //drop down frame size for higher initial frame rate 146 | sensor_t * s = esp_camera_sensor_get(); 147 | //s->set_framesize(s, FRAMESIZE_QVGA); 148 | s->set_framesize(s, FRAMESIZE_UXGA);//v12345vtm 149 | 150 | 151 | 152 | 153 | WiFi.begin(ssid, password); 154 | 155 | while (WiFi.status() != WL_CONNECTED) { 156 | delay(500); 157 | Serial.print("."); 158 | } 159 | Serial.println(""); 160 | Serial.println("WiFi connected"); 161 | 162 | startCameraServer(); 163 | 164 | Serial.print("Camera Ready! Use 'http://"); 165 | Serial.print(WiFi.localIP()); 166 | Serial.println("' to connect , de stream zit op een andere poortkanaal 9601 "); 167 | Serial.print("stream Ready! Use 'http://"); 168 | Serial.print(WiFi.localIP()); 169 | Serial.println(":9601/stream "); 170 | Serial.print("image Ready! Use 'http://"); 171 | Serial.print(WiFi.localIP()); 172 | Serial.println("/capture "); 173 | 174 | } 175 | 176 | void loop() { 177 | Servos();// put your main code here, to run repeatedly: 178 | delay(250); 179 | } 180 | 181 | void Servos() { 182 | Serial.print("ValCamTilt : "); 183 | Serial.println(ValCamTilt); 184 | ServoTilt.write(ValCamTilt); 185 | 186 | Serial.print("ValCamRotate : "); 187 | Serial.println(ValCamRotate); 188 | ServoRotate.write(ValCamRotate); 189 | 190 | } 191 | -------------------------------------------------------------------------------- /GitHub-esp32-cam-webserver-camcontrol/app_httpd.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #include "esp_http_server.h" 15 | #include "esp_timer.h" 16 | #include "esp_camera.h" 17 | #include "img_converters.h" 18 | //#include "camera_index.h" 19 | //#include "camera_index_html.h" 20 | #include "Arduino.h" 21 | 22 | 23 | #include "fb_gfx.h" 24 | #include "fd_forward.h" 25 | #include "dl_lib.h" 26 | #include "fr_forward.h" 27 | 28 | #define ENROLL_CONFIRM_TIMES 5 29 | #define FACE_ID_SAVE_NUMBER 7 30 | 31 | #define FACE_COLOR_WHITE 0x00FFFFFF 32 | #define FACE_COLOR_BLACK 0x00000000 33 | #define FACE_COLOR_RED 0x000000FF 34 | #define FACE_COLOR_GREEN 0x0000FF00 35 | #define FACE_COLOR_BLUE 0x00FF0000 36 | #define FACE_COLOR_YELLOW (FACE_COLOR_RED | FACE_COLOR_GREEN) 37 | #define FACE_COLOR_CYAN (FACE_COLOR_BLUE | FACE_COLOR_GREEN) 38 | #define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED) 39 | 40 | 41 | 42 | ///////// 43 | 44 | 45 | 46 | static const char PROGMEM INDEX2_HTML[] = R"rawliteral( 47 | 48 | 49 | 50 | 51 | 52 | ESP32 OV2460 simplified by v12345vtm 53 | 56 | 57 | 58 |
59 | 62 |
63 | 274 |
275 | 279 |
280 |
281 |
282 | 285 | 286 | 287 | 288 | 289 | )rawliteral"; 290 | /////////// 291 | int ValCamTilt = 90, ValCamRotate = 90, ValCamFake = 90; 292 | 293 | typedef struct { 294 | size_t size; //number of values used for filtering 295 | size_t index; //current value index 296 | size_t count; //value count 297 | int sum; 298 | int * values; //array to be filled with values 299 | } ra_filter_t; 300 | 301 | typedef struct { 302 | httpd_req_t *req; 303 | size_t len; 304 | } jpg_chunking_t; 305 | 306 | #define PART_BOUNDARY "123456789000000000000987654321" 307 | static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; 308 | static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; 309 | static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; 310 | 311 | static ra_filter_t ra_filter; 312 | httpd_handle_t stream_httpd = NULL; 313 | httpd_handle_t camera_httpd = NULL; 314 | 315 | static mtmn_config_t mtmn_config = {0}; 316 | static int8_t detection_enabled = 0; 317 | static int8_t recognition_enabled = 0; 318 | static int8_t is_enrolling = 0; 319 | static face_id_list id_list = {0}; 320 | 321 | static ra_filter_t * ra_filter_init(ra_filter_t * filter, size_t sample_size){ 322 | memset(filter, 0, sizeof(ra_filter_t)); 323 | 324 | filter->values = (int *)malloc(sample_size * sizeof(int)); 325 | if(!filter->values){ 326 | return NULL; 327 | } 328 | memset(filter->values, 0, sample_size * sizeof(int)); 329 | 330 | filter->size = sample_size; 331 | return filter; 332 | } 333 | 334 | static int ra_filter_run(ra_filter_t * filter, int value){ 335 | if(!filter->values){ 336 | return value; 337 | } 338 | filter->sum -= filter->values[filter->index]; 339 | filter->values[filter->index] = value; 340 | filter->sum += filter->values[filter->index]; 341 | filter->index++; 342 | filter->index = filter->index % filter->size; 343 | if (filter->count < filter->size) { 344 | filter->count++; 345 | } 346 | return filter->sum / filter->count; 347 | } 348 | 349 | static void rgb_print(dl_matrix3du_t *image_matrix, uint32_t color, const char * str){ 350 | fb_data_t fb; 351 | fb.width = image_matrix->w; 352 | fb.height = image_matrix->h; 353 | fb.data = image_matrix->item; 354 | fb.bytes_per_pixel = 3; 355 | fb.format = FB_BGR888; 356 | fb_gfx_print(&fb, (fb.width - (strlen(str) * 14)) / 2, 10, color, str); 357 | } 358 | 359 | static int rgb_printf(dl_matrix3du_t *image_matrix, uint32_t color, const char *format, ...){ 360 | char loc_buf[64]; 361 | char * temp = loc_buf; 362 | int len; 363 | va_list arg; 364 | va_list copy; 365 | va_start(arg, format); 366 | va_copy(copy, arg); 367 | len = vsnprintf(loc_buf, sizeof(loc_buf), format, arg); 368 | va_end(copy); 369 | if(len >= sizeof(loc_buf)){ 370 | temp = (char*)malloc(len+1); 371 | if(temp == NULL) { 372 | return 0; 373 | } 374 | } 375 | vsnprintf(temp, len+1, format, arg); 376 | va_end(arg); 377 | rgb_print(image_matrix, color, temp); 378 | if(len > 64){ 379 | free(temp); 380 | } 381 | return len; 382 | } 383 | 384 | static void draw_face_boxes(dl_matrix3du_t *image_matrix, box_array_t *boxes, int face_id){ 385 | int x, y, w, h, i; 386 | uint32_t color = FACE_COLOR_YELLOW; 387 | if(face_id < 0){ 388 | color = FACE_COLOR_RED; 389 | } else if(face_id > 0){ 390 | color = FACE_COLOR_GREEN; 391 | } 392 | fb_data_t fb; 393 | fb.width = image_matrix->w; 394 | fb.height = image_matrix->h; 395 | fb.data = image_matrix->item; 396 | fb.bytes_per_pixel = 3; 397 | fb.format = FB_BGR888; 398 | for (i = 0; i < boxes->len; i++){ 399 | // rectangle box 400 | x = (int)boxes->box[i].box_p[0]; 401 | y = (int)boxes->box[i].box_p[1]; 402 | w = (int)boxes->box[i].box_p[2] - x + 1; 403 | h = (int)boxes->box[i].box_p[3] - y + 1; 404 | fb_gfx_drawFastHLine(&fb, x, y, w, color); 405 | fb_gfx_drawFastHLine(&fb, x, y+h-1, w, color); 406 | fb_gfx_drawFastVLine(&fb, x, y, h, color); 407 | fb_gfx_drawFastVLine(&fb, x+w-1, y, h, color); 408 | #if 0 409 | // landmark 410 | int x0, y0, j; 411 | for (j = 0; j < 10; j+=2) { 412 | x0 = (int)boxes->landmark[i].landmark_p[j]; 413 | y0 = (int)boxes->landmark[i].landmark_p[j+1]; 414 | fb_gfx_fillRect(&fb, x0, y0, 3, 3, color); 415 | } 416 | #endif 417 | } 418 | } 419 | 420 | static int run_face_recognition(dl_matrix3du_t *image_matrix, box_array_t *net_boxes){ 421 | dl_matrix3du_t *aligned_face = NULL; 422 | int matched_id = 0; 423 | 424 | aligned_face = dl_matrix3du_alloc(1, FACE_WIDTH, FACE_HEIGHT, 3); 425 | if(!aligned_face){ 426 | Serial.println("Could not allocate face recognition buffer"); 427 | return matched_id; 428 | } 429 | if (align_face(net_boxes, image_matrix, aligned_face) == ESP_OK){ 430 | if (is_enrolling == 1){ 431 | int8_t left_sample_face = enroll_face(&id_list, aligned_face); 432 | 433 | if(left_sample_face == (ENROLL_CONFIRM_TIMES - 1)){ 434 | Serial.printf("Enrolling Face ID: %d\n", id_list.tail); 435 | } 436 | Serial.printf("Enrolling Face ID: %d sample %d\n", id_list.tail, ENROLL_CONFIRM_TIMES - left_sample_face); 437 | rgb_printf(image_matrix, FACE_COLOR_CYAN, "ID[%u] Sample[%u]", id_list.tail, ENROLL_CONFIRM_TIMES - left_sample_face); 438 | if (left_sample_face == 0){ 439 | is_enrolling = 0; 440 | Serial.printf("Enrolled Face ID: %d\n", id_list.tail); 441 | } 442 | } else { 443 | matched_id = recognize_face(&id_list, aligned_face); 444 | if (matched_id >= 0) { 445 | Serial.printf("Match Face ID: %u\n", matched_id); 446 | rgb_printf(image_matrix, FACE_COLOR_GREEN, "Hello Subject %u", matched_id); 447 | } else { 448 | Serial.println("No Match Found"); 449 | rgb_print(image_matrix, FACE_COLOR_RED, "Intruder Alert!"); 450 | matched_id = -1; 451 | } 452 | } 453 | } else { 454 | Serial.println("Face Not Aligned"); 455 | //rgb_print(image_matrix, FACE_COLOR_YELLOW, "Human Detected"); 456 | } 457 | 458 | dl_matrix3du_free(aligned_face); 459 | return matched_id; 460 | } 461 | 462 | static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){ 463 | jpg_chunking_t *j = (jpg_chunking_t *)arg; 464 | if(!index){ 465 | j->len = 0; 466 | } 467 | if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){ 468 | return 0; 469 | } 470 | j->len += len; 471 | return len; 472 | } 473 | 474 | static esp_err_t capture_handler(httpd_req_t *req){ 475 | camera_fb_t * fb = NULL; 476 | esp_err_t res = ESP_OK; 477 | int64_t fr_start = esp_timer_get_time(); 478 | 479 | fb = esp_camera_fb_get(); 480 | if (!fb) { 481 | Serial.println("Camera capture failed"); 482 | httpd_resp_send_500(req); 483 | return ESP_FAIL; 484 | } 485 | 486 | httpd_resp_set_type(req, "image/jpeg"); 487 | httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); //antwoord header an capture pagina 488 | 489 | size_t out_len, out_width, out_height; 490 | uint8_t * out_buf; 491 | bool s; 492 | bool detected = false; 493 | int face_id = 0; 494 | if(!detection_enabled || fb->width > 400){ 495 | size_t fb_len = 0; 496 | if(fb->format == PIXFORMAT_JPEG){ 497 | fb_len = fb->len; 498 | res = httpd_resp_send(req, (const char *)fb->buf, fb->len); 499 | } else { 500 | jpg_chunking_t jchunk = {req, 0}; 501 | res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL; 502 | httpd_resp_send_chunk(req, NULL, 0); 503 | fb_len = jchunk.len; 504 | } 505 | esp_camera_fb_return(fb); 506 | int64_t fr_end = esp_timer_get_time(); 507 | Serial.printf("JPG: %uB %ums\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000)); 508 | return res; 509 | } 510 | 511 | dl_matrix3du_t *image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3); 512 | if (!image_matrix) { 513 | esp_camera_fb_return(fb); 514 | Serial.println("dl_matrix3du_alloc failed"); 515 | httpd_resp_send_500(req); 516 | return ESP_FAIL; 517 | } 518 | 519 | out_buf = image_matrix->item; 520 | out_len = fb->width * fb->height * 3; 521 | out_width = fb->width; 522 | out_height = fb->height; 523 | 524 | s = fmt2rgb888(fb->buf, fb->len, fb->format, out_buf); 525 | esp_camera_fb_return(fb); 526 | if(!s){ 527 | dl_matrix3du_free(image_matrix); 528 | Serial.println("to rgb888 failed"); 529 | httpd_resp_send_500(req); 530 | return ESP_FAIL; 531 | } 532 | 533 | box_array_t *net_boxes = face_detect(image_matrix, &mtmn_config); 534 | 535 | if (net_boxes){ 536 | detected = true; 537 | if(recognition_enabled){ 538 | face_id = run_face_recognition(image_matrix, net_boxes); 539 | } 540 | draw_face_boxes(image_matrix, net_boxes, face_id); 541 | free(net_boxes->box); 542 | free(net_boxes->landmark); 543 | free(net_boxes); 544 | } 545 | 546 | jpg_chunking_t jchunk = {req, 0}; 547 | s = fmt2jpg_cb(out_buf, out_len, out_width, out_height, PIXFORMAT_RGB888, 90, jpg_encode_stream, &jchunk); 548 | dl_matrix3du_free(image_matrix); 549 | if(!s){ 550 | Serial.println("JPEG compression failed"); 551 | return ESP_FAIL; 552 | } 553 | 554 | int64_t fr_end = esp_timer_get_time(); 555 | Serial.printf("FACE: %uB %ums %s%d\n", (uint32_t)(jchunk.len), (uint32_t)((fr_end - fr_start)/1000), detected?"DETECTED ":"", face_id); 556 | return res; 557 | } 558 | 559 | static esp_err_t stream_handler(httpd_req_t *req){ 560 | camera_fb_t * fb = NULL; 561 | esp_err_t res = ESP_OK; 562 | size_t _jpg_buf_len = 0; 563 | uint8_t * _jpg_buf = NULL; 564 | char * part_buf[64]; 565 | dl_matrix3du_t *image_matrix = NULL; 566 | bool detected = false; 567 | int face_id = 0; 568 | int64_t fr_start = 0; 569 | int64_t fr_ready = 0; 570 | int64_t fr_face = 0; 571 | int64_t fr_recognize = 0; 572 | int64_t fr_encode = 0; 573 | 574 | static int64_t last_frame = 0; 575 | if(!last_frame) { 576 | last_frame = esp_timer_get_time(); 577 | } 578 | 579 | res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); 580 | if(res != ESP_OK){ 581 | return res; 582 | } 583 | 584 | while(true){ 585 | detected = false; 586 | face_id = 0; 587 | fb = esp_camera_fb_get(); 588 | if (!fb) { 589 | Serial.println("Camera capture failed"); 590 | res = ESP_FAIL; 591 | } else { 592 | fr_start = esp_timer_get_time(); 593 | fr_ready = fr_start; 594 | fr_face = fr_start; 595 | fr_encode = fr_start; 596 | fr_recognize = fr_start; 597 | if(!detection_enabled || fb->width > 400){ 598 | if(fb->format != PIXFORMAT_JPEG){ 599 | bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); 600 | esp_camera_fb_return(fb); 601 | fb = NULL; 602 | if(!jpeg_converted){ 603 | Serial.println("JPEG compression failed"); 604 | res = ESP_FAIL; 605 | } 606 | } else { 607 | _jpg_buf_len = fb->len; 608 | _jpg_buf = fb->buf; 609 | } 610 | } else { 611 | 612 | image_matrix = dl_matrix3du_alloc(1, fb->width, fb->height, 3); 613 | 614 | if (!image_matrix) { 615 | Serial.println("dl_matrix3du_alloc failed"); 616 | res = ESP_FAIL; 617 | } else { 618 | if(!fmt2rgb888(fb->buf, fb->len, fb->format, image_matrix->item)){ 619 | Serial.println("fmt2rgb888 failed"); 620 | res = ESP_FAIL; 621 | } else { 622 | fr_ready = esp_timer_get_time(); 623 | box_array_t *net_boxes = NULL; 624 | if(detection_enabled){ 625 | net_boxes = face_detect(image_matrix, &mtmn_config); 626 | } 627 | fr_face = esp_timer_get_time(); 628 | fr_recognize = fr_face; 629 | if (net_boxes || fb->format != PIXFORMAT_JPEG){ 630 | if(net_boxes){ 631 | detected = true; 632 | if(recognition_enabled){ 633 | face_id = run_face_recognition(image_matrix, net_boxes); 634 | } 635 | fr_recognize = esp_timer_get_time(); 636 | draw_face_boxes(image_matrix, net_boxes, face_id); 637 | free(net_boxes->box); 638 | free(net_boxes->landmark); 639 | free(net_boxes); 640 | } 641 | if(!fmt2jpg(image_matrix->item, fb->width*fb->height*3, fb->width, fb->height, PIXFORMAT_RGB888, 90, &_jpg_buf, &_jpg_buf_len)){ 642 | Serial.println("fmt2jpg failed"); 643 | res = ESP_FAIL; 644 | } 645 | esp_camera_fb_return(fb); 646 | fb = NULL; 647 | } else { 648 | _jpg_buf = fb->buf; 649 | _jpg_buf_len = fb->len; 650 | } 651 | fr_encode = esp_timer_get_time(); 652 | } 653 | dl_matrix3du_free(image_matrix); 654 | } 655 | } 656 | } 657 | if(res == ESP_OK){ 658 | size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); 659 | res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); 660 | } 661 | if(res == ESP_OK){ 662 | res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); 663 | } 664 | if(res == ESP_OK){ 665 | res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); 666 | } 667 | if(fb){ 668 | esp_camera_fb_return(fb); 669 | fb = NULL; 670 | _jpg_buf = NULL; 671 | } else if(_jpg_buf){ 672 | free(_jpg_buf); 673 | _jpg_buf = NULL; 674 | } 675 | if(res != ESP_OK){ 676 | break; 677 | } 678 | int64_t fr_end = esp_timer_get_time(); 679 | 680 | int64_t ready_time = (fr_ready - fr_start)/1000; 681 | int64_t face_time = (fr_face - fr_ready)/1000; 682 | int64_t recognize_time = (fr_recognize - fr_face)/1000; 683 | int64_t encode_time = (fr_encode - fr_recognize)/1000; 684 | int64_t process_time = (fr_encode - fr_start)/1000; 685 | 686 | int64_t frame_time = fr_end - last_frame; 687 | last_frame = fr_end; 688 | frame_time /= 1000; 689 | uint32_t avg_frame_time = ra_filter_run(&ra_filter, frame_time); 690 | Serial.printf("MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps), %u+%u+%u+%u=%u %s%d\n", 691 | (uint32_t)(_jpg_buf_len), 692 | (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, 693 | avg_frame_time, 1000.0 / avg_frame_time, 694 | (uint32_t)ready_time, (uint32_t)face_time, (uint32_t)recognize_time, (uint32_t)encode_time, (uint32_t)process_time, 695 | (detected)?"DETECTED ":"", face_id 696 | ); 697 | } 698 | 699 | last_frame = 0; 700 | return res; 701 | } 702 | 703 | static esp_err_t cmd_handler(httpd_req_t *req){ 704 | char* buf; 705 | size_t buf_len; 706 | char variable[32] = {0,}; 707 | char value[32] = {0,}; 708 | 709 | buf_len = httpd_req_get_url_query_len(req) + 1; 710 | if (buf_len > 1) { 711 | buf = (char*)malloc(buf_len); 712 | if(!buf){ 713 | httpd_resp_send_500(req); 714 | return ESP_FAIL; 715 | } 716 | if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { 717 | if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) == ESP_OK && 718 | httpd_query_key_value(buf, "val", value, sizeof(value)) == ESP_OK) { 719 | } else { 720 | free(buf); 721 | httpd_resp_send_404(req); 722 | return ESP_FAIL; 723 | } 724 | } else { 725 | free(buf); 726 | httpd_resp_send_404(req); 727 | return ESP_FAIL; 728 | } 729 | free(buf); 730 | } else { 731 | httpd_resp_send_404(req); 732 | return ESP_FAIL; 733 | } 734 | 735 | int val = atoi(value); 736 | sensor_t * s = esp_camera_sensor_get(); 737 | int res = 0; 738 | 739 | if(!strcmp(variable, "framesize")) { 740 | 741 | if(s->pixformat == PIXFORMAT_JPEG) res = s->set_framesize(s, (framesize_t)val); 742 | } 743 | else if(!strcmp(variable, "quality")) res = s->set_quality(s, val); 744 | 745 | else if(!strcmp(variable, "CamTilt")) { 746 | ValCamTilt = val; 747 | Serial.print("ValCamTilt du cpp : "); 748 | Serial.println(ValCamTilt); 749 | } 750 | else if(!strcmp(variable, "CamRotate")) { 751 | ValCamRotate = val; 752 | Serial.print("ValCamRotate du cpp : "); 753 | Serial.println(ValCamRotate); 754 | } 755 | else if(!strcmp(variable, "CamFake")) { 756 | ValCamFake = val; 757 | Serial.print("ValCamFake du cpp : "); 758 | Serial.println(ValCamFake); 759 | } 760 | else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val); 761 | else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val); 762 | else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val); 763 | else if(!strcmp(variable, "gainceiling")) res = s->set_gainceiling(s, (gainceiling_t)val); 764 | else if(!strcmp(variable, "colorbar")) res = s->set_colorbar(s, val); 765 | else if(!strcmp(variable, "awb")) res = s->set_whitebal(s, val); 766 | else if(!strcmp(variable, "agc")) res = s->set_gain_ctrl(s, val); 767 | else if(!strcmp(variable, "aec")) res = s->set_exposure_ctrl(s, val); 768 | else if(!strcmp(variable, "hmirror")) res = s->set_hmirror(s, val); 769 | else if(!strcmp(variable, "vflip")) res = s->set_vflip(s, val); 770 | else if(!strcmp(variable, "awb_gain")) res = s->set_awb_gain(s, val); 771 | else if(!strcmp(variable, "agc_gain")) res = s->set_agc_gain(s, val); 772 | else if(!strcmp(variable, "aec_value")) res = s->set_aec_value(s, val); 773 | else if(!strcmp(variable, "aec2")) res = s->set_aec2(s, val); 774 | else if(!strcmp(variable, "dcw")) res = s->set_dcw(s, val); 775 | else if(!strcmp(variable, "bpc")) res = s->set_bpc(s, val); 776 | else if(!strcmp(variable, "wpc")) res = s->set_wpc(s, val); 777 | else if(!strcmp(variable, "raw_gma")) res = s->set_raw_gma(s, val); 778 | else if(!strcmp(variable, "lenc")) res = s->set_lenc(s, val); 779 | else if(!strcmp(variable, "special_effect")) res = s->set_special_effect(s, val); 780 | else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val); 781 | else if(!strcmp(variable, "ae_level")){ 782 | 783 | Serial.printf("var="); 784 | Serial.printf(variable); 785 | 786 | 787 | res = s->set_ae_level(s, val); 788 | Serial.printf(" valvar="); 789 | Serial.print(val); 790 | 791 | 792 | } 793 | else if(!strcmp(variable, "face_detect")) { 794 | detection_enabled = val; 795 | if(!detection_enabled) { 796 | recognition_enabled = 0; 797 | } 798 | } 799 | else if(!strcmp(variable, "face_enroll")) is_enrolling = val; 800 | else if(!strcmp(variable, "face_recognize")) { 801 | recognition_enabled = val; 802 | if(recognition_enabled){ 803 | detection_enabled = val; 804 | } 805 | } 806 | else { 807 | res = -1; 808 | } 809 | 810 | if(res){ 811 | return httpd_resp_send_500(req); 812 | } 813 | 814 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 815 | return httpd_resp_send(req, NULL, 0); 816 | } 817 | 818 | static esp_err_t status_handler(httpd_req_t *req){ 819 | static char json_response[1024]; 820 | 821 | sensor_t * s = esp_camera_sensor_get(); 822 | char * p = json_response; 823 | *p++ = '{'; 824 | 825 | p+=sprintf(p, "\"framesize\":%u,", s->status.framesize); 826 | p+=sprintf(p, "\"quality\":%u,", s->status.quality); 827 | 828 | p+=sprintf(p, "\"CamTilt\":%u,", ValCamTilt); 829 | p+=sprintf(p, "\"CamRotate\":%u,", ValCamRotate); 830 | p+=sprintf(p, "\"CamFake\":%u,", ValCamFake); 831 | 832 | p+=sprintf(p, "\"brightness\":%d,", s->status.brightness); 833 | p+=sprintf(p, "\"contrast\":%d,", s->status.contrast); 834 | p+=sprintf(p, "\"saturation\":%d,", s->status.saturation); 835 | p+=sprintf(p, "\"special_effect\":%u,", s->status.special_effect); 836 | p+=sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode); 837 | p+=sprintf(p, "\"awb\":%u,", s->status.awb); 838 | p+=sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain); 839 | p+=sprintf(p, "\"aec\":%u,", s->status.aec); 840 | p+=sprintf(p, "\"aec2\":%u,", s->status.aec2); 841 | p+=sprintf(p, "\"ae_level\":%d,", s->status.ae_level); 842 | p+=sprintf(p, "\"aec_value\":%u,", s->status.aec_value); 843 | p+=sprintf(p, "\"agc\":%u,", s->status.agc); 844 | p+=sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain); 845 | p+=sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling); 846 | p+=sprintf(p, "\"bpc\":%u,", s->status.bpc); 847 | p+=sprintf(p, "\"wpc\":%u,", s->status.wpc); 848 | p+=sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma); 849 | p+=sprintf(p, "\"lenc\":%u,", s->status.lenc); 850 | p+=sprintf(p, "\"vflip\":%u,", s->status.vflip); 851 | p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror); 852 | p+=sprintf(p, "\"dcw\":%u,", s->status.dcw); 853 | p+=sprintf(p, "\"colorbar\":%u,", s->status.colorbar); 854 | p+=sprintf(p, "\"face_detect\":%u,", detection_enabled); 855 | p+=sprintf(p, "\"face_enroll\":%u,", is_enrolling); 856 | p+=sprintf(p, "\"face_recognize\":%u", recognition_enabled); 857 | *p++ = '}'; 858 | *p++ = 0; 859 | httpd_resp_set_type(req, "application/json"); 860 | httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); 861 | return httpd_resp_send(req, json_response, strlen(json_response)); 862 | } 863 | 864 | static esp_err_t index_handler(httpd_req_t *req){ 865 | httpd_resp_set_type(req, "text/html"); 866 | Serial.printf("webpage loading"); 867 | 868 | // httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 869 | return httpd_resp_send(req, (const char *)INDEX2_HTML, strlen(INDEX2_HTML)); 870 | } 871 | 872 | 873 | void startCameraServer(){ 874 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 875 | 876 | httpd_uri_t index_uri = { 877 | .uri = "/", 878 | .method = HTTP_GET, 879 | .handler = index_handler, 880 | .user_ctx = NULL 881 | }; 882 | 883 | httpd_uri_t status_uri = { 884 | .uri = "/status", 885 | .method = HTTP_GET, 886 | .handler = status_handler, 887 | .user_ctx = NULL 888 | }; 889 | 890 | httpd_uri_t cmd_uri = { 891 | .uri = "/control", 892 | .method = HTTP_GET, 893 | .handler = cmd_handler, 894 | .user_ctx = NULL 895 | }; 896 | 897 | httpd_uri_t capture_uri = { 898 | .uri = "/capture", //v12345vtm 899 | .method = HTTP_GET, 900 | .handler = capture_handler, 901 | .user_ctx = NULL 902 | }; 903 | 904 | httpd_uri_t stream_uri = { 905 | .uri = "/stream", 906 | .method = HTTP_GET, 907 | .handler = stream_handler, 908 | .user_ctx = NULL 909 | }; 910 | 911 | 912 | ra_filter_init(&ra_filter, 20); 913 | 914 | mtmn_config.min_face = 80; 915 | mtmn_config.pyramid = 0.7; 916 | mtmn_config.p_threshold.score = 0.6; 917 | mtmn_config.p_threshold.nms = 0.7; 918 | mtmn_config.r_threshold.score = 0.7; 919 | mtmn_config.r_threshold.nms = 0.7; 920 | mtmn_config.r_threshold.candidate_number = 4; 921 | mtmn_config.o_threshold.score = 0.7; 922 | mtmn_config.o_threshold.nms = 0.4; 923 | mtmn_config.o_threshold.candidate_number = 1; 924 | 925 | face_id_init(&id_list, FACE_ID_SAVE_NUMBER, ENROLL_CONFIRM_TIMES); 926 | 927 | Serial.printf("Starting web server on port: '%d'\n", config.server_port); 928 | if (httpd_start(&camera_httpd, &config) == ESP_OK) { 929 | httpd_register_uri_handler(camera_httpd, &index_uri); 930 | httpd_register_uri_handler(camera_httpd, &cmd_uri); 931 | httpd_register_uri_handler(camera_httpd, &status_uri); 932 | httpd_register_uri_handler(camera_httpd, &capture_uri); 933 | } 934 | 935 | config.server_port += 1; 936 | config.ctrl_port += 1; 937 | 938 | config.server_port =9601;//stream port + also change this in the html-source in this file 939 | // config.ctrl_port =8081; 940 | 941 | Serial.printf("Starting stream server on stream port: '%d'\n", config.server_port); 942 | if (httpd_start(&stream_httpd, &config) == ESP_OK) { 943 | httpd_register_uri_handler(stream_httpd, &stream_uri); 944 | } 945 | } 946 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP-CAM-Ai-Thinker-Remote-Control-Tilt-and-Yaw 2 | Remote control Tilt and Yaw the cam, with manual command on WebServer page 3 | 4 | Because i need 2 Gpios, the SdCard dont work anymore. 5 | There is a "servofake" declared, i am a beginer and its the only way i found to succeed to make working the 2nd servo (ServoRotate), dont ask me why, i dont know...perhaps a MUX problem or declaration output need on Gpios, but i havent succeed in programming the MUX for declare Gpios to OUTPUT... 6 | 7 | I would be happy if someday a confirm programmer solved this beginner mistake :) 8 | 9 | --------------------------------------------------------------------------------------------------------------------------------- 10 | 11 | Remix of v12345vtm (thanks!): 12 | 13 | https://www.youtube.com/watch?v=PhrSWB4qWXg&t=16s 14 | 15 | https://github.com/v12345vtm/esp32-cam-webserver-arduino-simplified-arduino-html 16 | 17 | ---------------------------------------------------------------------------------------------------------------------------------- 18 | 19 | If you have a 3D Printer, i remix a tilt and yaw platform with 2 existing project : 20 | 21 | https://www.thingiverse.com/thing:3579507 22 | 23 | 24 | Enjoy! 25 | ;) 26 | 27 | --------------------------------------------------------------------------------