ESP32cam--獲取圖片通過串口傳到上位機

ESP32cam:分辨率、格式

資料來源

1、可用的分辨率如下:

typedef enum {
    FRAMESIZE_96x96,    // 96x96
    FRAMESIZE_QQVGA,    // 160x120
    FRAMESIZE_QQVGA2,   // 128x160
    FRAMESIZE_QCIF,     // 176x144
    FRAMESIZE_HQVGA,    // 240x176
    FRAMESIZE_240x240,  // 240x240
    FRAMESIZE_QVGA,     // 320x240
    FRAMESIZE_CIF,      // 400x296
    FRAMESIZE_VGA,      // 640x480
    FRAMESIZE_SVGA,     // 800x600
    FRAMESIZE_XGA,      // 1024x768
    FRAMESIZE_SXGA,     // 1280x1024
    FRAMESIZE_UXGA,     // 1600x1200
    FRAMESIZE_QXGA,     // 2048*1536
    FRAMESIZE_INVALID
} framesize_t;

2、可用的格式如下:

typedef enum {
    PIXFORMAT_RGB565,    // 2BPP/RGB565
    PIXFORMAT_YUV422,    // 2BPP/YUV422
    PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE
    PIXFORMAT_JPEG,      // JPEG/COMPRESSED
    PIXFORMAT_RGB888,    // 3BPP/RGB888
    PIXFORMAT_RAW,       // RAW
    PIXFORMAT_RGB444,    // 3BP2P/RGB444
    PIXFORMAT_RGB555,    // 3BP2P/RGB555
} pixformat_t;

在這裏選擇JPEG的格式。

JPEG格式圖片的基本知識

資料來源一
資料來源二
在這裏插入圖片描述1、JPEG圖片格式組成部分:

SOI(文件頭)+APP0(圖像識別信息)+ DQT(定義量化表)+ SOF0(圖像基本信息)+ DHT(定義Huffman表) + DRI(定義重新開始間隔)+ SOS(掃描行開始)+ EOI(文件尾)

其中粗體部分是必須的

2、簡單介紹:
 JPEG 文件的格式是分爲一個一個的段來存儲的,段的多少和長度並不是一定的。JPEG文件的每個段都一定包含兩部分,一個是段的標識,它由兩個字節構成:第一個字節是十六進制0xFF,第二個字節對於不同的段,這個值是不同的。緊接着的兩個字節存放的是這個段的長度(除了前面的兩個字節0xFF和0xXX,X表示不確定。他們是不算到段的長度中的)。注意:這個長度的表示方法是按照高位在前,低位在後的。

段標識(1Byte)+段類型(1Byte)+段長度(2Byte)+段內容(…)

  • 段標識已經固定爲:0xFF
  • 段類型就有很多種:
名稱 標記碼 說明
SOI D8 文件頭
EOI D9 文件尾
SOF0 C0 幀開始(標準 JPEG)
SOF1 C1 同上
DHT C4 定義 Huffman 表(霍夫曼表)
SOS DA 掃描行開始
DQT DB 定義量化表
DRI DD 定義重新開始間隔
APP0 E0 定義交換格式和圖像識別信息
COM FE 註釋

3、簡單粗暴的來說:

  • SOI 開頭兩個字節:0xFFD8
  • EOI 結尾兩個字節:0xFFD9

上ESP32CAM代碼

#include "esp_camera.h"

#define KEY_PIN       15

#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

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 = 20000000,
  .ledc_timer = LEDC_TIMER_0,
  .ledc_channel = LEDC_CHANNEL_0,
  
  .pixel_format = PIXFORMAT_JPEG,
  .frame_size = FRAMESIZE_UXGA,
  .jpeg_quality = 12,
  .fb_count = 1,
};

esp_err_t camera_init(){
    //initialize the camera
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.print("Camera Init Failed");
        return err;
    }
    sensor_t * s = esp_camera_sensor_get();
    //initial sensors are flipped vertically and colors are a bit saturated
    if (s->id.PID == OV2640_PID) {
        s->set_vflip(s, 1);//flip it back
        s->set_brightness(s, 1);//up the blightness just a bit
        s->set_contrast(s, 1);
    }
  
    Serial.print("Camera Init OK");
    return ESP_OK;
}

void setup() {
  Serial.begin(115200);
  
  camera_init();
  
  pinMode(KEY_PIN, INPUT_PULLUP); //設置按鍵管腳輸入上拉模式
  
  Serial.print("sys is running!");
}
int buttonState = 0;
int btnHold = 0;

void loop() {
  
     buttonState = digitalRead(KEY_PIN); //讀取按鍵返回狀態值
    if (buttonState == LOW && btnHold == 0) 
    { // 若按鍵被按下
        delay(100); //等待跳過按鍵抖動的不穩定過程
        if (buttonState == LOW) // 若按鍵被按下
        {  
            //Serial.print("click\r\n");
            btnHold = 1;

          //acquire a frame
          camera_fb_t * fb = esp_camera_fb_get();
          if (!fb) {
              
          }
          //replace this with your own function
          //process_image(fb->width, fb->height, fb->format, fb->buf, fb->len);
          
          //Serial.printf("width:%d, height:%d, format:%d, len:%d\r\n", fb->width, fb->height, fb->format, fb->len);

          for(int i = 0; i < fb->len; i++)
          {
              Serial.write(fb->buf[i++]); // 發送到上位機
          }
          //return the frame buffer back to the driver for reuse
          esp_camera_fb_return(fb);
            
        }
    }
    if(buttonState == HIGH)
    {
        btnHold = 0;
    }
}

