b8hqQHaWdEN1 发表于 2025-4-29 15:30:10

"ESP32-S3 AI摄像头"+DeepSeek—皮肤分析系统—灵犀镜

本帖最后由 b8hqQHaWdEN1 于 2025-4-29 22:21 编辑

【试用背景】最近发现DF创客社区发布了ESP32-S3 AI摄像头的试用,我便怀着激动的心情填写试用名单,最终有幸被选中,希望借此来表达我的想法和制作思路,同各位老师们一起探索。【概述】本项目灵感来源于童话世界中的魔镜,我命名为灵犀镜。该项目的核心是“ESP32-S3 AI摄像头”,利用自带的摄像头采集人脸图像信息并生成图片,借由Base64将图片转化为数据信息,最后传输给DeepSeek大模型返回识别的数据,并在串口监视器显示数据。【功能点】灵犀镜正如童话的魔镜一般,(告诉你谁才是这个世界最美丽的人),可以利用“ESP32-S3 AI摄像头”采集图片,交由DeepSeek大模型的强大图像分析能力来识别当前人脸状态,如:黑眼圈,皮肤状态,护肤建议等等,能成为各位的护肤专家。【未来展望】目前该项目的连接网络和发送deepseek等功能都是基于“ESP32-S3 AI摄像头”独立运行,但是串口数据还是要借助电脑串口监视器实现,也无法查看图片信息等,只能通过网络base64编码转图片才能查看,没有真正脱离电脑,目前有两个发展方向:1.可以借助其他显示屏显示数据,如:行空板或者其他显示屏等;2.进一步添加语音识别和语音合成,可以实现对话功能,拓展使用范围。【功能实现】一.准备工作 1.下载Arduino IDE,下载esp主板,这部分可以在产品wiki实现,因此不在赘述。2.获取你的deepseek api key,用于调用deepseek:浏览器打开deepseek的官网(DeepSeek | 深度求索),选择api开放平台,deepseek对话的是免费,但是调用api要收费,也不用担心,几块钱可以用很久 点击充值,随便多少都行,随后在api文档创建新的api,该api只有在创建第一次才能看见,请牢记你的api key。
3. 测试deepseek连通性:复制如下代码,在其中更改wifi,密码,和deepseek api,并上传程序#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

const char* ssid = "";   //替换你的wifi
const char* password = "";//替换你的密码
const char* apiKey = "";   //替换你的api
const char* host = "api.deepseek.com";
const int httpsPort = 443;

// 超时配置
const unsigned long wifiTimeout = 15000;
const unsigned long responseTimeout = 15000;

WiFiClientSecure client;

void setup() {
Serial.begin(115200);
Serial.println("测试deepseek连接\n");

// 完全禁用证书验证
client.setInsecure();

connectWiFi();
}

void loop() {
if (Serial.available()) {
    String question = Serial.readStringUntil('\n');
    question.trim();
   
    if (question.length() > 0) {
      // 显示用户问题
      Serial.print("\n[用户问题] ");
      Serial.println(question);
      Serial.println("[正在发送分析请求...]");
      
      bool result = sendRequest(question);
      
      if (!result) {
      Serial.println("[请求失败,请检查连接]");
      }
      Serial.println("\n[请输入新问题]");
    }
}
}

void connectWiFi() {
WiFi.begin(ssid, password);
Serial.print("正在连接WiFi");

unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
   
    if (millis() - start > wifiTimeout) {
      Serial.println("\n连接超时!");
      ESP.restart();
    }
}
Serial.println("\nWiFi连接成功!");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
}

bool sendRequest(String prompt) {
if (!client.connect(host, httpsPort)) {
    Serial.println("服务器连接失败");
    return false;
}

// 构建HTTP请求
String request = createRequest(prompt);
client.print(request);

// 处理响应
return processResponse();
}

String createRequest(String prompt) {
DynamicJsonDocument doc(2048);// 增加文档大小以适应系统提示
doc["model"] = "deepseek-chat";
doc["stream"] = true;
doc["temperature"] = 0.3;      // 添加温度参数

JsonArray messages = doc.createNestedArray("messages");

// 系统角色设定
JsonObject systemMsg = messages.createNestedObject();
systemMsg["role"] = "system";
systemMsg["content"] = "你是deepseek,是一个乐于助人的助手";

// 用户问题
JsonObject userMsg = messages.createNestedObject();
userMsg["role"] = "user";
userMsg["content"] = prompt;

String body;
serializeJson(doc, body);

return String("POST /v1/chat/completions HTTP/1.1\r\n") +
         "Host: " + host + "\r\n" +
         "Authorization: Bearer " + apiKey + "\r\n" +
         "Content-Type: application/json\r\n" +
         "Accept: text/event-stream\r\n" +
         "Connection: close\r\n" +
         "Content-Length: " + body.length() + "\r\n\r\n" +
         body;
}

bool processResponse() {
unsigned long lastData = millis();
bool gotResponse = false;

while (client.connected() || client.available()) {
    if (client.available()) {
      String line = client.readStringUntil('\n');
      line.trim();
      lastData = millis();

      if (line.startsWith("data: ")) {
      String json = line.substring(6);
      if (json == "") break;

      DynamicJsonDocument doc(1024);
      if (deserializeJson(doc, json)) break;

      const char* content = doc["choices"]["delta"]["content"];
      if (content) {
          Serial.print(content);
          gotResponse = true;
      }
      }
    } else {
      if (millis() - lastData > responseTimeout) {
      Serial.println("\n响应超时");
      break;
      }
      delay(10);
    }
}

client.stop();
return gotResponse;
}
打开串口监视器发送问题,稍等就能得到回复遇到网络或者服务器问题不回复可以按rst重试几次。回复如下:
二.代码实现 测试没问题就可以复制如下代码:#include "esp_camera.h"
#include <WiFi.h>
#include <base64.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

