使用ESPIDF驱动ov2640摄像头,python上位机显示数据

简介

由于手上有一块esp32cam,想要把esp32cam驱动起来,并且实现自己编写上位机来实现读取ov2640的数据。后续应该会将数据传输方式改为通过网络协议传输数据,传输速度更块,且可以更方便的去调试程序。

坑点

这里涉及到一些我完成这个项目时的一些坑点。

坑点是:刚开始我写出esp32cam捕捉图像数据后的代码使用串口发送,然后使用arduino串口软件及sscom串口助手可以读取单片机输出的程序,但是vscde的串行监视器插件无法读取串口数据(我还以为是esp32cam的串口还需要重新配置),后调试发现关闭硬件流控可以正常读取数据。

其实通过python上位机通过串口早就可以读取数据,但是我刚开始使用esp32cam上的usb串口发送数据,上位机程序去读取esp32发送的图像数据,发现一旦开始读取数据就会造成单片机的卡死,如何确定单片机程序是否卡死呢?我的做法是在发送串口数据的循环之前加入led闪烁指示灯 ,但是后来我觉得这样可能不太严谨,于是我将led指示灯闪烁创建为一个任务,因为freertos操作系统可以同时运行多个任务,这样就可以测试使用python程序读取串口数据时是否会死机。经测试确实一旦上位机开始读取数据会使单片机死机,后我想到使用usb转ttl模块去读取数据,发现数据可以正常读取。至此数据读取问题已解决,着手处理数据处理问题。 

上代码 

下面话不多说,直接上代码

第一版:波特率115200,传输的数据为16进制后转为二进制处理,也可实现,但是读取转化数据较慢,于是有了第二版,后面贴(此处引用了espressif__esp32-camera开源库,极大的加快了我的编码进度)

摄像头初始化代码

///////摄像头初始化.c文件
#include "mycamera.h"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "esp_camera.h"
/******************************************Camera module functions pin definitions***************************/
// ESP32Cam (AiThinker) PIN Map
#define CAM_PIN_PWDN 32 //掉电/省电模式,高电平有效                                          input
#define CAM_PIN_RESET -1 // software reset will be performed系统复位管脚,低电平有效         input
#define CAM_PIN_XCLK 0  //外部时钟输入端口,可接外部晶振                                     input
#define CAM_PIN_SIOD 26 //SCCB 总线的数据线,可类比 I2C 的 SDA                               io
#define CAM_PIN_SIOC 27 //SCCB 总线的时钟线,可类比 I2C 的 SCL                               input
//D0~D7 摄像头数据总线output
#define CAM_PIN_D7 35
#define CAM_PIN_D6 34
#define CAM_PIN_D5 39
#define CAM_PIN_D4 36
#define CAM_PIN_D3 21
#define CAM_PIN_D2 19
#define CAM_PIN_D1 18
#define CAM_PIN_D0 5
#define CAM_PIN_VSYNC 25    //帧同步信号            output
#define CAM_PIN_HREF 23   //行同步信号              output
#define CAM_PIN_PCLK 22 //像素同步时钟输出信号       output


static const char *TAG = "example:take_picture";

#if ESP_CAMERA_SUPPORTED
static camera_config_t camera_config = {
    .pin_pwdn = CAM_PIN_PWDN,
    .pin_reset = CAM_PIN_RESET,
    .pin_xclk = CAM_PIN_XCLK,
    .pin_sccb_sda = CAM_PIN_SIOD,
    .pin_sccb_scl = CAM_PIN_SIOC,

    .pin_d7 = CAM_PIN_D7,
    .pin_d6 = CAM_PIN_D6,
    .pin_d5 = CAM_PIN_D5,
    .pin_d4 = CAM_PIN_D4,
    .pin_d3 = CAM_PIN_D3,
    .pin_d2 = CAM_PIN_D2,
    .pin_d1 = CAM_PIN_D1,
    .pin_d0 = CAM_PIN_D0,
    .pin_vsync = CAM_PIN_VSYNC,
    .pin_href = CAM_PIN_HREF,
    .pin_pclk = CAM_PIN_PCLK,

    //XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental)
    .xclk_freq_hz = 20000000,
    .ledc_timer = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,

    .pixel_format = PIXFORMAT_JPEG, //YUV422,GRAYSCALE,RGB565,JPEG
    .frame_size = FRAMESIZE_QQVGA,    //QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates.

    .jpeg_quality = 30, //0-63, for OV series camera sensors, lower number means higher quality
    .fb_count = 1,       //When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode.
    .fb_location = CAMERA_FB_IN_PSRAM,
    .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};

 esp_err_t init_camera(void)
{
    //initialize the camera
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Camera Init Failed");
        return err;
    }
    ESP_LOGE(TAG, "Camera Init success!");

    return ESP_OK;
}
#endif

