基于XIAO ESP32S3 Sense 的最小 DIY 相机

科技   2024-09-18 13:30   广东  



故事背景

我一直对间谍小工具和微型电子产品非常着迷。我一直想创造一个可以装在口袋里的微型相机,能够悄无声息地捕捉精彩瞬间。随着技术的进步和像 Xiao ESP32S3 Sense 这样功能强大的微控制器的出现,我终于有机会实现这个梦想了。

使用实际部署模型的设备为 tinyML 项目收集图像数据通常也是一项挑战。因此,这款相机也是远程图像数据收集的有用设备。

这个名为 “The Smallest DIY Spy Cam ”的项目是一个可以自己制作的小型摄像头。它既简单又经济实惠,是进入嵌入式电子世界的绝佳方式。

我们使用的 Xiao ESP32S3 Sense 具有体积小、功耗低、可扩展摄像头模块(sense)和功能强大等特点,非常适合制作最小的 DIY 间谍摄像头。

 1 DIY 微型摄像头

材料清单

硬件类

Seeed Studio XIAO ESP32S3 Sens

Li-Ion Battery 100mAh
软件类

Arduino IDE

工具类

Solder Wire, Lead Free

Soldering iron (generic)


 2 XIAO

特点

  • 外形小巧: 这款相机采用 Xiao ESP32S3 Sense,体积非常小巧,易于隐藏。

  • 按下按钮即可捕捉图像 专用按钮可让您立即拍照。

  • 自动图像命名: 每张图片都以顺序文件名(image1image2image3 等)保存,因此您永远不会丢失捕获的图片。

  • SD 卡存储: 图像可直接保存到 SD 卡中,便于传输到电脑上。

  • 省电模式: 长按按钮可使设备进入深度睡眠状态,耗电量极低。当再次检测到长按按钮时,它会唤醒并打开 LED 指示灯。

组件:

 3 组件


  • Xiao ESP32S3 Sense 操作的大脑,提供处理能力和连接性。

  • XiaoCamera 模块:捕捉高质量图像。

  • Micro SD 卡模块:存储拍摄的图像。

  • 按钮:用于图像捕捉和电源控制。

  • 锂电池:为相机供电。

  • 电线: 将各部件连接在一起所必需的电线。


硬件组装


  • 首先将摄像头模块连接到 Xiao ESP32S3 Sense

  • 然后,连接 SD 卡模块。确保接线正确,以免在数据存储过程中出现任何问题。

 4 XIAO 部分引脚

  • 将捕捉按钮连接到 Xiao 的 GPIO 引脚 D0。

  • 将锂电池焊接到 Xiao 的电池焊盘上,以获得便携式电源。

 5 组装部件

因为我用的是廉价烙铁,所以焊接得不是很整齐,但这并不重要。您需要用 3D 打印相机的外壳。我附上了打印用的 STL 文件。这是完全组装好的相机与 20 KSH(可能只有一分钱大小)的对比。

 6 微型相机与一分钱硬币对比图


软件配置

  • 如果还没有 Arduino IDE,请下载。

  • 克隆此项目的 GitHub 仓库。

  • Arduino IDE 中打开项目,并从板管理器中选择 Xiao ESP32S3 板。

以下是代码

这个 camera.ino 代码只是在按下按钮时捕捉图像,并按顺序将图像保存在 SD 卡中。

//camera.ino