// 摄像头引脚定义
#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
#define BUTTON_PIN      0

// WiFi配置
const char* ssid = "";
const char* password = "";
const char* apiKey = "";
const char* host = "api.deepseek.com";
const int httpsPort = 443;

// 超时配置
const unsigned long wifiTimeout = 15000;
const unsigned long responseTimeout = 15000;

WiFiClientSecure client;

void setup() {
Serial.begin(115200);
while(!Serial); // 串口连接

pinMode(BUTTON_PIN, INPUT_PULLUP);

// 摄像头配置
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_240X240;
config.pixel_format = PIXFORMAT_JPEG;
config.jpeg_quality = 10;
config.fb_count = 1;

// 初始化摄像头
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
    Serial.printf("摄像头初始化失败: 0x%x\n", err);
    return;
}

// 完全禁用证书验证
client.setInsecure();

connectWiFi();
Serial.println("\n系统就绪,按下BOOT按钮进行皮肤分析");
}

void loop() {
static uint32_t lastPress = 0;
if(digitalRead(BUTTON_PIN) == LOW && millis() - lastPress > 1000) {
    lastPress = millis();
    String imageData = captureToBase64();
    if(imageData.length() > 0){
      sendAnalysisRequest(imageData);
    }
    while(digitalRead(BUTTON_PIN) == LOW) delay(10);
}
delay(10);
}

String captureToBase64() {
camera_fb_t *fb = esp_camera_fb_get();
if(!fb || !fb->buf || fb->len == 0) {
    Serial.println("捕获失败");
    if(fb) esp_camera_fb_return(fb);
    return "";
}

// 直接编码JPEG数据为Base64
String base64Data = base64::encode(fb->buf, fb->len);
esp_camera_fb_return(fb);

return "data:image/jpeg;base64," + base64Data;
}

void connectWiFi() {
WiFi.begin(ssid, password);
Serial.print("正在连接WiFi");

unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
   
    if (millis() - start > wifiTimeout) {
      Serial.println("\n连接超时!");
      ESP.restart();
    }
}
Serial.println("\nWiFi连接成功!");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
}

void sendAnalysisRequest(String imageData) {
if (!client.connect(host, httpsPort)) {
    Serial.println("服务器连接失败");
    return;
}

// 构建请求内容
String prompt = "分析该人物的皮肤状态:" + imageData;

// 构建HTTP请求
String request = createRequest(prompt);
client.print(request);

// 处理响应
processResponse();
}

String createRequest(String prompt) {
DynamicJsonDocument doc(24576); // Base64数据长度
doc["model"] = "deepseek-chat";
doc["stream"] = true;
doc["temperature"] = 0.3;

JsonArray messages = doc.createNestedArray("messages");

// 系统角色设定
JsonObject systemMsg = messages.createNestedObject();
systemMsg["role"] = "system";
systemMsg["content"] = "你是灵犀镜,专业皮肤科分析助手,用简洁中文指出面部问题并提供建议,格式:问题发现+建议列表";

// 用户消息
JsonObject userMsg = messages.createNestedObject();
userMsg["role"] = "user";
userMsg["content"] = prompt;

String body;
serializeJson(doc, body);

return String("POST /v1/chat/completions HTTP/1.1\r\n") +
         "Host: " + host + "\r\n" +
         "Authorization: Bearer " + apiKey + "\r\n" +
         "Content-Type: application/json\r\n" +
         "Accept: text/event-stream\r\n" +
         "Connection: close\r\n" +
         "Content-Length: " + body.length() + "\r\n\r\n" +
         body;
}

void processResponse() {
Serial.println("\n[皮肤分析结果]");
unsigned long lastData = millis();
bool gotResponse = false;

while (client.connected() || client.available()) {
    if (client.available()) {
      String line = client.readStringUntil('\n');
      line.trim();
      lastData = millis();

      if (line.startsWith("data: ")) {
      String json = line.substring(6);
      if (json == "") break;

      DynamicJsonDocument doc(1024);
      if (deserializeJson(doc, json)) break;

      const char* content = doc["choices"]["delta"]["content"];
      if (content) {
          Serial.print(content);
          gotResponse = true;
      }
      }
    } else {
      if (millis() - lastData > responseTimeout) {
      Serial.println("\n响应超时");
      break;
      }
      delay(10);
    }
}

client.stop();
if (!gotResponse) Serial.println("未收到有效响应");
Serial.println("\n[分析完成,可再次拍照]");
}
步骤如下
1.在Arduino IDE中选择File->Examples->ESP32->Camera->CameraWebServer示例
2.使用下面的代码替换CameraWebServer中的代码,只要替换主程序即可(注意:需要填入WIFI账号密码和api key)3. 按下“ESP32-S3 AI摄像头”boot按键,自动采集图片4. 图片自动压缩并转为base64格式,发送deepseek大模型,目前测试deepseek可以识别图片base64格式5. 打开串口监视器返回皮肤情况+护肤建议等



https://www.bilibili.com/video/BV1AsGmzREBr?buvid=XUB283FB9990F888F282764E1B889A1689536&from_spmid=dt.space-dt.video.0&is_story_h5=false&mid=B%2B%2Bap2FLB1nam6J37LLtxQ%3D%3D&plat_id=116&share_from=ugc&share_medium=android&share_plat=android&share_session_id=226174c7-4c01-416a-a711-814efdfd0da5&share_source=COPY&share_tag=s_i&spmid=united.player-video-detail.0.0×tamp=1745936353&unique_k=oIkcj3w&up_id=184951246





页: [1]
查看完整版本: "ESP32-S3 AI摄像头"+DeepSeek—皮肤分析系统—灵犀镜