第一版

  • ESP-idf端的代码

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include "myled.h"
#include "sdkconfig.h"
#include "mycamera.h"
#include "driver/uart.h"
static const char *TAG = "example:take_picture";
void app_main(void)
{   
    
    myled_init();
    uart_init();
    xTaskCreate(myled_Blink_task, "myled_Blink_task", 2048, NULL, 5, NULL);
    init_camera();
     while (1) {
                vTaskDelay(pdMS_TO_TICKS(1000));
                ESP_LOGI(TAG, "Taking picture...");
                camera_fb_t *pic = esp_camera_fb_get();
       if (pic) {
                // 串口发送图片数据
                int i=pic->len;
                printf("IMG_START\n");//开始标志
                 for (int j = 0; j < i; ++j) {
                     printf("%02x ", pic->buf[j]);
                 }
                printf("\r\n");//结束  标志
                printf("IMG_END\n");
                ESP_LOGI(TAG, "Picture taken! Its size was: %zu bytes", pic->len);
                esp_camera_fb_return(pic);
        }
        else {
            ESP_LOGE(TAG, "Camera capture failed");
        }

    }
}

上位机端代码(使用python编写)

import serial
import tkinter as tk
from tkinter import messagebox, ttk
from PIL import Image, ImageTk, ImageFile
import io
import binascii
import time

ImageFile.LOAD_TRUNCATED_IMAGES = True

