camera buffer_handle_t
时间: 2025-06-27 14:12:32 浏览: 18
<think>好的,我现在需要回答用户关于Android中camera和buffer_handle_t的关系或用法的问题,特别是在HAL或图形编程中的使用。首先,我需要回忆一下Android系统中的相机数据流处理机制。
用户提到了buffer_handle_t,这应该是Gralloc分配的缓冲区句柄,用于图形内存管理。在Android HAL层,相机子系统通常会生成图像数据,这些数据需要传输到其他组件进行处理或显示。Gralloc HAL负责分配这些缓冲区,并返回buffer_handle_t来标识它们。
接下来,用户可能想知道相机如何获取和使用这些缓冲区。例如,当相机捕获一帧图像时,它需要从BufferQueue获取一个缓冲区,填充数据后交还给消费者端,比如SurfaceView或SurfaceTexture。这里可能涉及到ANativeWindow,因为生产者(相机)通过ANativeWindow连接到BufferQueue,并通过dequeueBuffer获取可用的buffer_handle_t,填充数据后通过queueBuffer提交。
还需要考虑buffer_handle_t的结构,它可能包含元数据如宽高、格式、步幅等,这些对于正确解析图像数据很重要。另外,usage标志位也很关键,因为它们指定了缓冲区的用途,比如CPU可读、GPU可写等,这会影响内存分配的方式。
用户提供的引用中,引用[4]提到当通过Canvas锁定Surface时,会连接到BufferQueue的生产者端,这可能影响其他生产者如相机或GLES的使用。因此,在同时使用Canvas和相机时需要注意这一点,避免冲突。
另外,引用[3]提到ANativeWindow是C/C++中的Surface等价物,相机可能通过ANativeWindow来操作BufferQueue,获取buffer_handle_t。因此,相机HAL可能使用ANativeWindow的API来管理缓冲区。
总结一下,buffer_handle_t在相机流程中用于传递图像数据,通过Gralloc分配,由生产者(相机)通过BufferQueue获取和提交,消费者端处理这些缓冲区。需要结合HAL接口和图形内存管理来详细说明用法,包括如何分配、传递和处理这些缓冲区,以及相关的注意事项,比如usage标志和并发访问的问题。</think>在Android相机和图形编程中,`buffer_handle_t`是Gralloc内存分配器返回的缓冲区句柄,用于跨进程或跨组件传递图像数据。以下是其关键应用场景及与相机模块的关系解析:
---
### 1. **相机数据流的缓冲区管理**
- **BufferQueue机制**:
Android相机通过`BufferQueue`实现生产者-消费者模型。相机作为**生产者**,通过`ANativeWindow`接口从`BufferQueue`获取可用缓冲区(通过`dequeueBuffer()`),填充图像数据后通过`queueBuffer()`提交[^3][^4]。
- 示例代码片段:
```cpp
// 从Surface获取ANativeWindow
ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
// 请求缓冲区
ANativeWindow_Buffer buffer;
ANativeWindow_lock(window, &buffer, nullptr);
// 填充数据到buffer_handle_t对应的内存
// ...
ANativeWindow_unlockAndPost(window);
```
- **`buffer_handle_t`的作用**:
每个从`BufferQueue`获取的缓冲区对应一个`buffer_handle_t`,其指向通过Gralloc分配的共享内存,包含图像元数据(如宽高`width/height`、像素格式`format`、步幅`stride`)。
---
### 2. **Gralloc与HAL的交互**
- **内存分配规则**:
相机HAL通过`GrallocModule`分配缓冲区时需指定`usage`标志(如`GRALLOC_USAGE_SW_READ_OFTEN`表示CPU可读),这直接影响内存的物理布局和访问权限[^1]。
- 例如,相机预览可能需要:
```cpp
buffer_handle_t buffer;
gralloc_module_t->alloc(..., CAMERA_STREAM, &buffer);
```
- **跨进程传递**:
`buffer_handle_t`通过Binder序列化(如`GraphicBuffer`对象)传递,消费者(如SurfaceFlinger或应用)通过`GraphicBufferMapper`锁定和解锁缓冲区以访问数据。
---
### 3. **关键应用场景**
- **相机预览与录像**:
相机HAL将捕获的帧写入`buffer_handle_t`,消费者(如`SurfaceTexture`)从`BufferQueue`获取并渲染。时间戳(`timestamp`)由相机HAL设置,确保视频同步[^2]。
- **零拷贝处理**:
使用`buffer_handle_t`可直接将相机输出传递给GPU(如OpenGL ES纹理)或硬件编码器,避免内存复制。例如:
```cpp
// 将buffer_handle_t绑定为EGLImage
EGLImageKHR image = eglCreateImageKHR(..., buffer_handle_t);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
```
---
### 4. **注意事项**
- **并发冲突**:
若通过Canvas锁定Surface进行软件渲染,将独占`BufferQueue`的生产者端,导致相机或GLES无法连接。因此需避免混合使用Canvas与硬件渲染。
- **格式兼容性**:
相机输出格式(如`HAL_PIXEL_FORMAT_YCrCb_420_SP`)需与消费者(如编解码器)支持的格式匹配,否则需要格式转换。
---
阅读全文
相关推荐