很無奈串口打印的十六進制數開始的字節是這樣子的,根本就不符合JPEG格式!

FF FF 00 4A 49 00 01 00 00 00 FF 00 00 08 0B 08 0B 0B 0D 0E 
1E 12 11 25 1C 1E 26 2D 26 29 36 3B 33 34 2A 52 41 4A 4E 2F 
55 54 5A 4C 4A DB 43 0D 0E 10 23 14 4A 2A 4A 4A 4A 4A 4A 4A 
4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A 4A FF 
00 00 01 01 01 01 00 00 00 00 01 03 05 07 09 0B C4 B5 00 01 
03 04 05 04 00 01 01 03 04 05 21 41 13 61 22 14 81 A1 23 B1 
15 D1 24 62 82 0A 17 19 25 27 29 34 36 38 3A 44 46 48 4A 54 
56 58 5A 64 66 68 6A 74 76 78 7A 84 86 88 8A 93 95 97 99 A2

最後發現是串口寫數據的方式是錯誤的:

    for(int i = 0; i < fb->len; i++)
    {
        Serial.write(fb->buf[i++]); // 錯誤的方法
    }

正確的方法:

Serial.write(fb->buf, fb->len); // 正確的發送圖片數據的方法

修改之後就在數據的開頭看到了喜聞樂見的 FF D8 (這是JPEG格式的開頭)

FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 00 00 00 00 00 
FF DB 00 43 00 0C 08 09 0B 09 08 0C 0B 0A 0B 0E 0D 0C 0E 12 
1E 14 12 11 11 12 25 1A 1C 16 1E 2C 26 2E 2D 2B 26 2A 29 30 

最後完整的代碼

#include "esp_camera.h"

#define KEY_PIN       15

#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

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 = 20000000,
  .ledc_timer = LEDC_TIMER_0,
  .ledc_channel = LEDC_CHANNEL_0,
  
  .pixel_format = PIXFORMAT_JPEG,
  .frame_size = FRAMESIZE_UXGA,
  .jpeg_quality = 12,
  .fb_count = 1,
};

esp_err_t camera_init(){
    //initialize the camera
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.print("Camera Init Failed");
        return err;
    }
    sensor_t * s = esp_camera_sensor_get();
    //initial sensors are flipped vertically and colors are a bit saturated
    if (s->id.PID == OV2640_PID) {
//        s->set_vflip(s, 1);//flip it back
//        s->set_brightness(s, 1);//up the blightness just a bit
//        s->set_contrast(s, 1);
    }
  
    Serial.print("Camera Init OK");
    return ESP_OK;
}

void setup() {
  Serial.begin(115200);

  camera_init();

  pinMode(KEY_PIN, INPUT_PULLUP); //設置按鍵管腳輸入上拉模式
  
  Serial.print("sys is running!");
}
int buttonState = 0;
int btnHold = 0;

void loop() {
  
    buttonState = digitalRead(KEY_PIN); //讀取按鍵返回狀態值
    if (buttonState == LOW && btnHold == 0) 
    { // 若按鍵被按下
        delay(100); //等待跳過按鍵抖動的不穩定過程
        if (buttonState == LOW) // 若按鍵被按下
        {  
          	btnHold = 1; // 不支持連按標誌

          	//acquire a frame
          	camera_fb_t * fb = esp_camera_fb_get();
          	if (!fb) 
          	{
              	Serial.print( "Camera capture failed");
          	} 
          	else 
          	{
              	//Serial.printf("width:%d, height:%d, format:%d, len:%d\r\n", fb->width, fb->height, fb->format, fb->len);
               	Serial.write(fb->buf, fb->len);
                
               	esp_camera_fb_return(fb); 
          	}
         }
    }
    if(buttonState == HIGH)
    {
        btnHold = 0;
    }
}

Qt上位機顯示串口圖片

 使用Qt編寫串口上位機用於顯示圖片

QSerialPort *serialPort;

QByteArray byteArray;

QPixmap pixmap;

QByteArray g_photo_data;

// 串口接收數據
void MainWindow::slotReadData()
{
    QByteArray temp = serialPort->readAll();

    if(!temp.isEmpty())
    {
        ui->textEditReceive->append(temp);

        g_photo_data.append(temp);
    }
}
// 顯示圖片按鈕
void MainWindow::on_pushButtonStart_clicked()
{
    QImage image;
    bool flag = image.loadFromData((const uchar *)g_photo_data.data(),g_photo_data.size());

    if(flag)
    {
        QPixmap pixmap=QPixmap::fromImage(image);
        ui->labelImage->setPixmap(pixmap);
        g_photo_data.clear();
    }
}

效果圖

ESP32 cam實物圖:
在這裏插入圖片描述Qt上位機軟件圖:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章