class SerialImageReceiver:
    def __init__(self, root):
        self.root = root
        self.root.title("串口图像接收器(平滑进度)")
        self.root.geometry("800x600")
        
        self.ser = None
        self.receiving = False
        self.image_data_hex = b''  # 有效图像数据(不含标志位)
        self.raw_buffer = b''      # 原始接收数据
        self.flag_detected = False # 开始标志是否已检测
        
        # 标志位配置
        self.start_flag = b'IMG_START\r\n'
        self.end_flag = b'IMG_END\r\n'
        
        # 进度相关参数(核心修改)
        self.estimated_total = 50000  # 预估总数据量(字节,可根据实际调整)
        self.received_bytes = 0       # 已接收的有效数据字节数
        self.waiting_progress = 0     # 等待阶段的进度(0-30%)
        
        self.setup_ui()
        print("程序启动,等待数据传输...")
    
    def setup_ui(self):
        # 串口配置区域(同之前)
        config_frame = ttk.LabelFrame(self.root, text="串口配置")
        config_frame.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(config_frame, text="端口:").grid(row=0, column=0, padx=5, pady=5)
        self.port_var = tk.StringVar(value="COM16")
        self.port_entry = ttk.Entry(config_frame, textvariable=self.port_var, width=10)
        self.port_entry.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(config_frame, text="波特率:").grid(row=0, column=2, padx=5, pady=5)
        self.baud_var = tk.StringVar(value="115200")
        self.baud_entry = ttk.Entry(config_frame, textvariable=self.baud_var, width=10)
        self.baud_entry.grid(row=0, column=3, padx=5, pady=5)
        
        self.start_btn = ttk.Button(config_frame, text="开始接收", command=self.start_receive)
        self.start_btn.grid(row=0, column=4, padx=5, pady=5)
        
        self.stop_btn = ttk.Button(config_frame, text="停止接收", command=self.stop_receive, state=tk.DISABLED)
        self.stop_btn.grid(row=0, column=5, padx=5, pady=5)
        
        # 进度条(同之前,但逻辑修改)
        self.progress_label = ttk.Label(self.root, text="进度: 0%")
        self.progress_label.pack(fill=tk.X, padx=10, pady=5)
        
        self.progress_bar = ttk.Progressbar(self.root, orient="horizontal", length=400, mode="determinate")
        self.progress_bar.pack(fill=tk.X, padx=10, pady=5)
        
        # 图像显示区域(同之前)
        image_frame = ttk.LabelFrame(self.root, text="图像显示")
        image_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        self.image_label = ttk.Label(image_frame, text="等待接收图像...")
        self.image_label.pack(fill=tk.BOTH, expand=True)
    
    def console_log(self, message):
        timestamp = time.strftime("[%H:%M:%S] ")
        print(timestamp + message)
    
    def start_receive(self):
        if self.receiving:
            messagebox.showwarning("提示", "正在接收数据,请先停止")
            return
        
        port = self.port_var.get().strip()
        try:
            baudrate = int(self.baud_var.get().strip())
        except ValueError:
            messagebox.showerror("错误", "波特率必须是整数")
            return
        
        try:
            self.ser = serial.Serial(port, baudrate, timeout=0.01)
            if not self.ser.is_open:
                messagebox.showerror("错误", f"无法打开串口 {port}")
                return
        except Exception as e:
            messagebox.showerror("错误", f"串口打开失败: {str(e)}")
            return
        
        # 重置进度参数(核心修改)
        self.receiving = True
        self.flag_detected = False
        self.image_data_hex = b''
        self.raw_buffer = b''
        self.received_bytes = 0
        self.waiting_progress = 0
        
        self.start_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
        self.console_log(f"打开串口 {port},波特率 {baudrate}")
        self.update_progress(0)  # 重置进度为0
        self.root.after(10, self.receive_loop)
    
    def stop_receive(self):
        self.receiving = False
        if self.ser and self.ser.is_open:
            self.ser.close()
            self.console_log("串口已关闭")
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
    
    def receive_loop(self):
        if not self.receiving or not self.ser or not self.ser.is_open:
            return
        
        try:
            if self.ser.in_waiting > 0:
                data = self.ser.read(self.ser.in_waiting)
                self.raw_buffer += data
                self.console_log(f"收到数据: 长度={len(data)}字节")
                
                # 阶段1:未检测到开始标志(等待阶段)
                if not self.flag_detected:
                    # 缓慢增长进度(0-30%),避免用户觉得程序无响应
                    self.waiting_progress += min(0.5, 30 / self.estimated_total * len(data))
                    current_progress = min(30, self.waiting_progress)
                    self.update_progress(current_progress)
                    
                    # 检测到开始标志后进入阶段2
                    if self.start_flag in self.raw_buffer:
                        start_idx = self.raw_buffer.index(self.start_flag) + len(self.start_flag)
                        self.image_data_hex = self.raw_buffer[start_idx:]
                        self.raw_buffer = self.image_data_hex
                        self.flag_detected = True
                        self.received_bytes = len(self.image_data_hex)  # 初始化已接收字节数
                        self.console_log(f"检测到开始标志,当前有效数据长度={self.received_bytes}字节")
                
                # 阶段2:已检测到开始标志(接收数据阶段)
                elif self.flag_detected and self.end_flag not in self.raw_buffer:
                    # 累加已接收的有效数据长度
                    self.received_bytes = len(self.raw_buffer)
                    # 计算进度(30%-90%):用已接收字节数 ÷ 预估总长度,映射到30%-90%区间
                    progress_ratio = min(1.0, self.received_bytes / self.estimated_total)
                    current_progress = 30 + (90 - 30) * progress_ratio
                    self.update_progress(current_progress)
                    self.console_log(f"接收中... 已接收={self.received_bytes}/{self.estimated_total}字节")
                
                # 阶段3:检测到结束标志(完成阶段)
                if self.flag_detected and self.end_flag in self.raw_buffer:
                    end_idx = self.raw_buffer.index(self.end_flag)
                    self.image_data_hex = self.raw_buffer[:end_idx]
                    self.console_log(f"检测到结束标志,总有效数据长度={len(self.image_data_hex)}字节")
                    self.receiving = False
                    self.update_progress(95)  # 处理前先到95%
                    self.process_image()
        
        except Exception as e:
            self.console_log(f"接收错误: {str(e)}")
            self.stop_receive()
        
        if self.receiving:
            self.root.after(10, self.receive_loop)
    
    def process_image(self):
        self.console_log(f"开始处理数据: 原始有效数据长度={len(self.image_data_hex)}字节")
        
        # 清理数据(同之前)
        hex_data = self.image_data_hex.replace(b' ', b'')
        hex_data = hex_data.replace(b'\n', b'').replace(b'\r', b'')
        self.console_log(f"清理后十六进制长度={len(hex_data)}字节")
        
        # 检查非十六进制字符(同之前)
        valid_chars = set(b'0123456789abcdefABCDEF')
        invalid_chars = [c for c in hex_data if c not in valid_chars]
        if invalid_chars:
            self.console_log(f"警告: 检测到非十六进制字符: {[chr(c) for c in invalid_chars[:5]]}...")
        
        # 处理奇数长度(同之前)
        if len(hex_data) % 2 != 0:
            self.console_log("十六进制长度为奇数,自动补0")
            hex_data += b'0'
        
        # 转换为二进制并显示(同之前)
        try:
            image_data = binascii.unhexlify(hex_data)
            self.console_log(f"转换成功,二进制长度={len(image_data)}字节")
            
            with open('received_image.jpeg', 'wb') as f:
                f.write(image_data)
            self.console_log("图像已保存到 received_image.jpeg")
            
            image = Image.open(io.BytesIO(image_data))
            max_w, max_h = 700, 400
            w, h = image.size
            scale = min(max_w/w, max_h/h)
            image = image.resize((int(w*scale), int(h*scale)), Image.LANCZOS)
            
            tk_img = ImageTk.PhotoImage(image)
            self.image_label.config(image=tk_img, text="")
            self.image_label.image = tk_img
            self.console_log(f"图像显示成功 (尺寸: {int(w*scale)}x{int(h*scale)})")
        
        except binascii.Error as e:
            self.console_log(f"转换失败: {str(e)}")
        except Exception as e:
            self.console_log(f"图像处理失败: {str(e)}")
        
        self.stop_receive()
        self.update_progress(100)  # 最终进度100%
    
    def update_progress(self, percent):
        # 确保进度在0-100之间
        percent = max(0, min(100, percent))
        self.progress_bar['value'] = percent
        self.progress_label.config(text=f"进度: {int(percent)}%")

if __name__ == '__main__':
    root = tk.Tk()
    app = SerialImageReceiver(root)
    root.mainloop()

第二版

  • esp-idf端

    #include <stdio.h>
    #include <freertos/FreeRTOS.h>
    #include <freertos/task.h>
    #include <esp_log.h>
    #include "myled.h"
    #include "sdkconfig.h"
    #include "mycamera.h"
    #include "driver/uart.h"
    static const char *TAG = "example:take_picture";
    /*串口测试数据*/
    // -------------------------- 串口初始化 --------------------------
    #define UART_BAUD_RATE 921600
    #define UART_NUM UART_NUM_0
    #define BUF_SIZE (1024)
    void uart_init(void) {
        uart_config_t uart_config = {
            .baud_rate = UART_BAUD_RATE,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
            .source_clk = UART_SCLK_APB
        };
    
        // 配置串口参数
        uart_param_config(UART_NUM, &uart_config);
        // 设置串口引脚(UART0默认引脚:TX=1,RX=3,无需额外配置)
        uart_set_pin(UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
        // 安装串口驱动(使用中断,提高效率)
        uart_driver_install(UART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0);
    }
    // -------------------------- 串口初始化 --------------------------
    /*            */ /*串口测试数据*/
    void app_main(void)
    {   
        
        //  ESP_LOGI("MAIN", "Hello, World!");
        myled_init();
        uart_init();
        xTaskCreate(myled_Blink_task, "myled_Blink_task", 2048, NULL, 5, NULL);
        init_camera();
         while (1) {
            vTaskDelay(pdMS_TO_TICKS(1000));
            ESP_LOGI(TAG, "Taking picture...");
            camera_fb_t *pic = esp_camera_fb_get();
                 if (pic) {
                    // 串口发送图片数据
                int i=pic->len;
                printf("IMG_START\n");
                uart_write_bytes(UART_NUM_0, pic->buf, pic->len);  // 用uart库直接发二进制
                printf("\r\n");
                printf("IMG_END\n");
                ESP_LOGI(TAG, "Picture taken! Its size was: %zu bytes", pic->len);
                esp_camera_fb_return(pic);
            }
            else {
                ESP_LOGE(TAG, "Camera capture failed");
            }
    
        }
    }

    python上位机端

    import serial
    import tkinter as tk
    from tkinter import messagebox, ttk
    from PIL import Image, ImageTk, ImageFile
    import io
    import time
    
    # 支持加载可能被截断的图像
    ImageFile.LOAD_TRUNCATED_IMAGES = True
    
    class SerialImageReceiver:
        def __init__(self, root):
            self.root = root
            self.root.title("高速串口图像接收器")
            self.root.geometry("800x600")
            
            # 核心状态变量
            self.ser = None
            self.receiving = False
            self.raw_buffer = b''          # 原始接收缓冲区
            self.flag_detected = False     # 开始标志检测状态
            self.image_data = b''          # 二进制图像数据
            
            # 通信协议标志(与ESP32严格一致)
            self.start_flag = b'IMG_START\r\n'
            self.end_flag = b'IMG_END\r\n'
            
            # 进度控制参数(根据实际图像大小调整)
            self.estimated_max_size = 50000  # 增大预估尺寸,适配更多场景
            self.received_size = 0
            self.waiting_progress = 0
            
            # 初始化界面
            self.setup_ui()
            print("程序启动,等待图像数据...")
    
        def setup_ui(self):
            # 1. 串口配置区域
            config_frame = ttk.LabelFrame(self.root, text="串口设置")
            config_frame.pack(fill=tk.X, padx=10, pady=5)
            
            ttk.Label(config_frame, text="端口:").grid(row=0, column=0, padx=5, pady=5)
            self.port_var = tk.StringVar(value="COM16")  # 替换为实际串口(如COM3)
            self.port_entry = ttk.Entry(config_frame, textvariable=self.port_var, width=10)
            self.port_entry.grid(row=0, column=1, padx=5, pady=5)
            
            ttk.Label(config_frame, text="波特率:").grid(row=0, column=2, padx=5, pady=5)
            self.baud_var = tk.StringVar(value="921600")  # 与ESP32发送端一致
            self.baud_entry = ttk.Entry(config_frame, textvariable=self.baud_var, width=10)
            self.baud_entry.grid(row=0, column=3, padx=5, pady=5)
            
            self.start_btn = ttk.Button(config_frame, text="开始接收", command=self.start_receive)
            self.start_btn.grid(row=0, column=4, padx=5, pady=5)
            
            self.stop_btn = ttk.Button(config_frame, text="停止接收", command=self.stop_receive, state=tk.DISABLED)
            self.stop_btn.grid(row=0, column=5, padx=5, pady=5)
            
            # 2. 进度显示区域
            progress_frame = ttk.Frame(self.root)
            progress_frame.pack(fill=tk.X, padx=10, pady=2)
            
            self.status_label = ttk.Label(progress_frame, text="状态: 未连接")
            self.status_label.pack(side=tk.LEFT, padx=5)
            
            self.progress_bar = ttk.Progressbar(progress_frame, orient="horizontal", length=500, mode="determinate")
            self.progress_bar.pack(side=tk.RIGHT, expand=True, fill=tk.X, padx=5)
            
            # 3. 图像显示区域
            image_frame = ttk.LabelFrame(self.root, text="图像预览")
            image_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
            
            self.image_label = ttk.Label(image_frame, text="等待接收图像...")
            self.image_label.pack(fill=tk.BOTH, expand=True)
    
        def log(self, message):
            """带时间戳的日志输出"""
            timestamp = time.strftime("[%H:%M:%S] ")
            print(timestamp + message)
    
        def update_status(self, progress, text):
            """更新进度条和状态文本"""
            self.progress_bar['value'] = min(100, max(0, progress))
            self.status_label.config(text=f"状态: {text}")
            self.root.update_idletasks()  # 强制刷新UI,避免进度条卡顿
    
        def start_receive(self):
            """启动接收流程"""
            if self.receiving:
                messagebox.showwarning("提示", "已在接收中,请先停止")
                return
            
            # 获取串口参数
            port = self.port_var.get().strip()
            try:
                baudrate = int(self.baud_var.get().strip())
            except ValueError:
                messagebox.showerror("错误", "波特率必须为整数")
                return
            
            # 尝试打开串口(使用pyserial的正确参数)
            try:
                self.ser = serial.Serial(
                    port=port,
                    baudrate=baudrate,
                    timeout=0.01,
                    parity=serial.PARITY_NONE,
                    stopbits=serial.STOPBITS_ONE,
                    bytesize=serial.EIGHTBITS
                )
                if not self.ser.is_open:
                    raise Exception("串口未正常打开")
            except Exception as e:
                messagebox.showerror("串口错误", f"打开失败: {str(e)}")
                return
            
            # 初始化接收状态
            self.receiving = True
            self.flag_detected = False
            self.raw_buffer = b''
            self.image_data = b''
            self.received_size = 0
            self.waiting_progress = 0
            
            # 更新UI
            self.start_btn.config(state=tk.DISABLED)
            self.stop_btn.config(state=tk.NORMAL)
            self.update_status(0, f"已连接 {port} ({baudrate}波特率)")
            self.log(f"串口打开成功: {port} {baudrate}")
            
            # 启动接收循环
            self.receive_loop()
    
        def stop_receive(self):
            """停止接收并清理资源"""
            self.receiving = False
            if self.ser and self.ser.is_open:
                self.ser.close()
                self.log("串口已关闭")
            
            # 重置UI
            self.start_btn.config(state=tk.NORMAL)
            self.stop_btn.config(state=tk.DISABLED)
            self.update_status(0, "已停止接收")
            self.image_label.config(text="等待接收图像...", image="")
    
        def receive_loop(self):
            """核心接收循环,分阶段处理数据"""
            if not self.receiving or not self.ser or not self.ser.is_open:
                return
            
            try:
                # 读取可用数据(一次最多4096字节,平衡效率和响应速度)
                if self.ser.in_waiting > 0:
                    data = self.ser.read(min(self.ser.in_waiting, 4096))
                    self.raw_buffer += data
                    self.log(f"接收数据: {len(data)}字节 (累计: {len(self.raw_buffer)}字节)")
    
                    # 阶段1: 等待开始标志 (0-30%)
                    if not self.flag_detected:
                        # 缓慢增长进度,避免用户误以为程序无响应
                        self.waiting_progress += min(1, 30 * len(data) / self.estimated_max_size)
                        self.update_status(self.waiting_progress, "等待图像开始标志...")
                        
                        if self.start_flag in self.raw_buffer:
                            # 提取开始标志后的有效数据
                            start_idx = self.raw_buffer.index(self.start_flag) + len(self.start_flag)
                            self.raw_buffer = self.raw_buffer[start_idx:]
                            self.flag_detected = True
                            self.received_size = len(self.raw_buffer)
                            self.log("检测到开始标志,开始接收图像数据")
                            self.update_status(30, "接收图像数据中...")
    
                    # 阶段2: 接收图像数据 (30-95%)
                    elif self.flag_detected and self.end_flag not in self.raw_buffer:
                        self.received_size = len(self.raw_buffer)
                        # 计算进度(映射到30-95%区间)
                        progress = 30 + 65 * min(1.0, self.received_size / self.estimated_max_size)
                        self.update_status(progress, f"已接收: {self.received_size}字节")
    
                    # 阶段3: 检测到结束标志,处理图像
                    if self.flag_detected and self.end_flag in self.raw_buffer:
                        end_idx = self.raw_buffer.index(self.end_flag)
                        self.image_data = self.raw_buffer[:end_idx]
                        self.log(f"检测到结束标志,图像大小: {len(self.image_data)}字节")
                        
                        self.receiving = False
                        self.update_status(95, "处理图像中...")
                        self.process_image()
    
            except Exception as e:
                self.log(f"接收错误: {str(e)}")
                self.stop_receive()
                return
            
            # 继续循环(短延迟保证响应速度)
            if self.receiving:
                self.root.after(1, self.receive_loop)
    
        def process_image(self):
            """快速处理并显示图像"""
            try:
                # 保存图像(可选)
                with open("received_image.jpg", "wb") as f:
                    f.write(self.image_data)
                
                # 快速显示图像
                image = Image.open(io.BytesIO(self.image_data))
                # 高效缩放(保持比例)
                image.thumbnail((700, 500))  # 限制最大尺寸
                
                tk_img = ImageTk.PhotoImage(image)
                self.image_label.config(image=tk_img, text="")
                self.image_label.image = tk_img  # 防止图像被垃圾回收
                
                self.log(f"图像显示成功: {image.size[0]}x{image.size[1]}")
                self.update_status(100, "图像接收完成")
    
            except Exception as e:
                self.log(f"图像处理失败: {str(e)}")
                self.update_status(100, "处理失败,请重试")
            
            # 可选:关闭自动重连,改为手动控制(避免串口持续占用)
            # 如需自动重连,保留下面一行;否则注释掉
            # self.root.after(500, self.start_receive)
    
    if __name__ == "__main__":
        # 确保使用pyserial库(避免serial库冲突)
        try:
            import serial.tools.list_ports
        except ImportError:
            messagebox.showerror("库错误", "请安装pyserial库:pip install pyserial")
        else:
            root = tk.Tk()
            app = SerialImageReceiver(root)
            root.mainloop()

    展示

  • 代码演示效果如下:

  • 2025-10-02 18-15-45

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值