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格式圖片的基本知識
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上位機軟件圖: