Dự án ESP32 Webcam gọi Video Call Skype / Zoom hoặc làm Camera giám sát
Trong dự án ESP32 Webcam này, IoTZone sẽ hướng dẫn bạn cách sử dụng ESP32 Cam để làm Webcam trong các cuộc gọi video trên Skype/Zoom hoặc thậm chí làm Camera giám sát trong nhà. Cùng theo dõi nhé!
ESP32 Webcam gọi Video Call Skype/Zoom
Với demo dự án này, bạn có thể ứng dụng trong Linux OS và sử dụng thiết bị v4l2loopback. Bạn có thể sử dụng với tất cả các dự án Deep Learning trên ESP32 Camera.
Bạn có thể sử dụng filter trước khi chuyển hình ảnh sang ứng dụng Zoom/Skype. Ví dụ, mình sử dụng filter 2 dấu chấm tròn đỏ để che mắt trong demo dự án này:
Chuẩn bị phần cứng & phần mềm
Về phần cứng, bạn cần:
- Máy tính chạy trên Linux
- ESP32 Camera
Về phần mềm, bạn cần cài sẵn các phần mềm sau:
- sudo apt install python3-pip
- pip3 install git+https://github.com/antmicro/python3-v4l2
- pip3 install –user cv2
- pip3 install –user numpy
- git clone https://github.com/umlaeute/v4l2loopback.git (sau đó thực hiện && sudo make install)
Bạn có thể gõ Teminal sau để tạo thiết bị ảo /dev/video6:
sudo modprobe v4l2loopback video_nr=6
Bạn có thể dùng dòng lệnh sau để kiểm tra:
ls /dev/video*
Các ứng dụng Zoom/Skype sẽ đọc thiết bị dev/video6 mà bạn đã tạo để lấy source hình ảnh. Dưới đây là hình minh họa cách giao tiếp giữa ESP32 Webcam và PC (có tên là TCP Server / Client):
ESP32 WebCam TCP Server >> PC TCP Client /dev/video >> Skype /dev/video
Nạp code
Bạn nạp code sau vào mạch ESP32:
#include "esp_camera.h" #include <WiFi.h> #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 WiFiServer server(8088); bool connected = false; WiFiClient live_client; void configCamera(){ 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_QVGA; config.jpeg_quality = 9; config.fb_count = 1; esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } } //continue sending camera frame void liveCam(WiFiClient &client){ //capture a frame camera_fb_t * fb = esp_camera_fb_get(); if (!fb) { Serial.println("Frame buffer could not be acquired"); return; } client.write(fb->buf, fb->len); client.flush(); client.print("\r\n"); client.flush(); //return the frame buffer back to be reused esp_camera_fb_return(fb); } void setup() { Serial.begin(115200); WiFi.begin("I3.41", "xxx"); Serial.println(""); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); String IP = WiFi.localIP().toString(); Serial.println("IP address: " + IP); server.begin(); configCamera(); } void loop() { WiFiClient client = server.available(); if (client.connected()) { live_client = client; connected = true; } if(live_client.connected() == false) { connected = false; } if(connected) { liveCam(live_client); } }
Bạn hãy nạp code trên vào mạch ESP32 và kiểm tra kết quả nhé!
ESP32 WebCam phát video trực tuyến – làm Camera giám sát
Trong dự án này, bạn có thể sử dụng ESP32 Camera làm một Web Server phát video trực tuyến, và bạn có thể truy cập vào xem video đó bằng bất kỳ thiết bị nào trong mạng của mình.
Chuẩn bị phần cứng & phần mềm
Về phần cứng, bạn cần chuẩn bị:
- ESP32 Camera
- FTDI programmer
- Dây jumper female to female
- Nguồn 5V cho ESP32 Webcam
Về phần mềm, bạn có thể cài đặt tiện ích ESP32 trong phần mềm Arduino IDE trước nhé!
Chương trình code mẫu
#include "esp_camera.h" #include <WiFi.h> #include "esp_timer.h" #include "img_converters.h" #include "Arduino.h" #include "fb_gfx.h" #include "soc/soc.h" //disable brownout problems #include "soc/rtc_cntl_reg.h" //disable brownout problems #include "esp_http_server.h" //Replace with your network credentials const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; #define PART_BOUNDARY "123456789000000000000987654321" // This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM #define CAMERA_MODEL_AI_THINKER //#define CAMERA_MODEL_M5STACK_PSRAM //#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM // Not tested with this model //#define CAMERA_MODEL_WROVER_KIT #if defined(CAMERA_MODEL_WROVER_KIT) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 21 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 19 #define Y4_GPIO_NUM 18 #define Y3_GPIO_NUM 5 #define Y2_GPIO_NUM 4 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 #elif defined(CAMERA_MODEL_M5STACK_PSRAM) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 25 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 32 #define VSYNC_GPIO_NUM 22 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM) #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM 15 #define XCLK_GPIO_NUM 27 #define SIOD_GPIO_NUM 25 #define SIOC_GPIO_NUM 23 #define Y9_GPIO_NUM 19 #define Y8_GPIO_NUM 36 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 39 #define Y5_GPIO_NUM 5 #define Y4_GPIO_NUM 34 #define Y3_GPIO_NUM 35 #define Y2_GPIO_NUM 17 #define VSYNC_GPIO_NUM 22 #define HREF_GPIO_NUM 26 #define PCLK_GPIO_NUM 21 #elif defined(CAMERA_MODEL_AI_THINKER) #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 #else #error "Camera model not selected" #endif static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; httpd_handle_t stream_httpd = NULL; static esp_err_t stream_handler(httpd_req_t *req){ camera_fb_t * fb = NULL; esp_err_t res = ESP_OK; size_t _jpg_buf_len = 0; uint8_t * _jpg_buf = NULL; char * part_buf[64]; res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); if(res != ESP_OK){ return res; } while(true){ fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); res = ESP_FAIL; } else { if(fb->width > 400){ if(fb->format != PIXFORMAT_JPEG){ bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); esp_camera_fb_return(fb); fb = NULL; if(!jpeg_converted){ Serial.println("JPEG compression failed"); res = ESP_FAIL; } } else { _jpg_buf_len = fb->len; _jpg_buf = fb->buf; } } } if(res == ESP_OK){ size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); } if(res == ESP_OK){ res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); } if(res == ESP_OK){ res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); } if(fb){ esp_camera_fb_return(fb); fb = NULL; _jpg_buf = NULL; } else if(_jpg_buf){ free(_jpg_buf); _jpg_buf = NULL; } if(res != ESP_OK){ break; } //Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len)); } return res; } void startCameraServer(){ httpd_config_t config = HTTPD_DEFAULT_CONFIG(); config.server_port = 80; httpd_uri_t index_uri = { .uri = "/", .method = HTTP_GET, .handler = stream_handler, .user_ctx = NULL }; //Serial.printf("Starting web server on port: '%d'\n", config.server_port); if (httpd_start(&stream_httpd, &config) == ESP_OK) { httpd_register_uri_handler(stream_httpd, &index_uri); } } void setup() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector Serial.begin(115200); Serial.setDebugOutput(false); 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; if(psramFound()){ config.frame_size = FRAMESIZE_UXGA; config.jpeg_quality = 10; config.fb_count = 2; } else { config.frame_size = FRAMESIZE_SVGA; config.jpeg_quality = 12; config.fb_count = 1; } // Camera init esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } // Wi-Fi connection WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.print("Camera Stream Ready! Go to: http://"); Serial.print(WiFi.localIP()); // Start streaming web server startCameraServer(); } void loop() { delay(1); }
Trước khi nạp code, bạn nhớ thay đổi thông tin xác thực mạng WiFi của mình nhé, tại dòng code sau:
const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Sau đó, bạn chọn đúng module Camera của mình. Hiện tại, mình đang dùng mô hình AI_Thinker:
Nếu bạn đang dùng cùng module máy ảnh, thì bạn không cần thay đổi gì về code. Nếu không, bạn cần chỉnh sửa đoạn code sau để phù hợp:
#define CAMERA_MODEL_AI_THINKER
Kết nối phần cứng và nạp code
Kết nối mạch ESP32 Webcam với FTDI theo sơ đồ sau:
Lưu ý rằng bạn cần phải kết nối GPIO 0 với chân GND, để có thể upload code lên. Sơ đồ kết nối như sau:
ESP32 Camera | FTDI |
GND | GND |
5V | VCC (5V) |
U0R | TX |
U0T | RX |
GPIO 0 | GND |
Để upload code, bạn làm như sau:
- Chọn Tools >> Board và click vào AI-Thinker ESP32 CAM
- Vào Tools >> Port và chọn cổng COM đã kết nối với ESP32
- Click vào nút upload code
- Trên cửa sổ Serial xuất hiện các dấu chấm, bạn nhấn vào nút reset (RST) trên mạch ESP32 Webcam
Sau đó, bạn chờ vài giây để upload code thành công.
Lấy địa chỉ IP
Sau khi upload code, bạn phải ngắt kết nối GPIO 0 từ GND và mở Serial Monitor ở tốc độ 115200, nhấn nút RST trên ESP32 Webcam.
Sau đó, địa chỉ IP ESP32 được in trên Serial Monitor:
Truy cập vào Web Server truyền phát video
Bây giờ, bạn có thể dùng laptop hoặc điện thoại, máy tính bảng để truy cập vào xem video đang được truyền phát trực tiếp:
- Mở trình duyệt Internet
- Nhập địa chỉ ESP32 Webcam vào thanh tìm kiếm
- Nhấn OK và chờ loading, một trang web có luồng video từ ESP32 Camera sẽ hiển thị:
Lời kết
Đây là hướng dẫn cách làm dự án ESP32 Webcam cơ bản, bạn có thể nâng cấp nó lên thành những dự án nâng cao hơn, chẳng hạn như trợ lý ảo trong Smart home, dùng thêm mô hình máy ảnh và gắn ESP32 Webcam vào bên trong để làm thành một camera giám sát hiện đại:
Bạn hãy thử nhé! Chúc các bạn thành công!
IoTZone – Chuyên cung cấp thiết bị điện tử & tài liệu cho Makers
- Website: https://www.iotzone.vn/
- Fanpage: https://www.facebook.com/Iotzonemaker
- SDT: 0364174499
- Zalo: https://zalo.me/0364174499