#include "camera_pins.h" #include <WiFi.h> #include "esp_camera.h" #include <SD_MMC.h> // SD卡库 #include <shibie_inferencing.h> #include "edge-impulse-sdk/dsp/image/image.hpp" // 摄像头配置 #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 // Edge Impulse模型参数 #define EI_CAMERA_RAW_FRAME_BUFFER_COLS 320 #define EI_CAMERA_RAW_FRAME_BUFFER_ROWS 240 #define EI_CAMERA_FRAME_BYTE_SIZE 3 // WiFi配置 const char* ssid = "88888888"; const char* password = "11111111"; WiFiServer server(80); // 全局变量 static bool is_initialised = false; uint8_t* snapshot_buf = nullptr; // 摄像头初始化函数 bool ei_camera_init(void) { if (is_initialised) return true; 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 = 12; config.fb_count = 1; esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed: 0x%x\n", err); return false; } is_initialised = true; return true; } // 图像捕获函数 bool ei_camera_capture(uint32_t img_width, uint32_t img_height, uint8_t* out_buf) { if (!is_initialised) { Serial.println("Camera not initialized"); return false; } camera_fb_t* fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); return false; } bool converted = fmt2rgb888(fb->buf, fb->len, PIXFORMAT_JPEG, snapshot_buf); esp_camera_fb_return(fb); if (!converted) { Serial.println("RGB conversion failed"); return false; } if ((img_width != EI_CAMERA_RAW_FRAME_BUFFER_COLS) || (img_height != EI_CAMERA_RAW_FRAME_BUFFER_ROWS)) { ei::image::processing::crop_and_interpolate_rgb888( out_buf, EI_CAMERA_RAW_FRAME_BUFFER_COLS, EI_CAMERA_RAW_FRAME_BUFFER_ROWS, out_buf, img_width, img_height ); } return true; } // Edge Impulse数据获取回调 static int ei_camera_get_data(size_t offset, size_t length, float* out_ptr) { size_t pixel_ix = offset * 3; size_t pixels_left = length; size_t out_ptr_ix = 0; while (pixels_left != 0) { out_ptr[out_ptr_ix] = (snapshot_buf[pixel_ix + 2] << 16) + (snapshot_buf[pixel_ix + 1] << 8) + snapshot_buf[pixel_ix]; out_ptr_ix++; pixel_ix += 3; pixels_left--; } return 0; } // 执行Edge Impulse推理 String run_inference() { // 分配图像缓冲区 snapshot_buf = (uint8_t*)ps_malloc(EI_CAMERA_RAW_FRAME_BUFFER_COLS * EI_CAMERA_RAW_FRAME_BUFFER_ROWS * EI_CAMERA_FRAME_BYTE_SIZE); if (!snapshot_buf) { return "{\"error\":\"Memory allocation failed\"}"; } // 捕获图像 if (!ei_camera_capture(EI_CLASSIFIER_INPUT_WIDTH, EI_CLASSIFIER_INPUT_HEIGHT, snapshot_buf)) { free(snapshot_buf); return "{\"error\":\"Image capture failed\"}"; } // 准备信号 ei::signal_t signal; signal.total_length = EI_CLASSIFIER_INPUT_WIDTH * EI_CLASSIFIER_INPUT_HEIGHT; signal.get_data = &ei_camera_get_data; // 执行推理 ei_impulse_result_t result = {0}; EI_IMPULSE_ERROR err = run_classifier(&signal, &result, false); free(snapshot_buf); if (err != EI_IMPULSE_OK) { return "{\"error\":\"Classification failed: " + String(err) + "\"}"; } // 生成JSON响应 String json = "{\"predictions\":["; for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) { json += "{\"label\":\"" + String(ei_classifier_inferencing_categories[i]) + "\","; json += "\"value\":" + String(result.classification[i].value, 5) + "}"; if (i < EI_CLASSIFIER_LABEL_COUNT - 1) json += ","; } json += "],\"timing\":{"; json += "\"dsp\":" + String(result.timing.dsp) + ","; json += "\"classification\":" + String(result.timing.classification) + ","; json += "\"anomaly\":" + String(result.timing.anomaly) + "}}"; return json; } // 处理HTTP请求 void handle_client(WiFiClient& client) { String request = client.readStringUntil('\n'); request.trim(); if (request.indexOf("GET /photo") != -1) { camera_fb_t* fb = esp_camera_fb_get(); if (!fb) { client.println("HTTP/1.1 500 Internal Server Error"); client.println("Content-Type: text/plain"); client.println(); client.println("Camera capture failed"); return; } client.println("HTTP/1.1 200 OK"); client.println("Content-Type: image/jpeg"); client.println("Content-Length: " + String(fb->len)); client.println("Access-Control-Allow-Origin: *"); client.println(); client.write(fb->buf, fb->len); esp_camera_fb_return(fb); } else if (request.indexOf("GET /classify") != -1) { String json = run_inference(); client.println("HTTP/1.1 200 OK"); client.println("Content-Type: application/json"); client.println("Access-Control-Allow-Origin: *"); client.println("Connection: close"); client.println(); client.println(json); } else { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println(); client.println("<html><body>"); client.println("ESP32-CAM Edge Impulse"); client.println("View Camera"); client.println("Run Classification"); client.println("</body></html>"); } } void setup() { Serial.begin(115200); // 初始化摄像头 if (!ei_camera_init()) { Serial.println("Failed to initialize camera"); while(1) delay(1000); } // 连接WiFi WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nConnected! IP address: " + WiFi.localIP().toString()); server.begin(); Serial.println("HTTP server started"); } void loop() { WiFiClient client = server.available(); if (client) { while (client.connected()) { if (client.available()) { handle_client(client); break; } delay(1); } client.stop(); } delay(10); }浏览器上可以看实时图像,串口监视可以看图像识别结果






