首先是ESP32CAM有WiFi模块,通过WiFi与本机连接同一个ip地址完成拍摄图片后上传本地服务器并保存
ESP32CAM在VSCODE使用platformio开发
项目具体步骤为
1ESP32CAM的代码烧录
2本地服务器的启动
3重启ESP32CAM完成与本地服务器连接,在串口工具可以查看连接状态
由于在ESP32CAM上进行了开闪光灯的操作,如不需要请自行注释
ESP32CAM
#include <Arduino.h>
#include <WiFi.h>
#include "esp_camera.h"
#include <Base64.h> // 引入 Base64 库
const char *ssid = ""; //替换为实际的WiFi名称
const char *password = ""; // 替换为实际的WiFi密码
const IPAddress serverIP(192, 168, 3, 235); //替换为你的服务器的 IP 地址
uint16_t serverPort = 8080; // 替换为你的设置的服务器端口号
#define maxcache 1436
WiFiClient client; // 声明一个客户端对象,用于与服务器进行连接
// 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
#define LED_PIN 4 // ESP32-CAM的LED引脚,默认GPIO 4
static camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_sscb_sda = SIOD_GPIO_NUM,
.pin_sscb_scl = SIOC_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_VGA,
.jpeg_quality = 12,
.fb_count = 1,
};
// Wi-Fi 初始化
void wifi_init() {
WiFi.mode(WIFI_STA);
WiFi.setSleep(false); // 关闭 STA 模式下 Wi-Fi 休眠,提高响应速度
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi Connected!");
Serial.print("IP Address:");
Serial.println(WiFi.localIP());
}
// 摄像头初始化
esp_err_t camera_init() {
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
Serial.println("Camera Init Failed");
return err;
}
sensor_t * s = esp_camera_sensor_get();
Serial.println("Camera Init OK!");
return ESP_OK;
}
// 打开LED
void led_off() {
digitalWrite(LED_PIN, LOW); // 打开LED(LOW通常是打开LED)
}
// 关闭LED
void led_on() {
digitalWrite(LED_PIN, HIGH); // 关闭LED
}
void setup() {
Serial.begin(115200);
while (!Serial); // 等待串口连接
Serial.println("Starting...");
wifi_init();
camera_init();
pinMode(LED_PIN, OUTPUT); // 设置LED引脚为输出模式
Serial.println("Testing LED...");
}
// 发送图片到服务器
void send_image_to_server(camera_fb_t * fb) {
client.print("Frame Begin"); // 一张图片的起始标志
// 将图片数据分段发送
String encodedData = base64::encode(fb->buf, fb->len);
int leng = encodedData.length();
int timess = leng / maxcache;
int extra = leng % maxcache;
for (int j = 0; j < timess; j++) {
client.write(encodedData.c_str() + j * maxcache, maxcache);
}
client.write(encodedData.c_str() + timess * maxcache, extra);
client.print("Frame Over"); // 一张图片的结束标志
Serial.print("This Frame Length: ");
Serial.print(fb->len);
Serial.println(". Success To Send Image For TCP!");
esp_camera_fb_return(fb); // 释放帧缓冲
}
void loop() {
static unsigned long lastMillis = 0;
unsigned long currentMillis = millis();
// 每隔 10 秒拍照并发送
if (currentMillis - lastMillis >= 10000) { // 10000 毫秒即 10 秒
lastMillis = currentMillis; // 更新最后发送时间
Serial.println("Trying to capture and send image...");
if (client.connect(serverIP, serverPort)) { // 连接服务器
led_on(); // 打开 LED(亮灯)
delay(500); // 等待500ms,确保LED亮起
camera_fb_t * fb = esp_camera_fb_get(); // 获取摄像头的帧数据
if (!fb) {
Serial.println("Camera Capture Failed");
led_off(); // 关闭 LED(熄灯)
return;
}
else {
send_image_to_server(fb);
led_off(); // 关闭 LED(熄灯)
}
}
else {
Serial.println("Connection to server failed, trying again...");
led_off(); // 关闭 LED(熄灯)
client.stop(); // 发送完成后关闭连接
}
}
delay(100); // 稍微延迟,减少 CPU 占用
}
python搭建服务器
import base64
import socket
import threading
import time
import numpy as np
import cv2
import os
from datetime import datetime
begin_data = b'Frame Begin'
end_data = b'Frame Over'
# 图片保存目录
SAVE_DIR = "received_images"
# 确保保存目录存在
if not os.path.exists(SAVE_DIR):
os.makedirs(SAVE_DIR)
# 接收数据
# ESP32发送一张照片的流程
# 先发送Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送1430 余数最后发送
# 完毕后发送结束标志 Frame Over 表示一张图片发送完毕
# 1430 来自ESP32cam发送的一个包大小为1430 接收到数据 data格式为b''
def handle_sock(sock, addr):
temp_data = b''
t1 = int(round(time.time() * 1000))
while True:
data = sock.recv(1436)
# 如果这一帧数据包的开头是 b'Frame Begin' 则是一张图片的开始
if data[0:len(begin_data)] == begin_data:
# 将这一帧数据包的开始标志信息(b'Frame Begin')清除 因为他不属于图片数据
data = data[len(begin_data):len(data)]
# 判断这一帧数据流是不是最后一个帧 最后一针数据的结尾时b'Frame Over'
while data[-len(end_data):] != end_data:
temp_data = temp_data + data # 不是结束的包 讲数据添加进temp_data
data = sock.recv(1436) # 继续接受数据 直到接受的数据包包含b'Frame Over' 表示是这张图片的最后一针
# 判断为最后一个包 将数据去除 结束标志信息 b'Frame Over'
temp_data = temp_data + data[0:(len(data) - len(end_data))] # 将多余的(\r\nFrame Over)去掉 其他放入temp_data
# 将接收到的 Base64 编码的数据解码
base64_data = temp_data.decode('utf-8')
img_data = base64.b64decode(base64_data)
# 显示图片
receive_data = np.frombuffer(img_data, dtype='uint8') # 将获取到的字符流数据转换成1维数组
r_img = cv2.imdecode(receive_data, cv2.IMREAD_COLOR) # 将数组解码成图像
# 计算帧率
t2 = int(round(time.time() * 1000))
fps = 1000 // (t2 - t1)
cv2.putText(r_img, "FPS" + str(fps), (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
# # 显示接收到图片
# if r_img is not None:
# cv2.imshow('server_frame', r_img)
# if cv2.waitKey(10) & 0xFF == ord('q'):
# break
# t1 = t2
# print("接收到的数据包大小:" + str(len(temp_data))) # 显示该张照片数据大小
# 保存图片
save_image(r_img,addr)
temp_data = b'' # 清空数据 便于下一章照片使用
def save_image(img, addr):
# 使用当前时间戳作为文件名,确保文件名唯一
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# 生成文件名,可以根据客户端的 IP 或者端口生成
filename = os.path.join(SAVE_DIR, f"image_{timestamp}_{addr[0]}_{addr[1]}.jpg")
# 保存图片
cv2.imwrite(filename, img)
print(f"图片已保存为: {filename}")
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('192.168.3.235', 8080)) # 替换为你的本机ip地址以及你想要设置的端口号
server.listen(5)
CONNECTION_LIST = []
# 主线程循环接收客户端连接
while True:
sock, addr = server.accept()
CONNECTION_LIST.append(sock)
print('Connect--{}'.format(addr))
# 连接成功后开一个线程用于处理客户端
client_thread = threading.Thread(target=handle_sock, args=(sock, addr))
client_thread.start()