#include "camera_pins.h"
#include <WiFi.h>
#include "esp_camera.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Edge Impulse模型库(需手动添加到项目目录)
#include "shibie_inferencing.h"
#include "edge-impulse-sdk/dsp/image/image.hpp"
#include "esp_task_wdt.h"
// 功能开关
#define ENABLE_INFERENCE 1
#define ENABLE_HTTP_SERVER 1
#define ENABLE_OLED_DISPLAY 1
#define SUPPORT_OBJECT_DETECTION 0
// 摄像头配置
#define CAMERA_MODEL_AI_THINKER
#define XCLK_FREQ_HZ 8000000 // 8MHz时钟,更稳定
#define FRAME_SIZE FRAMESIZE_QVGA // 320x240
#define JPEG_QUALITY 10 // 低压缩比,高质量
#define MAX_CAPTURE_RETRIES 3 // 捕获重试次数
// 图像尺寸
#define EI_CAMERA_COLS 320
#define EI_CAMERA_ROWS 240
#define MODEL_INPUT_WIDTH EI_CLASSIFIER_INPUT_WIDTH
#define MODEL_INPUT_HEIGHT EI_CLASSIFIER_INPUT_HEIGHT
// OLED配置
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
#define OLED_SDA 21
#define OLED_SCL 22
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
// WiFi配置
const char* ssid = "88888888";
const char* password = "11111111";
// 全局变量
WiFiServer server(80);
static bool is_initialised = false;
static bool wifi_connected = false;
uint8_t* snapshot_buf = NULL; // 原始RGB缓冲区
uint8_t* model_buf = NULL; // 模型输入缓冲区
camera_fb_t* fb = NULL;
// 摄像头配置
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 = XCLK_FREQ_HZ,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAME_SIZE,
.jpeg_quality = JPEG_QUALITY,
.fb_count = 2, // 双缓冲
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
// 正确的JPEG转RGB888函数(使用ESP32官方函数)
bool convert_jpeg_to_rgb888(const uint8_t* jpeg_data, size_t jpeg_size, uint8_t* rgb_data, size_t rgb_size) {
// 使用ESP32摄像头库自带的fmt2rgb888函数(官方推荐)
return fmt2rgb888(jpeg_data, jpeg_size, PIXFORMAT_JPEG, rgb_data);
}
// 备用缩放函数(双线性插值,解决SDK缩放失败问题)
bool backup_resize_rgb888(const uint8_t* src, uint32_t src_width, uint32_t src_height,
uint8_t* dst, uint32_t dst_width, uint32_t dst_height) {
if (!src || !dst || src_width == 0 || src_height == 0 || dst_width == 0 || dst_height == 0) {
return false;
}
float x_ratio = (float)src_width / (float)dst_width;
float y_ratio = (float)src_height / (float)dst_height;
for (uint32_t y = 0; y < dst_height; y++) {
for (uint32_t x = 0; x < dst_width; x++) {
float src_x = x * x_ratio;
float src_y = y * y_ratio;
uint32_t x1 = (uint32_t)src_x;
uint32_t y1 = (uint32_t)src_y;
uint32_t x2 = (x1 < src_width - 1) ? x1 + 1 : x1;
uint32_t y2 = (y1 < src_height - 1) ? y1 + 1 : y1;
float fx = src_x - x1;
float fy = src_y - y1;
for (uint8_t c = 0; c < 3; c++) {
uint8_t v11 = src[(y1 * src_width + x1) * 3 + c];
uint8_t v12 = src[(y2 * src_width + x1) * 3 + c];
uint8_t v21 = src[(y1 * src_width + x2) * 3 + c];
uint8_t v22 = src[(y2 * src_width + x2) * 3 + c];
uint8_t v1 = (uint8_t)((1 - fx) * v11 + fx * v21);
uint8_t v2 = (uint8_t)((1 - fx) * v12 + fx * v22);
dst[(y * dst_width + x) * 3 + c] = (uint8_t)((1 - fy) * v1 + fy * v2);
}
}
}
return true;
}
/* -------------------------- 工具函数 -------------------------- */
void oled_print(const char* line1, const char* line2 = "", const char* line3 = "") {
#ifdef ENABLE_OLED_DISPLAY
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(line1);
if (strlen(line2) > 0) {
display.setCursor(0, 16);
display.println(line2);
}
if (strlen(line3) > 0) {
display.setCursor(0, 32);
display.println(line3);
}
display.display();
#endif
}
/* -------------------------- 摄像头操作 -------------------------- */
bool camera_init() {
if (is_initialised) return true;
// 初始化摄像头(带重试)
esp_err_t err;
int init_retry = 0;
while (init_retry < 3) {
err = esp_camera_init(&camera_config);
if (err == ESP_OK) break;
Serial.printf("摄像头初始化失败(重试%d): %s\n", init_retry+1, esp_err_to_name(err));
init_retry++;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
if (err != ESP_OK) {
Serial.println("摄像头初始化彻底失败");
return false;
}
// 优化传感器参数
sensor_t* s = esp_camera_sensor_get();
if (s) {
s->set_vflip(s, 1); // 翻转图像
s->set_hmirror(s, 1); // 镜像图像
s->set_awb_gain(s, 1); // 自动白平衡
s->set_exposure_ctrl(s, 1); // 自动曝光
s->set_gain_ctrl(s, 1); // 自动增益
s->set_brightness(s, 0); // 亮度适中
s->set_contrast(s, 0); // 对比度适中
}
// 分配内存缓冲区(4字节对齐)
size_t snapshot_size = EI_CAMERA_COLS * EI_CAMERA_ROWS * 3;
size_t model_size = MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3;
snapshot_buf = (uint8_t*)aligned_alloc(4, snapshot_size);
model_buf = (uint8_t*)aligned_alloc(4, model_size);
// 对齐分配失败时降级为普通分配
if (!snapshot_buf) snapshot_buf = (uint8_t*)malloc(snapshot_size);
if (!model_buf) model_buf = (uint8_t*)malloc(model_size);
if (!snapshot_buf || !model_buf) {
Serial.println("内存缓冲区分配失败");
camera_deinit();
return false;
}
Serial.printf("分配内存: snapshot=%zu bytes, model=%zu bytes\n", snapshot_size, model_size);
is_initialised = true;
Serial.println("摄像头初始化成功");
return true;
}
void camera_deinit() {
if (is_initialised) {
esp_camera_deinit();
if (snapshot_buf) free(snapshot_buf);
if (model_buf) free(model_buf);
snapshot_buf = NULL;
model_buf = NULL;
is_initialised = false;
}
}
// 优化后的图像捕获函数(修复JPEG转RGB错误)
bool camera_capture(uint32_t img_width, uint32_t img_height, uint8_t* out_buf) {
if (!is_initialised) {
Serial.println("摄像头未初始化");
return false;
}
for (int retry = 0; retry < MAX_CAPTURE_RETRIES; retry++) {
// 获取JPEG帧
fb = esp_camera_fb_get();
if (!fb) {
Serial.printf("获取帧失败(重试%d)\n", retry+1);
vTaskDelay(200 / portTICK_PERIOD_MS);
continue;
}
// 过滤无效帧(1KB~200KB)
if (fb->len < 1024 || fb->len > 1024*200) {
Serial.printf("JPEG数据异常(%u字节),重试\n", fb->len);
esp_camera_fb_return(fb);
continue;
}
// 打印JPEG文件头(验证格式是否正确)
Serial.print("JPEG头: ");
for (int i = 0; i < 8 && i < fb->len; i++) {
Serial.printf("%02X ", fb->buf[i]); // 正常JPEG头应为FF D8 FF E0 ...
}
Serial.println();
// 转换JPEG到RGB888(使用正确的convert函数)
bool converted = convert_jpeg_to_rgb888(fb->buf, fb->len, snapshot_buf,
EI_CAMERA_COLS * EI_CAMERA_ROWS * 3);
esp_camera_fb_return(fb);
fb = NULL;
if (converted) {
// 打印RGB数据前8字节(验证是否有效)
Serial.print("RGB头: ");
for (int i = 0; i < 8; i++) {
Serial.printf("%02X ", snapshot_buf[i]);
}
Serial.println();
// 尝试SDK缩放函数
bool resized = false;
Serial.println("尝试SDK缩放...");
resized = ei::image::processing::crop_and_interpolate_rgb888(
snapshot_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
out_buf, img_width, img_height
);
if (!resized) {
// SDK缩放失败时用备用函数
Serial.println("SDK缩放失败,尝试备用缩放...");
resized = backup_resize_rgb888(
snapshot_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
out_buf, img_width, img_height
);
}
if (resized) {
Serial.println("缩放成功");
return true;
}
// 缩放失败原因
Serial.println("缩放失败原因:数据格式异常");
} else {
Serial.printf("JPEG转RGB失败(重试%d)\n", retry+1);
}
vTaskDelay(200 / portTICK_PERIOD_MS);
}
Serial.println("图像处理最终失败");
return false;
}
/* -------------------------- 推理核心 -------------------------- */
#ifdef ENABLE_INFERENCE
void run_inference() {
if (!is_initialised || !snapshot_buf || !model_buf) {
oled_print("未就绪", "摄像头异常");
return;
}
Serial.println("\n===== 开始推理 =====");
oled_print("识别中...");
if (!camera_capture(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT, model_buf)) {
oled_print("处理失败", "图像异常");
return;
}
// 准备模型输入信号
ei::signal_t signal;
signal.total_length = MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT;
signal.get_data = [](size_t offset, size_t length, float* out) {
size_t pixel_ix = offset * 3;
for (size_t i = 0; i < length; i++) {
out[i] = (model_buf[pixel_ix] - 127.5f) / 127.5f; // R
out[i + length] = (model_buf[pixel_ix + 1] - 127.5f) / 127.5f; // G
out[i + length * 2] = (model_buf[pixel_ix + 2] - 127.5f) / 127.5f; // B
pixel_ix += 3;
}
return 0;
};
// 运行推理
ei_impulse_result_t result;
EI_IMPULSE_ERROR err = run_classifier(&signal, &result, false);
if (err != EI_IMPULSE_OK) {
Serial.printf("推理失败: %d\n", err);
oled_print("推理错误", String(err).c_str());
return;
}
// 解析结果
float max_prob = 0;
const char* max_label = "未知";
for (uint16_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
if (result.classification[i].value > max_prob) {
max_prob = result.classification[i].value;
max_label = ei_classifier_inferencing_categories[i];
}
}
// 显示结果
Serial.printf("识别结果: %s (%.1f%%)\n", max_label, max_prob*100);
oled_print("识别为:", max_label, (String(max_prob*100, 1) + "%").c_str());
Serial.println("===== 推理结束 =====");
}
#endif
/* -------------------------- HTTP服务 -------------------------- */
#ifdef ENABLE_HTTP_SERVER
void handle_client(WiFiClient& client) {
String req = client.readStringUntil('\n');
req.trim();
Serial.println("HTTP请求: " + req);
if (req.startsWith("GET /photo")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: image/jpeg");
client.println("Connection: close");
client.println();
fb = esp_camera_fb_get();
if (fb && fb->len > 1024) {
client.write(fb->buf, fb->len);
esp_camera_fb_return(fb);
} else {
client.print("获取照片失败");
if (fb) esp_camera_fb_return(fb);
}
fb = NULL;
} else if (req.startsWith("GET /infer")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
if (ENABLE_INFERENCE) {
run_inference();
client.println("推理已触发");
} else {
client.println("推理功能未启用");
}
} else {
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: text/plain");
client.println();
client.println("支持: /photo, /infer");
}
client.stop();
}
#endif
/* -------------------------- 任务与初始化 -------------------------- */
void inference_task(void* param) {
while (1) {
if (wifi_connected && is_initialised) {
#ifdef ENABLE_INFERENCE
run_inference();
#endif
} else {
String status = wifi_connected ? "等待摄像头" : "等待WiFi";
oled_print("准备中", status.c_str());
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void http_task(void* param) {
server.begin();
Serial.println("HTTP服务启动");
while (1) {
if (wifi_connected) {
WiFiClient client = server.available();
if (client) handle_client(client);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void wifi_monitor_task(void* param) {
while (1) {
if (WiFi.status() != WL_CONNECTED) {
if (wifi_connected) {
wifi_connected = false;
Serial.println("WiFi连接丢失");
oled_print("错误", "WiFi断开");
}
WiFi.reconnect();
Serial.println("尝试重连WiFi...");
oled_print("重连WiFi中");
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 20) {
vTaskDelay(500 / portTICK_PERIOD_MS);
retry++;
}
if (WiFi.status() == WL_CONNECTED) {
wifi_connected = true;
Serial.printf("WiFi重连成功: %s\n", WiFi.localIP().toString().c_str());
oled_print("WiFi已恢复", WiFi.localIP().toString().c_str());
}
} else if (!wifi_connected) {
wifi_connected = true;
Serial.printf("WiFi已连接: %s\n", WiFi.localIP().toString().c_str());
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
while (!Serial) vTaskDelay(100 / portTICK_PERIOD_MS);
Serial.println("Edge Impulse 识别系统启动中");
// 初始化OLED
#ifdef ENABLE_OLED_DISPLAY
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("OLED初始化失败(忽略)");
} else {
oled_print("系统启动中");
}
#endif
// 初始化摄像头
if (!camera_init()) {
oled_print("错误", "摄像头初始化失败");
while (1) vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// 连接WiFi
WiFi.begin(ssid, password);
WiFi.setSleep(false);
Serial.printf("连接WiFi: %s...\n", ssid);
oled_print("连接WiFi", ssid);
// 创建任务
xTaskCreatePinnedToCore(inference_task, "inference", 40960, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(http_task, "http", 20480, NULL, 1, NULL, 1);
xTaskCreatePinnedToCore(wifi_monitor_task, "wifi_monitor", 8192, NULL, 1, NULL, 1);
}
void loop() {
vTaskDelay(portMAX_DELAY);
}修改