#include "esp_camera.h"#include "FS.h"#include "SD.h"#include "SPI.h"// 使用 XIAO_ESP32S3 模型摄像头,具备 PSRAM// 摄像头引脚定义#define PWDN_GPIO_NUM     -1#define RESET_GPIO_NUM    -1#define XCLK_GPIO_NUM 10#define SIOD_GPIO_NUM 40#define SIOC_GPIO_NUM 39#define Y9_GPIO_NUM 48#define Y8_GPIO_NUM 11#define Y7_GPIO_NUM 12#define Y6_GPIO_NUM 14#define Y5_GPIO_NUM 16#define Y4_GPIO_NUM 18#define Y3_GPIO_NUM 17#define Y2_GPIO_NUM 15#define VSYNC_GPIO_NUM 38#define HREF_GPIO_NUM 47#define PCLK_GPIO_NUM 13#define LED_GPIO_NUM 21#define capturePin      D0//采集按键引脚unsigned long lastCaptureTime = 0; // 上次拍摄时间int imageCount = 1;                // 图片文件计数bool camera_sign = false;          // 摄像头状态标志bool sd_sign = false;              // SD卡状态标志bool captureFlag = false;// 保存图片到SD卡void photo_save(const char * fileName) {// 拍摄照片camera_fb_t *fb = esp_camera_fb_get();if (!fb) {Serial.println("Failed to get camera frame buffer");return;}// 将照片保存到文件writeFile(SD, fileName, fb->buf, fb->len);// 释放图像缓冲区esp_camera_fb_return(fb);Serial.println("Photo saved to file");}// 写入数据到SD卡void writeFile(fs::FS &fs, const char * path, uint8_t * data, size_t len){Serial.printf("Writing file: %s\n", path);File file = fs.open(path, FILE_WRITE);if(!file){Serial.println("Failed to open file for writing");return;}if(file.write(data, len) == len){Serial.println("File written");} else {Serial.println("Write failed");}file.close();}void setup() {Serial.begin(115200);pinMode(capturePin, 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_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.frame_size = FRAMESIZE_UXGA;config.pixel_format = PIXFORMAT_JPEG; // 设置为JPEG格式config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;config.fb_location = CAMERA_FB_IN_PSRAM;config.jpeg_quality = 12;config.fb_count = 1;// 如果存在 PSRAM IC,则以 UXGA 分辨率和更高的 JPEG 质量启动// 用于更大的预分配帧缓冲区。if(config.pixel_format == PIXFORMAT_JPEG){if(psramFound()){config.jpeg_quality = 10;config.fb_count = 2;config.grab_mode = CAMERA_GRAB_LATEST;} else {// 没有PSRAM时降低分辨率config.frame_size = FRAMESIZE_SVGA;config.fb_location = CAMERA_FB_IN_DRAM;}} else {// 人脸检测时使用的分辨率config.frame_size = FRAMESIZE_240X240;#if CONFIG_IDF_TARGET_ESP32S3config.fb_count = 2;#endif}//初始化摄像头esp_err_t err = esp_camera_init(&config);if (err != ESP_OK) {Serial.printf("Camera init failed with error 0x%x", err);return;}camera_sign = true; // 摄像头初始化成功标志// 初始化SD卡if(!SD.begin(21)){Serial.println("Card Mount Failed");return;}uint8_t cardType = SD.cardType();// 检测SD卡类型if(cardType == CARD_NONE){Serial.println("No SD card attached");return;}Serial.print("SD Card Type: ");if(cardType == CARD_MMC){Serial.println("MMC");} else if(cardType == CARD_SD){Serial.println("SDSC");} else if(cardType == CARD_SDHC){Serial.println("SDHC");} else {Serial.println("UNKNOWN");}sd_sign = true; // SD卡初始化成功标志Serial.println("*** XIAO ESP32S3 Spy Camera ***");Serial.println("Press button to capture and save an image\n");}void loop() {take_pic();}void take_pic(){if(camera_sign && sd_sign){if (digitalRead(capturePin) == 0) { //检测按键是否按下delay(200); //消抖延时Serial.println("\nImage Captured");char filename[32];sprintf(filename, "/image%d.jpg", imageCount);photo_save(filename);Serial.printf("Saved picture:%s\n", filename);Serial.println("");imageCount++;}}}


高效捕捉图像

在这个增强版代码中:

  • 单击:设备捕捉图像并将其保存到 SD 卡中。每张图像都按顺序命名,可避免在相机打开(从深度睡眠模式唤醒)时覆盖之前的图像。

  • 长按:使设备进入深度休眠状态,通过关闭不必要的进程有效节约电能。再次长按将唤醒设备,使其能够继续捕捉图像。

这一功能使设备特别适用于长期使用或在偏远地区进行图像数据采集,因为在这些地区持续供电是不现实的。这对 TinyML 图像数据采集尤其有价值,它能使设备在需要时保持休眠状态,从而大大延长了设备在野外的电池寿命。

深度睡眠模式代码

//DeepSleep.ino

