本帖最后由 kylinpoet 于 2025-5-6 14:04 编辑
一、项目缘起:
ESP32 S3 + CAM 实在是低成本、高效的安防搭档。本项目通过在esp32上搭建http视频流服务,利用上位机实时获取摄像头的传递数据,进行识别。相应的数据处理在上位机完成。包括使用yolov5的预训练80分类数据进行人体识别,以及通过消息平台进行报警。
二、操作步骤:
1. 视频图传
在 dfrobot 的wiki库里找到并刷入,视频图传演示代码(点击访问),需要注意的是使用 Arduino 1.8版本刷入才行,Arduino 2 会输入失败(留待大佬解决)
- #include "esp_camera.h"
- #include <WiFi.h>
-
- //
- // WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality
- // Ensure ESP32 Wrover Module or other board with PSRAM is selected
- // Partial images will be transmitted if image exceeds buffer size
- //
- // You must select partition scheme from the board menu that has at least 3MB APP space.
- // Face Recognition is DISABLED for ESP32 and ESP32-S2, because it takes up from 15
- // seconds to process single frame. Face Detection is ENABLED if PSRAM is enabled as well
-
- #define PWDN_GPIO_NUM -1
- #define RESET_GPIO_NUM -1
- #define XCLK_GPIO_NUM 5
- #define Y9_GPIO_NUM 4
- #define Y8_GPIO_NUM 6
- #define Y7_GPIO_NUM 7
- #define Y6_GPIO_NUM 14
- #define Y5_GPIO_NUM 17
- #define Y4_GPIO_NUM 21
- #define Y3_GPIO_NUM 18
- #define Y2_GPIO_NUM 16
- #define VSYNC_GPIO_NUM 1
- #define HREF_GPIO_NUM 2
- #define PCLK_GPIO_NUM 15
- #define SIOD_GPIO_NUM 8
- #define SIOC_GPIO_NUM 9
-
- // ===========================
- // Enter your WiFi credentials
- // ===========================
- const char *ssid = "**********";
- const char *password = "**********";
-
- void startCameraServer();
- void setupLedFlash(int pin);
-
- void setup() {
- Serial.begin(115200);
- Serial.setDebugOutput(true);
- Serial.println();
-
- 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_sccb_sda = SIOD_GPIO_NUM;
- config.pin_sccb_scl = SIOC_GPIO_NUM;
- config.pin_pwdn = PWDN_GPIO_NUM;
- config.pin_reset = RESET_GPIO_NUM;
- config.xclk_freq_hz = 20000000;
- config.frame_size = FRAMESIZE_UXGA;
- config.pixel_format = PIXFORMAT_JPEG; // for streaming
- //config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
- config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
- config.fb_location = CAMERA_FB_IN_PSRAM;
- config.jpeg_quality = 12;
- config.fb_count = 1;
-
- // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
- // for larger pre-allocated frame buffer.
- if (config.pixel_format == PIXFORMAT_JPEG) {
- if (psramFound()) {
- config.jpeg_quality = 10;
- config.fb_count = 2;
- config.grab_mode = CAMERA_GRAB_LATEST;
- } else {
- // Limit the frame size when PSRAM is not available
- config.frame_size = FRAMESIZE_SVGA;
- config.fb_location = CAMERA_FB_IN_DRAM;
- }
- } else {
- // Best option for face detection/recognition
- config.frame_size = FRAMESIZE_240X240;
- #if CONFIG_IDF_TARGET_ESP32S3
- config.fb_count = 2;
- #endif
- }
-
- #if defined(CAMERA_MODEL_ESP_EYE)
- pinMode(13, INPUT_PULLUP);
- pinMode(14, INPUT_PULLUP);
- #endif
-
- // 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;
- }
-
- sensor_t *s = esp_camera_sensor_get();
- // initial sensors are flipped vertically and colors are a bit saturated
- if (s->id.PID == OV3660_PID) {
- s->set_vflip(s, 1); // flip it back
- s->set_brightness(s, 1); // up the brightness just a bit
- s->set_saturation(s, -2); // lower the saturation
- }
- // drop down frame size for higher initial frame rate
- if (config.pixel_format == PIXFORMAT_JPEG) {
- s->set_framesize(s, FRAMESIZE_QVGA);
- }
-
- #if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
- s->set_vflip(s, 1);
- s->set_hmirror(s, 1);
- #endif
-
- #if defined(CAMERA_MODEL_ESP32S3_EYE)
- s->set_vflip(s, 1);
- #endif
-
- // Setup LED FLash if LED pin is defined in camera_pins.h
- #if defined(LED_GPIO_NUM)
- setupLedFlash(LED_GPIO_NUM);
- #endif
-
- WiFi.begin(ssid, password);
- WiFi.setSleep(false);
-
- Serial.print("WiFi connecting");
- while (WiFi.status() != WL_CONNECTED) {
- delay(500);
- Serial.print(".");
- }
- Serial.println("");
- Serial.println("WiFi connected");
-
- startCameraServer();
-
- Serial.print("Camera Ready! Use 'http://");
- Serial.print(WiFi.localIP());
- Serial.println("' to connect");
- }
-
- void loop() {
- // Do nothing. Everything is done in another task by the web server
- delay(10000);
- }
复制代码
刷入成功后,我们会在串口中看到wifi连接信息。访问相应地址后,我们会看到以下内容。
 
