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格式图片的基本知识

资料来源一资料来源二

esp32c6低功耗摄像机 esp32cam像素_上位机

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); // 正确的发送图片数据的方法

或者

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

修改之后就在数据的开头看到了喜闻乐见的 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实物图:

esp32c6低功耗摄像机 esp32cam像素_#define_02

Qt上位机软件图:

esp32c6低功耗摄像机 esp32cam像素_i++_03