#include "esp_camera.h"#include "FS.h"#include "SD.h"#include "SPI.h"#include <Preferences.h>#define PWDN_GPIO_NUM     -1#define RESET_GPIO_NUM    -1#define XCLK_GPIO_NUM 10#define SIOD_GPIO_NUM 40#define SIOC_GPIO_NUM 39#define Y9_GPIO_NUM 48#define Y8_GPIO_NUM 11#define Y7_GPIO_NUM 12#define Y6_GPIO_NUM 14#define Y5_GPIO_NUM 16#define Y4_GPIO_NUM 18#define Y3_GPIO_NUM 17#define Y2_GPIO_NUM 15#define VSYNC_GPIO_NUM 38#define HREF_GPIO_NUM 47#define PCLK_GPIO_NUM 13#define LED_GPIO_NUM 21#define capturePin        D0#define LEDAnode         D5#define statusLED         D6#define captureLED        D4Preferences preferences;  // 用于存储非易失性数值的首选项对象unsigned long lastPressTime = 0;  // 最后一次按下按钮的开始时间unsigned long pressDuration = 0;  // 按下按钮的持续时间int imageCount = 1;  // 文件计数器bool camera_sign = false; // 检查摄像机状态bool sd_sign = false;  // 检查SD卡状态void photo_save(const char * fileName) {// 拍摄照片camera_fb_t *fb = esp_camera_fb_get();if (!fb) {Serial.println("Failed to get camera frame buffer");return;}// 将照片保存到文件writeFile(SD, fileName, fb->buf, fb->len);// 释放图像缓冲区esp_camera_fb_return(fb);Serial.println("Photo saved to file");}void writeFile(fs::FS &fs, const char * path, uint8_t * data, size_t len){Serial.printf("Writing file: %s\n", path);File file = fs.open(path, FILE_WRITE);if(!file){Serial.println("Failed to open file for writing");return;}if(file.write(data, len) == len){Serial.println("File written");} else {Serial.println("Write failed");}file.close();}void setup() {Serial.begin(115200);pinMode(capturePin, INPUT_PULLUP);pinMode(statusLED, OUTPUT);  // 将 LED 引脚初始化为输出pinMode(LEDAnode, OUTPUT);pinMode(captureLED, OUTPUT);digitalWrite(LEDAnode, HIGH); //我使用的是共阳极 RGB LEDdigitalWrite(captureLED, HIGH);// 初始化首选项preferences.begin("camera", false);// 从非易失性存储器中读取存储的图像计数imageCount = preferences.getInt("imageCount", 1); // 如果未设置,默认为 1// 初始化相机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.frame_size = FRAMESIZE_UXGA;config.pixel_format = PIXFORMAT_JPEG;config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;config.fb_location = CAMERA_FB_IN_PSRAM;config.jpeg_quality = 12;config.fb_count = 1;if(config.pixel_format == PIXFORMAT_JPEG){if(psramFound()){config.jpeg_quality = 10;config.fb_count = 2;config.grab_mode = CAMERA_GRAB_LATEST;} else {config.frame_size = FRAMESIZE_SVGA;config.fb_location = CAMERA_FB_IN_DRAM;}} else {config.frame_size = FRAMESIZE_240X240;}esp_err_t err = esp_camera_init(&config);if (err != ESP_OK) {Serial.printf("Camera init failed with error 0x%x", err);return;}camera_sign = true;// Initialize SD cardif(!SD.begin(21)){Serial.println("Card Mount Failed");return;}uint8_t cardType = SD.cardType();if(cardType == CARD_NONE){Serial.println("No SD card attached");return;}sd_sign = true;Serial.println("*** XIAO ESP32S3 Spy Camera ***");Serial.println("Press and hold the button to enter deep sleep.");Serial.println("Press the button briefly to capture an image.");esp_sleep_enable_ext0_wakeup(static_cast<gpio_num_t>(capturePin), 0);digitalWrite(statusLED, LOW);digitalWrite(captureLED, HIGH);}void loop() {handleButtonPress();}void handleButtonPress() {int buttonState = digitalRead(capturePin);if (buttonState == LOW) {if (lastPressTime == 0) {lastPressTime = millis();  // 记录按压开始时间}pressDuration = millis() - lastPressTime;if (pressDuration > 2000) {  // 长按检测(>2 秒)Serial.println("Long press detected: Going to deep sleep");digitalWrite(statusLED, HIGH);delay(1000);  // 深度睡眠前去抖动的延迟goToDeepSleep();  // 进入深度睡眠}} else {if (lastPressTime > 0 && pressDuration > 100 && pressDuration < 1000) {  // 短按检测Serial.println("Short press detected: Taking picture");digitalWrite(captureLED, LOW);captureImage();digitalWrite(captureLED, HIGH);}lastPressTime = 0;  // 释放按钮时重置计时pressDuration = 0;}}void captureImage() {char filename[32];sprintf(filename, "/image%d.jpg", imageCount);photo_save(filename);  // 捕捉并记录图像Serial.printf("Saved picture: %s\n", filename);imageCount++;// 将新的图像计数存储在非易失性存储器中preferences.putInt("imageCount", imageCount);}void goToDeepSleep() {// 睡眠前存储图像计数preferences.putInt("imageCount", imageCount);preferences.end();  // 关闭首选项digitalWrite(statusLED, HIGH);esp_deep_sleep_start();  // 进入深度睡眠}
  • 将代码上传到您的 Xiao ESP32S3 Sense。


