ESP32 XIAO——串口发送图像数据及Python接收处理

在嵌入式开发中,经常会涉及到图像数据的传输与处理。本文将介绍如何使用 ESP32 采集图像数据并通过串口发送,以及使用 Python 程序接收和保存这些图像数据。

ESP32 端代码

首先,在 ESP32 的代码中,我们引入了相关的库和头文件,包括 esp_camera.hFS.hSD.hSPI.h 等。

通过定义一些变量来记录拍摄时间、文件计数器以及相机和 SD 卡的状态。

关键的函数 photo_save 用于拍摄照片、保存到 SD 卡,并通过串口发送图像数据。在发送时,先发送 START_JPEG 标记,然后发送图像数据,最后发送 END_JPEG 标记。

writeFile 函数用于将图像数据写入 SD 卡文件。

setup 函数中,进行了串口、相机和 SD 卡的初始化设置。

loop 函数中,当相机和 SD 卡都准备好时,每隔一分钟拍摄一张照片并进行处理。

#include "esp_camera.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM
#include "camera_pins.h"

unsigned long lastCaptureTime = 0; // Last shooting time
int imageCount = 1;                // File Counter
bool camera_sign = false;          // Check camera status
bool sd_sign = false;              // Check sd status

// 新增UART相关配置
#define UART_TX_PIN    GPIO_NUM_43   // 根据实际硬件连接修改
#define UART_RX_PIN    GPIO_NUM_44   // 根据实际硬件连接修改
#define UART_BAUD_RATE 115200

// Save pictures to SD card and send via UART
void photo_save(const char * fileName) {
  // Take a photo
  camera_fb_t *fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Failed to get camera frame buffer");
    return;
  }

  // Save photo to file
  writeFile(SD, fileName, fb->buf, fb->len);

  // Send image data via UART
  // Serial1.write((uint8_t *)fb->buf, fb->len); // 发送图像数据
  // Serial1.write("START_JPEG");
  // Serial1.write((uint8_t *)fb->buf, fb->len);
  // Serial1.write("END_JPEG");
  Serial1.write((uint8_t *)"START_JPEG", 10); // 发送 START_JPEG 标记
  Serial1.write((uint8_t *)fb->buf, fb->len); // 发送图像数据
  Serial1.write((uint8_t *)"END_JPEG", 8);    // 发送 END_JPEG 标记

  Serial.println("Photo saved to file and sent via UART");

  // Release image buffer
  esp_camera_fb_return(fb);
}