这里要注意下,网址首页打开是视频流的形式,访问 /capture 子页面,是抓取的实时图片。
我们在上位机上用的就是这个子页面。


2. 基于电脑yoloV5实现
yoloV5的代码,我们主要参考这里的文档(点击访问),但要做一些改变:

这里主要是判断是否检测到人类,以及为了正确阈值,设置的置信度。
另外还要增加发送消息预警的函数,这里是使用免费微信消息推送服务:server酱,当然你可以使用其它类似服务:

以下是识别到人类活动后的消息推送:

参考代码如下:
- import cv2
- import torch
- import time
- from yolov5 import YOLOv5
- import urllib.request
- import numpy as np
- import requests
- from img_upload import img_upload
-
- url = 'http://192.168.1.43:80/capture' # ESP32-CAM的IP地址
- sendkey = ''
- # Load the YOLOv5 model
- model_path = "d:/yolov5s.pt" # 模型路径
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- model = YOLOv5(model_path, device=device)
-
- last_alert_time = 0
- # 通过server酱发送微信消息推送
- def send_alert(title, img):
- url = f"https://sctapi.ftqq.com/{sendkey}.send"
- # 使用Markdown语法在desp中嵌入图片
- image_url = img_upload(img)
- desp = f"" # 图片需为可访问的URL
- payload = {
- "text": title,
- "desp": desp
- }
- response = requests.post(url, data=payload)
- return response.json()
-
-
- while True:
- img_resp = urllib.request.urlopen(url) # 从URL获取图像数据
- img_source = img_resp.read()
- imgnp = np.array(bytearray(img_source), dtype=np.uint8) # 将图像数据转换为NumPy数
- frame = cv2.imdecode(imgnp, -1) # 解码JPEG图像数据
-
- # 记录处理开始时间
- start_time = time.time()
-
- # 将帧传递给模型进行预测
- results = model.predict(frame, size=640) # 调整输入图像大小为640x640
- # 遍历检测结果
- for i, det in enumerate(results.pred[0]):
- if det is not None and len(det):
- det = det.unsqueeze(0)
- for *xyxy, conf, cls in det:
- # 获取类别名称和置信度
- class_name = results.names[int(cls)]
- confidence = float(conf)
- print(f"检测到类别: {class_name}, 置信度: {confidence:.2f}")
- if class_name == "person" and confidence > 0.5:
- current_time = time.time()
- # 检查是否距离上次报警超过5分钟(300秒)
- if current_time - last_alert_time >= 300: # 5分钟内只报一次警
- print(f"检测到人类活动,触发报警")
- send_alert('检测到人类活动,触发报警', img_source)
- last_alert_time = current_time # 更新上次报警时间
- # 如需保存结果,可写入文件
-
- # 获取预测结果并在图像上绘制
- annotated_frame = results.render() # YOLOv5库中的render方法
- # 记录处理结束时间
- end_time = time.time()
- # 计算帧率
- processing_time = end_time - start_time
- fps = 1 / processing_time
- # 在图像上绘制帧率
- # cv2.putText(annotated_frame[0], f"FPS: {fps:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
- # 提取可写副本
- img_output = annotated_frame[0].copy()
- cv2.putText(img_output, f"FPS: {fps:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
- # 显示带有预测结果的帧
- cv2.imshow("YOLOv5 Detection", annotated_frame[0])
- # 打印详细信息到终端
- print(f"Processed frame in {processing_time:.4f} seconds, FPS: {fps:.2f}")
- # 按下 'q' 键退出
- if cv2.waitKey(1) & 0xFF == ord('q'):
- break
- # 释放摄像头资源并关闭所有窗口
- # cap.release()
- cv2.destroyAllWindows()
复制代码
顺便提下,yoloV5的预训练分类如下展示,class 0 是 person,其它可查看以下部分分类信息。完整内容请自行查阅。

3. 实践过程中的几点思考:
这里重点说明下测试过程中,填的几个坑:

1). 如上图①所示,示例代码里的模型路径,需要使用绝对路径,否则默认寻找的路径是 %homepath% ,也就是说需要将 pt 文件拷贝到这个目录,也就用户目录如:C:\Users\你的用户名\yolov5s.pt。如果此路径没有这个文件,他会去 https://github.com/ultralytics/y ... oad/v7.0/yolov5s.pt 下载,而这个下载地址有时候是需要特殊网络的!!!
2). 如上图②所示,因为我装的是 GPU 版的torch!!!,这里应该是使用 'cuda:0',而不是实例代码的 'cuda'。
三、安装图

|