代码亮点

  • 初始化:在设置过程中对摄像机、SD 卡和 LED 进行初始化。图像计数从非易失性存储器中存储和检索,确保按顺序保存图像,而不会覆盖现有文件。

  • 图像捕捉:只需按下按钮即可捕捉图像并将其保存到 SD 卡中。

  • 深度睡眠和唤醒:使用定时机制检测长按。如果按住按钮超过 2 秒钟,设备就会进入深度睡眠状态。当设备唤醒时,LED 灯亮起,表明它已准备好再次捕捉图像。

深度睡眠功能

// 深度睡眠功能void goToDeepSleep() {  // 保存图像计数和其他数据  preferences.putInt("imageCount", imageCount);  preferences.end();  // 关闭首选项
// Turn off LED before going to sleep digitalWrite(LED_GPIO_NUM, LOW);
esp_deep_sleep_start(); //进入深度睡眠 }
  • 处理按键:按钮逻辑检查短按(捕捉图像)和长按(进入深度睡眠)。

cppCopy codevoid handleButtonPress() {  if (isButtonPressed()) {    if (lastPressTime == 0) {      lastPressTime = millis();  // 记录按压开始时间    }
pressDuration = millis() - lastPressTime;
if (pressDuration > 2000) { // 长按检测(>2 秒) Serial.println("Long press detected: Going to deep sleep"); delay(500); // 深度睡眠前去抖延迟 // 睡眠前关闭 LED digitalWrite(LED_GPIO_NUM, LOW); goToDeepSleep(); // 进入深度睡眠 } } else { if (lastPressTime > 0 && pressDuration > 200 && pressDuration < 1000) { // 短按检测 Serial.println("Short press detected: Taking picture"); captureImage(); }
lastPressTime = 0; // 释放按钮时重置计时 pressDuration = 0; }}

运行情况:

上传代码并连接好一切后,使用电源按钮打开相机。按下捕捉按钮拍照图像将以唯一的顺序文件名保存在 SD 卡上。

以下是摄像机运行时串行监视器的输出结果

 7 输出结果

图像将这样保存:

 8 图片保存样例

图库:

 9 微型摄像机外型图片(a

 10 微型摄像机外型图片(b

相机拍摄的图像:

 11 微型摄像机拍摄图片(a

 12 微型摄像机拍摄图片(b

未来改进:

虽然该项目功能齐全,但仍有改进的余地。以下是对未来版本的一些设想:

  • 视频录制:拓展功能,捕捉视频短片。

  • 无限传输:整合一项功能,以无线方式将图像发送到智能手机或者电脑

  • 移动侦测:添加移动侦测功能,在检测到移动时自动捕捉图像


定制部件和外壳

 13 微型摄像机左侧外壳

 14 微型摄像机右侧外壳(b

原理图

 15 XIAO 原理图





 Seeed Studio XIAO25xiao RP2040 








|XIAO

Doly

|Wio Termial

-MBO

|XIAO 

| Raspberry Pi 5 

|Raspberry Pi Zero

STM32500KHz

|LVGLOLEDFFT

|XIAO ESP32C3 

创客项目秀|基于Seeed XIAO 的智能宠物猫砂盆

创客项目秀|基于Wio Terminal的Meshwork

创客项目秀|基于Grove Vision AI2模块的边缘计算机视觉项目

创客项目秀|基于XIAOESP32S3 Sense的在线语音助手

创客项目秀|基于Seeed XIAO ESP32S3 sense的HA自动化鱼缸

创客项目秀|基于XIAO ESP32 sense的宠物猫检测项目



新一代信息技术赋能|人才升级|产业创新



柴火课程合集!

柴火驻场会员火热招募中!

Seeed Studio物联网设备试用中心落地柴火!


----END----

  Chaihuo x.factory|深圳,河北 






柴火创客空间
深度服务产业升级的国际化双创平台
 最新文章