#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"
#include "freertos/semphr.h"
#include <SD_MMC.h> // SD卡库(使用SPI或SDMMC接口)
#include <SPIFFS.h>
#include <SD.h> // SPI接口SD卡库(新增)
#include <Time.h> // 时间函数库(新增)
#include "esp_task_wdt.h"
#include "freertos/semphr.h"
// 功能开关
#define ENABLE_INFERENCE 1
#define ENABLE_HTTP_SERVER 1
#define ENABLE_OLED_DISPLAY 1
#define ENABLE_SD_CARD 1 // 启用SD卡功能
#define SUPPORT_OBJECT_DETECTION 0
// 摄像头配置
#define CAMERA_MODEL_AI_THINKER
#define XCLK_FREQ_HZ 2000000 // 降低时钟频率
#define FRAME_SIZE FRAMESIZE_QVGA // 320x240分辨率
#define JPEG_QUALITY 12
#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";
// SD卡配置(SPI接口)
#define SD_MOUNT_POINT "/sdcard"
#define SD_PIN_CS 15 // 改为常用的SPI CS引脚(原5可能冲突)
// 全局变量
WiFiServer server(80);
static bool is_initialised = false;
static bool wifi_connected = false;
static bool sd_ready = false;
uint8_t* model_buf = NULL;
camera_fb_t* fb = NULL;
SemaphoreHandle_t camera_mutex = NULL;
SemaphoreHandle_t sd_mutex = 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 = 1,
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
/* -------------------------- 时间初始化 -------------------------- */
// 初始化时间(使用NTP同步)
void init_time() {
configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // 北京时间(UTC+8)
Serial.println("等待时间同步...");
time_t now = time(nullptr);
int retry_count = 0;
// 等待时间同步,超时则跳过
while (now < 1609459200 && retry_count < 10) { // 等待同步到2021年之后
delay(1000);
Serial.print(".");
now = time(nullptr);
retry_count++;
}
if (now >= 1609459200) {
Serial.println("\n时间同步完成");
struct tm* tm_info = localtime(&now);
char time_str[26];
strftime(time_str, 26, "%Y-%m-%d %H:%M:%S", tm_info);
Serial.print("当前时间: ");
Serial.println(time_str);
} else {
Serial.println("\n时间同步超时,使用默认时间");
}
}
/* -------------------------- SD卡操作 -------------------------- */
#ifdef ENABLE_SD_CARD
bool sd_init() {
sd_mutex = xSemaphoreCreateMutex();
if (!sd_mutex) {
Serial.println("SD卡互斥锁创建失败");
return false;
}
Serial.println("初始化SD卡...");
// 尝试SDMMC模式(仅适用于支持SDMMC的板子)
if (!SD_MMC.begin("/sdcard", true)) { // 1线模式
Serial.println("SDMMC模式失败,尝试SPI模式...");
// SPI模式初始化(低速模式提高兼容性)
if (!SD.begin(SD_PIN_CS, SPI, 1000000)) { // 1MHz低速率
Serial.println("SPI模式初始化失败,检查:");
Serial.println("1. 引脚连接是否正确(CS=" + String(SD_PIN_CS) + ", SCK=18, MOSI=23, MISO=19)");
Serial.println("2. SD卡是否为FAT32格式");
Serial.println("3. 尝试更换SD卡");
return false;
}
}
// 验证SD卡读写功能
File testFile = SD.open("/test.txt", FILE_WRITE);
if (testFile) {
testFile.println("SD卡测试成功");
testFile.close();
// 读取测试
testFile = SD.open("/test.txt", FILE_READ);
if (testFile) {
String content = testFile.readString();
testFile.close();
SD.remove("/test.txt"); // 删除测试文件
Serial.println("SD卡读写验证成功: " + content);
}
} else {
Serial.println("SD卡写入测试失败");
return false;
}
// 创建目录
if (!SD.exists("/logs")) SD.mkdir("/logs");
if (!SD.exists("/images")) SD.mkdir("/images");
sd_ready = true;
Serial.println("SD卡初始化成功");
return true;
}
// 写入日志到SD卡
void sd_log(const char* message) {
if (!sd_ready) return;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return;
time_t now = time(nullptr);
struct tm* tm = localtime(&now);
char filename[32];
sprintf(filename, "/logs/%04d%02d%02d.txt",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
File file = SD.open(filename, FILE_APPEND);
if (file) {
char timestamp[32];
sprintf(timestamp, "[%02d:%02d:%02d] ",
tm->tm_hour, tm->tm_min, tm->tm_sec);
file.print(timestamp);
file.println(message);
file.close();
}
xSemaphoreGive(sd_mutex);
}
// 保存图像到SD卡
bool sd_save_image(const uint8_t* data, size_t size) {
if (!sd_ready || !data || size == 0) return false;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return false;
time_t now = time(nullptr);
struct tm* tm = localtime(&now);
char filename[64];
sprintf(filename, "/images/img_%04d%02d%02d_%02d%02d%02d.jpg",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
File file = SD.open(filename, FILE_WRITE);
if (!file) {
xSemaphoreGive(sd_mutex);
return false;
}
bool success = (file.write(data, size) == size);
file.close();
xSemaphoreGive(sd_mutex);
if (success) {
Serial.printf("图像保存到: %s\n", filename);
sd_log((String("图像保存: ") + filename).c_str());
}
return success;
}
// 从SD卡读取图像
size_t sd_read_image(const char* filename, uint8_t* buffer, size_t max_size) {
if (!sd_ready || !buffer || max_size == 0) return 0;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return 0;
File file = SD.open(filename, FILE_READ);
if (!file) {
xSemaphoreGive(sd_mutex);
return 0;
}
size_t size = file.size();
if (size > max_size) size = max_size;
size_t read = file.read(buffer, size);
file.close();
xSemaphoreGive(sd_mutex);
return read;
}
#endif
/* -------------------------- 图像处理函数 -------------------------- */
bool convert_jpeg_to_rgb888_from_sd(const char* jpeg_path, uint8_t* rgb_data, size_t rgb_size) {
if (!sd_ready || !rgb_data) return false;
File file = SD.open(jpeg_path, FILE_READ);
if (!file) return false;
size_t jpeg_size = file.size();
if (jpeg_size == 0 || jpeg_size > 1024*200) {
file.close();
return false;
}
uint8_t* jpeg_buf = (uint8_t*)ps_malloc(jpeg_size);
if (!jpeg_buf) {
file.close();
return false;
}
size_t read = file.read(jpeg_buf, jpeg_size);
file.close();
if (read != jpeg_size) {
free(jpeg_buf);
return false;
}
bool success = fmt2rgb888(jpeg_buf, jpeg_size, PIXFORMAT_JPEG, rgb_data);
free(jpeg_buf);
return success;
}
// 备用缩放函数(当默认缩放失败时使用)
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;
}
if (((uintptr_t)dst) % 4 != 0) {
Serial.println("错误:输出缓冲区未4字节对齐");
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;
Serial.println("\n===== 摄像头初始化 =====");
camera_mutex = xSemaphoreCreateMutex();
#ifdef ENABLE_SD_CARD
// 初始化SD卡(允许失败,不影响主功能)
if (!sd_init()) {
Serial.println("SD卡初始化失败,继续启动系统");
}
#endif
gpio_uninstall_isr_service();
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));
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("摄像头初始化失败: ") + esp_err_to_name(err)).c_str());
}
#endif
if (err == ESP_ERR_INVALID_STATE) {
gpio_uninstall_isr_service();
}
init_retry++;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
if (err != ESP_OK) {
Serial.println("摄像头初始化彻底失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("摄像头初始化彻底失败");
}
#endif
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);
}
size_t model_size = MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3;
model_size = (model_size + 3) & ~3; // 4字节对齐
model_buf = (uint8_t*)aligned_alloc(4, model_size);
if (!model_buf) {
Serial.println("模型缓冲区分配失败");
camera_deinit();
return false;
}
Serial.printf("模型缓冲区: %zu bytes\n", model_size);
is_initialised = true;
Serial.println("摄像头初始化成功");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("摄像头初始化成功");
}
#endif
return true;
}
void camera_deinit() {
if (!is_initialised) return;
esp_camera_deinit();
if (model_buf) free(model_buf);
model_buf = NULL;
is_initialised = false;
}
bool camera_capture_to_sd() {
if (!is_initialised) return false;
if (xSemaphoreTake(camera_mutex, 2000 / portTICK_PERIOD_MS) != pdTRUE) {
return false;
}
bool success = false;
fb = esp_camera_fb_get();
if (fb && fb->len > 1024) {
#ifdef ENABLE_SD_CARD
if (sd_ready) {
success = sd_save_image(fb->buf, fb->len);
}
#endif
esp_camera_fb_return(fb);
fb = NULL;
}
xSemaphoreGive(camera_mutex);
return success;
}
bool process_image_from_sd(uint8_t* model_buf, size_t model_size) {
if (!sd_ready || !model_buf) return false;
File dir = SD.open("/images");
if (!dir) return false;
File latest_file;
time_t latest_time = 0;
while (File file = dir.openNextFile()) {
if (!file.isDirectory() && strstr(file.name(), ".jpg")) {
time_t t = file.getLastWrite();
if (t > latest_time) {
latest_time = t;
if (latest_file) latest_file.close();
latest_file = file;
} else {
file.close();
}
}
}
dir.close();
if (!latest_file) return false;
const char* img_path = latest_file.path();
latest_file.close();
size_t rgb_size = EI_CAMERA_COLS * EI_CAMERA_ROWS * 3;
uint8_t* rgb_buf = (uint8_t*)ps_malloc(rgb_size);
if (!rgb_buf) return false;
bool success = false;
if (convert_jpeg_to_rgb888_from_sd(img_path, rgb_buf, rgb_size)) {
success = ei::image::processing::crop_and_interpolate_rgb888(
rgb_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
model_buf, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT
);
if (!success) {
success = backup_resize_rgb888(
rgb_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
model_buf, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT
);
}
}
free(rgb_buf);
return success;
}
/* -------------------------- 推理核心 -------------------------- */
#ifdef ENABLE_INFERENCE
void run_inference() {
if (!is_initialised || !model_buf) {
oled_print("未就绪", "设备异常");
return;
}
Serial.println("\n===== 开始推理 =====");
oled_print("识别中...");
if (!camera_capture_to_sd()) {
oled_print("处理失败", "图像捕获失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("图像捕获失败");
}
#endif
return;
}
if (!process_image_from_sd(model_buf, MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3)) {
oled_print("处理失败", "图像转换失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("图像转换失败");
}
#endif
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;
if (pixel_ix + 3*length > MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3) {
Serial.println("错误:模型输入数据越界");
return -1;
}
for (size_t i = 0; i < length; i++) {
uint8_t r = model_buf[pixel_ix];
uint8_t g = model_buf[pixel_ix + 1];
uint8_t b = model_buf[pixel_ix + 2];
out[i] = (r - 127.5f) / 127.5f;
out[i + length] = (g - 127.5f) / 127.5f;
out[i + length * 2] = (b - 127.5f) / 127.5f;
pixel_ix += 3;
}
return 0;
};
ei_impulse_result_t result;
memset(&result, 0, sizeof(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());
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("推理失败: ") + String(err)).c_str());
}
#endif
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());
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("识别结果: ") + max_label + " (" + String(max_prob*100) + "%)").c_str());
}
#endif
}
#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();
#ifdef ENABLE_SD_CARD
if (sd_ready) {
File dir = SD.open("/images");
File latest_file;
while (File file = dir.openNextFile()) {
if (!file.isDirectory() && strstr(file.name(), ".jpg")) {
if (!latest_file || file.getLastWrite() > latest_file.getLastWrite()) {
if (latest_file) latest_file.close();
latest_file = file;
} else {
file.close();
}
}
}
dir.close();
if (latest_file) {
size_t fileSize = latest_file.size();
const size_t bufferSize = 1024;
uint8_t buffer[bufferSize];
while (fileSize > 0) {
size_t read = latest_file.read(buffer, min(bufferSize, fileSize));
client.write(buffer, read);
fileSize -= read;
}
latest_file.close();
} else {
client.print("无图像文件");
}
} else {
client.print("SD卡未就绪");
}
#else
client.print("SD卡功能未启用");
#endif
} 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();
run_inference();
client.println("推理已触发");
} else if (req.startsWith("GET /")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println("<html><body>");
client.println("ESP32 识别系统");
client.println("查看最新照片
");
client.println("运行识别
");
client.println("</body></html>");
} else {
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
client.println("404 Not Found");
}
delay(1);
client.stop();
}
#endif
/* -------------------------- WiFi连接 -------------------------- */
void connect_wifi() {
if (wifi_connected) return;
Serial.println("\n===== WiFi连接 =====");
oled_print("连接WiFi...", ssid);
WiFi.begin(ssid, password);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 20) {
delay(500);
Serial.print(".");
attempt++;
}
if (WiFi.status() == WL_CONNECTED) {
wifi_connected = true;
Serial.println("\nWiFi连接成功");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
oled_print("WiFi已连接", WiFi.localIP().toString().c_str());
#ifdef ENABLE_HTTP_SERVER
server.begin();
Serial.println("HTTP服务启动");
#endif
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("WiFi连接成功 - IP: ") + WiFi.localIP().toString()).c_str());
}
#endif
} else {
Serial.println("\nWiFi连接失败");
oled_print("WiFi连接失败", "请检查配置");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("WiFi连接失败");
}
#endif
}
}
/* -------------------------- 初始化和主循环 -------------------------- */
void setup() {
Serial.begin(115200);
Serial.println("Edge Impulse 识别系统启动中");
// 初始化OLED
#ifdef ENABLE_OLED_DISPLAY
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 OLED显示屏初始化失败"));
} else {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Edge Impulse");
display.println("识别系统启动中");
display.display();
}
#endif
// 初始化时间
init_time();
// 初始化摄像头
if (!camera_init()) {
oled_print("错误", "摄像头初始化失败");
delay(5000);
}
// 连接WiFi
connect_wifi();
// 启动看门狗
esp_task_wdt_init(10, false);
}
void loop() {
// 喂狗
esp_task_wdt_reset();
// 检查WiFi连接
if (!wifi_connected || WiFi.status() != WL_CONNECTED) {
wifi_connected = false;
connect_wifi();
}
// 处理HTTP请求
#ifdef ENABLE_HTTP_SERVER
if (wifi_connected) {
WiFiClient client = server.available();
if (client) {
handle_client(client);
}
}
#endif
// 主循环逻辑
static unsigned long last_inference_time = 0;
if (millis() - last_inference_time > 10000) { // 每10秒运行一次推理
last_inference_time = millis();
#ifdef ENABLE_INFERENCE
run_inference();
#endif
}
delay(100);
}