// SD card write file
void writeFile(fs::FS &fs, const char * path, uint8_t * data, size_t len){
    Serial.printf("Writing file: %s\r\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);
  while(!Serial); // When the serial monitor is turned on, the program starts to execute
  Serial1.begin(UART_BAUD_RATE, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN); // 使用GPIO44(RX), GPIO43(TX)

  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; // for streaming
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if(config.pixel_format == PIXFORMAT_JPEG){
    if(psramFound()){
      config.jpeg_quality = 10;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;
    } else {
      // Limit the frame size when PSRAM is not available
      config.frame_size = FRAMESIZE_SVGA;
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
#endif
  }

  // camera init
  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; // Camera initialization check passes

  // Initialize SD card
  if(!SD.begin(21)){
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();
  // Determine if the type of SD card is available
  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 initialization check passes
  Serial.println("Photos will begin in one minute, please be ready.");
}

void loop() {
  // Camera & SD available, start taking pictures
  if(camera_sign && sd_sign){
    // Get the current time
    unsigned long now = millis();

    // If it has been more than 1 minute since the last shot, take a picture and save it to the SD card
    if ((now - lastCaptureTime) >= 6000) {
      char filename[32];
      sprintf(filename, "/image%d.jpg", imageCount);
      photo_save(filename);
      Serial.printf("Saved picture: %s\r\n", filename);
      Serial.println("Photos will begin in one minute, please be ready.");
      imageCount++;
      lastCaptureTime = now;
    }
  }
}

Python 端代码

在 Python 代码中,首先配置串口参数,包括端口、波特率和超时时间。

定义了图像的起始和结束标记。

通过 serial.Serial 打开串口。

image_data 用于存储图像数据缓冲区。

save_image 函数用于将接收到的图像数据保存为文件。

read_serial 函数不断从串口读取数据,将数据追加到缓冲区,然后检查是否接收到完整的图像数据,提取并保存图像,清除已处理的数据。

import serial
import time

# 配置串口参数
SERIAL_PORT = 'COM7'  # 根据实际连接修改
BAUD_RATE = 115200    # 波特率,与 Arduino 的 UART_BAUD_RATE 一致
TIMEOUT = 3           # 超时时间(秒)

# 图像起始和结束标记
START_MARKER = b"START_JPEG"
END_MARKER = b"END_JPEG"

# 打开串口
try:
    ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=TIMEOUT) # 设置buffer很大
    print(f"Serial port {SERIAL_PORT} opened successfully.")
except Exception as e:
    print(f"Failed to open serial port: {e}")
    exit(1)

# 图像数据缓冲区
image_data = bytearray()

# 文件计数器
file_count = 1

def save_image(data):
    """将接收到的图像数据保存为文件"""
    global file_count
    filename = f"received_image_{file_count}.jpg"
    with open(filename, "wb") as f:
        f.write(data)
    print(f"Image saved as {filename}")
    file_count += 1

def read_serial():
    """从串口读取数据并处理"""
    global image_data
    try:
        while True:
            # 读取串口数据
            chunk = ser.read(ser.in_waiting or 1)  # 读取当前可用的所有字节
            # 读取串口的所有数据
            if not chunk:
                continue
            
            # 将数据追加到缓冲区
            image_data.extend(chunk)
            
            # print(f"Buffer content: {len(image_data)}")
            
            # 检查是否接收到完整的图像数据
            while True:
                start_index = image_data.find(START_MARKER)  # 查找起始标记
                end_index = image_data.find(END_MARKER)      # 查找结束标记
                
                # 如果start_index > end_index,说明开始的图像数据被截断,直接丢弃
                if start_index != -1 and end_index != -1 and start_index > end_index:
                    print(f"invaild data, discarding, from {start_index} start to {end_index} end")
                    image_data = image_data[start_index:]
                    start_index = 0
                    end_index = -1
                    t1 = time.time()
                
                # 提取图像
                if start_index != -1 and end_index != -1 and end_index > start_index:
                    # 提取完整的 JPEG 数据(去掉起始和结束标记)
                    jpeg_start = start_index + len(START_MARKER)
                    jpeg_end = end_index
                    complete_image = image_data[jpeg_start:jpeg_end]
                    
                    # 保存图像
                    save_image(complete_image)
                    
                    print(f"Received complete image, size: {len(complete_image)}, cost time: {1000*(time.time() - t1):.2f} ms")
                    
                    # 清除已处理的数据(包括 START_JPEG 之前的所有数据)
                    image_data = image_data[end_index + len(END_MARKER):]
                    start_index = -1
                    end_index = -1
                    t1 = time.time()  # 重置计时器
                else:
                    break

    except KeyboardInterrupt:
        print("Program terminated by user.")
    finally:
        ser.close()
        print("Serial port closed.")

if __name__ == "__main__":
    print("Waiting for image data from Arduino...")
    read_serial()

运行结果

Arduino 端输出了照片保存和发送的相关信息。

Python 端成功打开串口,接收到图像数据并保存为文件,输出了保存图像的相关信息,包括文件名、大小和处理时间等。

通过这样的方式,实现了 ESP32 与 Python 之间的图像数据传输和处理,为相关的嵌入式应用提供了一种可行的解决方案。

希望本文能对您在串口图像数据传输方面的开发有所帮助。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值