ESP32 CAM与YOLOV5的实时目标检测系统搭建指南
1. 为什么选择ESP32 CAM和YOLOV5?
如果你对物联网和AI感兴趣,想亲手做一个能“看懂”世界的智能设备,比如让摄像头自动识别门口的快递、监控花盆里植物的状态,或者做个能追踪宠物的玩具,那么ESP32 CAM和YOLOV5的组合绝对是你的“梦中情搭”。我折腾过不少方案,最后发现这套组合对于个人开发者和爱好者来说,性价比和可玩性都是顶级的。
先说说ESP32 CAM,这简直就是为物联网视觉应用而生的“小钢炮”。它核心是一颗ESP32芯片,自带Wi-Fi和蓝牙,最关键的是板上直接集成了一个OV2640摄像头模组(有些型号是OV7670),还有一颗PSRAM(伪静态随机存储器)。别小看这颗PSRAM,它能让摄像头处理更大尺寸、更清晰的照片,对于后续的目标检测至关重要。你只需要花几十块钱,就能得到一个能联网、能拍照、还能跑简单AI程序的硬件,这比用树莓派加USB摄像头成本低得多,功耗也更小。
再说说YOLOV5,这是目标检测领域的一个“明星”算法。YOLO的意思是“You Only Look Once”,顾名思义,它看一遍图片就能直接找出里面有什么物体、在哪里,速度非常快。相比它的前辈们,YOLOV5在保持高速度的同时,准确度也提升了不少,而且它对新手极其友好。它的代码库用PyTorch框架写成,结构清晰,提供了从轻量级到高精度的多种预训练模型(比如yolov5s, yolov5m, yolov5l等),你可以根据自己的硬件能力选择。最关键的是,它的生态非常好,有大量的社区支持和教程,遇到问题很容易找到解决方案。
那么,把它们俩结合起来是什么效果呢?简单说,就是让一个成本低廉、功耗很低的嵌入式设备,拥有了“顶尖”的视觉理解能力。ESP32 CAM负责“看”(采集图像并通过Wi-Fi传输),你的一台性能更强的电脑(甚至是另一块开发板)负责“思考”(运行YOLOV5模型进行推理)。这种“边缘采集,云端或近端推理”的模式,非常适合对实时性有要求,但又受限于终端设备计算能力的场景。我当初就是被这个想法吸引,亲手搭建了一套,过程虽然踩了些坑,但成功让摄像头认出我家猫并自动拍照时,那种成就感别提多棒了。
2. 硬件准备与ESP32 CAM基础烧录
万事开头难,但第一步只要走稳了,后面就顺了。首先,你得把硬件凑齐。
必备硬件清单:
- ESP32 CAM开发板:这是主角。市面上最常见的是AI-Thinker版本,就是那个蓝色小板子,认准这个买一般不会错。
- USB转TTL串口下载器:ESP32 CAM本身没有USB口,必须通过这个工具来给它烧录程序和供电。建议买一个带DTR和RTS引脚自动复位功能的,比如FT232RL或CH340G芯片的,会省事很多。
- 杜邦线:至少需要4根(母对母),用于连接下载器和ESP32 CAM。
- 一台电脑:用于编程和运行YOLOV5,Windows、Mac或Linux系统都可以。
拿到ESP32 CAM后,别急着通电,我们先来认识一下它的引脚。板子上最显眼的是那个排母。你需要用杜邦线将下载器与ESP32 CAM的以下引脚连接起来:
| 下载器引脚 | 连接至 ESP32 CAM 引脚 | 作用 |
|---|---|---|
| 3.3V | 3.3V | 供电(切记是3.3V!) |
| GND | GND | 共地 |
| TX | RX (U0R) | 下载器发送,ESP32接收 |
| RX | TX (U0T) | 下载器接收,ESP32发送 |
这里有个超级重要的坑我踩过:一定要接对电压!ESP32 CAM的工作电压是3.3V,如果你不小心接到了5V引脚上,板子很可能瞬间“阵亡”。连接好后,把ESP32 CAM自带的那个小跳线帽(如果有的话)接到GPIO0和GND之间,这是为了让它进入下载模式。有些板子可能没有跳线帽,你需要手动用一根杜邦线短接GPIO0和GND。
接下来是软件环境。我们需要Arduino IDE来给ESP32 CAM烧录固件。打开Arduino IDE,首先进入“文件”->“首选项”,在“附加开发板管理器网址”里添加这个网址:https://espressif.github.io/arduino-esp32/package_esp32_index.json。然后打开“工具”->“开发板”->“开发板管理器”,搜索“esp32”,找到由Espressif Systems提供的“esp32”平台并安装。安装完成后,在“工具”->“开发板”里就能选择“AI Thinker ESP32-CAM”了。
现在,我们来烧录一个最简单的测试程序,确保硬件和连接是好的。在Arduino IDE里新建一个项目,输入以下代码,这其实就是经典的“Blink”程序,但我们让板载的LED(通常连接到GPIO4,也就是闪光灯)闪烁:
void setup() {
pinMode(4, OUTPUT); // 将GPIO4设置为输出模式,控制闪光灯
}
void loop() {
digitalWrite(4, HIGH); // 打开闪光灯
delay(1000); // 等待1秒
digitalWrite(4, LOW); // 关闭闪光灯
delay(1000); // 等待1秒
}
在“工具”菜单里,确保端口选择了你的USB转TTL串口。点击上传按钮。如果一切顺利,你会看到编译和上传的进度条。上传成功后,记得把GPIO0和GND之间的短接线断开,然后按一下板子上的复位按钮(RST)。这时你应该能看到ESP32 CAM上的红色电源灯常亮,并且白色的小闪光灯开始一秒一亮、一秒一灭地闪烁。恭喜你,硬件通路和基础环境已经打通了!
3. 让ESP32 CAM变身网络摄像头服务器
硬件搞定后,我们要让ESP32 CAM活起来,变成一个能通过Wi-Fi访问的视频流服务器。这样,我们才能在电脑上获取到实时的画面,送给YOLOV5去分析。
Arduino IDE为我们提供了非常方便的示例库。打开“文件”->“示例”->“ESP32”->“Camera”->“CameraWebServer”。这个示例程序功能非常强大,可以直接在网页上看到摄像头画面,并能调整分辨率、亮度等参数。但我们最终的目标是和YOLOV5配合,所以需要对它进行一些“瘦身”和定制。
首先,你需要根据自己手头的ESP32 CAM型号,在代码开头取消对应型号的注释。对于最常见的AI-Thinker板子,就是取消 #define CAMERA_MODEL_AI_THINKER 这一行的注释。然后,找到 const char* ssid 和 const char* password,改成你家的Wi-Fi名称和密码。这里有个小建议:最好连接2.4GHz频段的Wi-Fi,因为ESP32对5GHz支持可能不稳定。
我们的核心修改目标是:让程序在启动Web服务器(默认端口80)的同时,再开启一个TCP Socket服务器。为什么需要这个?因为YOLOV5程序需要一种稳定、低延迟的方式从ESP32 CAM获取图像数据。虽然通过HTTP访问网页也能拿到图片,但效率不高。而通过TCP Socket,我们可以建立一条直接的、持续的数据通道,传输效率更高。
在 setup() 函数里,在 startCameraServer(); 这行代码之后,我们添加TCP服务器的初始化。例如,我们让它在1234端口监听:
WiFiServer tcpServer(1234); // 在全局变量区域定义
tcpServer.begin(); // 在setup()函数内,startCameraServer()之后调用
Serial.println("TCP Server started on port 1234");
然后,在 loop() 函数里,我们需要不断检查是否有客户端(也就是后面要跑的YOLOV5程序)连接,并处理数据收发。这里我写一个简化的处理逻辑:
void loop() {
// 检查是否有新的TCP客户端连接
WiFiClient client = tcpServer.available();
if (client) {
Serial.println("New TCP Client connected.");
while (client.connected()) {
// 这里可以发送图像数据,或者接收控制指令
// 例如,当YOLOV5端发送一个“capture”字符串时,我们回复一帧JPEG图像数据
if (client.available()) {
String command = client.readStringUntil('\n');
command.trim();
if (command == "get_frame") {
// 获取一帧摄像头图像
camera_fb_t * fb = esp_camera_fb_get();
if (fb) {
// 先发送图像数据长度
client.write((uint8_t*)&fb->len, sizeof(fb->len));
// 再发送图像数据本身
client.write(fb->buf, fb->len);
esp_camera_fb_return(fb);
}
}
}
delay(10);
}
client.stop();
Serial.println("TCP Client disconnected.");
}
delay(10);
}
这段代码的意思是,当YOLOV5程序连接上来并发送一个“get_frame”命令时,ESP32 CAM就会抓取当前的一帧JPEG图像,先发送4个字节的图像长度信息,再发送完整的图像数据。这种“请求-响应”的模式比持续推流更可控。修改好代码后,再次编译上传到ESP32 CAM。上传成功后,打开串口监视器(波特率115200),你会看到ESP32 CAM打印出的IP地址,比如 http://192.168.1.105。在浏览器里输入这个地址,你应该能看到摄像头画面了。同时,TCP服务器也在1234端口准备就绪。
4. 在电脑上搭建YOLOV5推理环境
现在,摄像头已经在线了,轮到“大脑”——YOLOV5出场了。我们需要在一台性能足够的电脑上配置Python环境来运行它。我强烈推荐使用 Anaconda 来管理Python环境,它能很好地解决不同项目间库版本冲突的问题。
首先,去Anaconda官网下载并安装。安装完成后,打开Anaconda Prompt(Windows)或终端(Mac/Linux),我们创建一个新的虚拟环境,专门用于这个项目:
conda create -n esp32_yolo python=3.8
conda activate esp32_yolo
这里指定Python 3.8是因为它在深度学习框架中兼容性比较好。环境激活后,命令行的前缀会变成 (esp32_yolo)。
接下来,安装PyTorch。这是YOLOV5依赖的核心深度学习框架。去PyTorch官网(https://pytorch.org/get-started/locally/),根据你的操作系统、包管理工具(我们选pip)、CUDA版本(如果你有NVIDIA显卡且安装了CUDA,就选择对应的版本;如果没有,就选CPU版本)生成安装命令。例如,对于没有独立显卡的电脑,安装CPU版本的命令大概是:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
安装过程可能会慢一点,耐心等待。完成后,我们来获取YOLOV5的源码。YOLOV5的官方仓库在GitHub上。我们直接克隆下来:
git clone https://github.com/ultralytics/yolov5
cd yolov5
pip install -r requirements.txt
requirements.txt 文件里列出了所有必需的Python库,像opencv-python(处理图像)、matplotlib(画图)等。pip会自动安装它们。安装完成后,我们可以先做个快速测试,验证环境是否正常。在yolov5目录下运行:
python detect.py --source data/images --weights yolov5s.pt --conf 0.25
这个命令会使用最小的预训练模型 yolov5s.pt 去检测 data/images 文件夹里自带的几张示例图片。如果一切正常,你会在 runs/detect/exp 目录下看到处理后的图片,上面画出了检测到的物体框和标签。看到这个,就说明YOLOV5环境已经成功搭建起来了!这个过程可能会自动下载 yolov5s.pt 模型文件,如果网络慢,也可以提前从YOLOV5的GitHub release页面手动下载好放到项目根目录。
5. 打通任督二脉:编写Python拉流与检测脚本
环境都准备好了,现在是时候写一个Python脚本,作为整个系统的“指挥官”。这个脚本要完成三件大事:第一,从ESP32 CAM的TCP服务器获取图像;第二,调用YOLOV5模型对图像进行推理检测;第三,把检测结果可视化显示出来,或者发送回ESP32 CAM进行联动。
首先,我们需要建立TCP连接。使用Python的 socket 库可以轻松实现:
import socket
import struct
ESP32_IP = "192.168.1.105" # 替换成你的ESP32 CAM的IP
ESP32_PORT = 1234
# 创建socket并连接
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((ESP32_IP, ESP32_PORT))
print(f"Connected to ESP32-CAM at {ESP32_IP}:{ESP32_PORT}")
接下来,我们要定义一个函数,通过这个socket向ESP32 CAM发送“get_frame”命令,并接收它传回来的图像数据。记得我们约定的协议:先接收4个字节的图像长度(整数),再接收对应长度的图像数据。
def get_frame_from_esp32(socket):
# 发送获取帧的命令
socket.sendall(b"get_frame\n")
# 接收4字节的图像长度
size_data = socket.recv(4)
if len(size_data) < 4:
return None
img_size = struct.unpack('<I', size_data)[0] # 小端字节序解包
# 接收图像数据
img_data = b""
while len(img_data) < img_size:
packet = socket.recv(img_size - len(img_data))
if not packet:
return None
img_data += packet
return img_data
拿到JPEG格式的图像数据(img_data)后,我们可以用OpenCV把它解码成我们熟悉的图像数组(numpy array):
import cv2
import numpy as np
def jpeg_to_cv2(img_bytes):
# 将字节数据转换为numpy数组
nparr = np.frombuffer(img_bytes, np.uint8)
# 用OpenCV解码JPEG图像
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
return img
核心的检测部分就轮到YOLOV5上场了。我们加载预训练模型,并对每一帧图像进行推理:
import torch
# 加载YOLOV5模型,使用本地下载好的权重文件
model = torch.hub.load('./yolov5', 'custom', path='./yolov5s.pt', source='local')
# 设置置信度阈值,低于这个值的检测结果会被过滤掉
model.conf = 0.4
while True:
# 1. 从ESP32获取一帧
jpeg_data = get_frame_from_esp32(client_socket)
if jpeg_data is None:
print("Failed to get frame.")
break
# 2. 转换为OpenCV图像
frame = jpeg_to_cv2(jpeg_data)
if frame is None:
continue
# 3. YOLOV5推理
results = model(frame)
# 4. 解析并绘制结果
detections = results.pandas().xyxy[0] # 获取检测结果DataFrame
for index, row in detections.iterrows():
x1, y1, x2, y2 = int(row['xmin']), int(row['ymin']), int(row['xmax']), int(row['ymax'])
label = row['name']
conf = row['confidence']
# 在图像上画框和文字
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, f"{label} {conf:.2f}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 如果检测到特定目标,比如“person”,可以发送指令回ESP32
if label == 'person':
client_socket.sendall(b"person_detected\n")
# 5. 显示结果
cv2.imshow('ESP32-CAM with YOLOV5', frame)
# 按'q'键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 清理工作
client_socket.close()
cv2.destroyAllWindows()
把上面这些代码段组合成一个完整的脚本,并确保你的IP地址和模型路径正确,运行它。你应该会看到一个弹出的窗口,里面显示着ESP32 CAM拍摄的实时画面,并且YOLOV5已经识别出的物体会被绿色框标出来,上面还有标签和置信度。当画面中出现“人”时,脚本还会向ESP32 CAM发送一个“person_detected”的消息。你可以根据这个思路,扩展出无数有趣的应用,比如检测到猫就拍照存档,检测到快递员就触发开门等等。
6. 性能优化与实战踩坑经验分享
系统跑起来后,你可能会遇到一些关于速度和稳定性的问题。别担心,这都是正常的,我们可以一步步优化。
1. 图像分辨率与帧率的权衡:
ESP32 CAM的OV2640摄像头最高可以输出UXGA(1600x1200)的图像,但对于YOLOV5来说,这太大了,推理速度会非常慢。YOLOV5模型的输入默认是640x640。所以,我们完全没必要传输那么大的图。在ESP32 CAM的代码里,找到设置帧大小的部分(通常是 s->set_framesize(s, FRAMESIZE_SVGA);)。FRAMESIZE_SVGA 是800x600,你可以尝试更小的尺寸,比如 FRAMESIZE_VGA (640x480) 甚至 FRAMESIZE_QVGA (320x240)。图像变小了,不仅传输更快,YOLOV5推理也更快。实测下来,对于近距离检测,QVGA分辨率很多时候已经够用,帧率能有显著提升。
2. JPEG压缩质量:
在 camera_config_t config 结构体里,有一个 config.jpeg_quality 参数,范围是0-63,数值越小质量越高、图片越大。对于检测任务,我们不需要精美的画质,可以把质量调低(比如设为20或30),这样能极大减少每一帧图像的数据量,缩短网络传输时间。
3. YOLOV5模型的选择:
YOLOV5提供了 s, m, l, x 等不同大小的模型。yolov5s.pt 是最小最快的,但精度稍低;yolov5x.pt 精度高但速度慢。在电脑性能允许的范围内做权衡。对于ESP32 CAM这个项目,yolov5s 或 yolov5m 通常是更好的选择。你甚至可以使用YOLOV5官方提供的“剪枝”或“量化”后的模型,它们体积更小,速度更快。
4. 网络稳定性:
Wi-Fi信号强度对视频流的稳定性至关重要。确保ESP32 CAM离路由器不要太远。如果出现卡顿或断连,可以在Python脚本里增加重连机制。另外,TCP Socket本身是可靠的,但如果一帧图像很大,可能会被拆成多个TCP包,我们的接收代码要确保能完整接收,这就是为什么前面要用循环 while len(img_data) < img_size: 来收包的原因。
5. 内存与资源管理:
ESP32 CAM的PSRAM虽然有几MB,但也不是无限的。在Arduino代码中,config.fb_count 设置帧缓冲区的数量。如果PSRAM存在,可以设为2,实现“乒乓缓冲”,一边采集一边发送,更流畅。在Python端,记得及时释放不再需要的变量,特别是循环内的临时变量,避免内存泄漏。用 cv2.destroyAllWindows() 关闭窗口也是个好习惯。
我印象最深的一个坑是早期没处理好TCP的“粘包”问题。就是两帧图像的数据可能会在接收端粘在一起,导致解码失败。后来我采用了“长度头+数据体”的协议(先发4字节长度,再发图像),并确保接收函数严格按照长度来读取,这个问题才彻底解决。所以,在嵌入式系统和PC通信时,设计一个简单明确的协议非常重要。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)