esp32s3的quirc识别不出二维码

哪来的野生bug
Posts: 2
Joined: Mon Jul 14, 2025 1:07 pm

esp32s3的quirc识别不出二维码

Postby 哪来的野生bug » Mon Jul 14, 2025 2:19 pm

硬件:esp32-s3,OV2460摄像头,微雪esp32-s3-lcd-2开发板
开发环境:linux,vscode,esp-idf v5.4.1

预期行为:识别二维码,在串口打印二维码内容,并在lcd上框出二维码
问题:quirc识别不到二维码,加的信息显示一直qrcode count是0

复现过程:
使用idf以template-app创建工程
加入espressif/quirc '*'和espressif/esp32-camera ‘*”组件
在menuconfig中打开PSRAM

尝试过的办法:
调整RGB565转灰度的算法
使用更清晰/内容更简单的二维码
使用quirc_flip

lcd显示摄像头帧是正常的,感觉quirc的处理并没有带来多少显示的延迟
另外,我的lcd是240x320,而QVGA是320x240,导致显示的图像是横着的,而我又没找到完全一致的framesize。如果还能帮忙解决这个小问题,我将不胜感激。

main.c

Code: Select all

#include <stdio.h>
#include "esp_timer.h"

#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/i2c.h"

#include "esp_log.h"

#include "esp_camera.h"

#include "quirc.h"
#include "quirc_internal.h"
#include "freertos/semphr.h" // 用于任务同步的信号量

SemaphoreHandle_t xDrawSemaphore;

#define TAG "APP_MAIN" // 定义TAG用于日志输出

#define DRAW_RECT_DEBUG 1

#if DRAW_RECT_DEBUG == 1
#define DRAW_RECT_LOG(...) ESP_LOGI(TAG, __VA_ARGS__)
#else
#define DRAW_RECT_LOG(...)
#endif

#define PWDN_GPIO_NUM 17  // power down is not used
#define RESET_GPIO_NUM -1 // software reset will be performed
#define XCLK_GPIO_NUM 8
#define SIOD_GPIO_NUM 21
#define SIOC_GPIO_NUM 16

#define Y9_GPIO_NUM 2
#define Y8_GPIO_NUM 7
#define Y7_GPIO_NUM 10
#define Y6_GPIO_NUM 14
#define Y5_GPIO_NUM 11
#define Y4_GPIO_NUM 15
#define Y3_GPIO_NUM 13
#define Y2_GPIO_NUM 12
#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 4
#define PCLK_GPIO_NUM 9

#define PIN_NUM_SCLK 39
#define PIN_NUM_MOSI 38
#define PIN_NUM_MISO 40

#define SPI_HOST SPI2_HOST

#define I2C_NUM 0 // I2C number
#define PIN_NUM_I2C_SDA 48
#define PIN_NUM_I2C_SCL 47

#define LCD_PIXEL_CLOCK_HZ (80 * 1000 * 1000)

#define PIN_NUM_LCD_DC 42
#define PIN_NUM_LCD_RST -1
#define PIN_NUM_LCD_CS 45

#define LCD_CMD_BITS 8
#define LCD_PARAM_BITS 8

#define LCD_H_RES 240
#define LCD_V_RES 320

#define PIN_NUM_BK_LIGHT 1

#define LCD_BL_LEDC_TIMER LEDC_TIMER_0
#define LCD_BL_LEDC_MODE LEDC_LOW_SPEED_MODE

#define LCD_BL_LEDC_CHANNEL LEDC_CHANNEL_0
#define LCD_BL_LEDC_DUTY_RES LEDC_TIMER_10_BIT // Set duty resolution to 13 bits
#define LCD_BL_LEDC_DUTY (1024)                // Set duty to 50%. (2 ** 13) * 50% = 4096
#define LCD_BL_LEDC_FREQUENCY (10000)          // Frequency in Hertz. Set frequency at 5 kHz

esp_lcd_panel_handle_t panel_handle;
void draw_rect(struct quirc_point* points, uint16_t* frame_buffer, int width, int height);

void camera_init(void)
{
    camera_config_t config;
    config.ledc_channel = LEDC_CHANNEL_0;
    config.ledc_timer = LEDC_TIMER_1;
    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_sccb_sda = SIOD_GPIO_NUM;
    config.pin_sccb_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_QVGA;
    config.pixel_format = PIXFORMAT_RGB565; // for streaming
    // config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
    config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
    config.fb_location = CAMERA_FB_IN_PSRAM;
    config.jpeg_quality = 12;
    config.fb_count = 2;

    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK)
    {
        printf("Camera init failed with error 0x%x", err);
        vTaskDelete(NULL);
        return;
    }

    sensor_t *s = esp_camera_sensor_get();
    s->set_hmirror(s, 1);
}

void display_init(void)
{
    ESP_LOGI(TAG, "SPI BUS init");
    spi_bus_config_t buscfg = {
        .sclk_io_num = PIN_NUM_SCLK,
        .mosi_io_num = PIN_NUM_MOSI,
        .miso_io_num = PIN_NUM_MISO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 4000,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
    ESP_LOGI(TAG, "Install panel IO");

    esp_lcd_panel_io_handle_t io_handle = NULL;

    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = PIN_NUM_LCD_DC,
        .cs_gpio_num = PIN_NUM_LCD_CS,
        .pclk_hz = LCD_PIXEL_CLOCK_HZ,
        .lcd_cmd_bits = LCD_CMD_BITS,
        .lcd_param_bits = LCD_PARAM_BITS,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };
    // Attach the LCD to the SPI bus
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_HOST, &io_config, &io_handle));

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = PIN_NUM_LCD_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
    };
    ESP_LOGI(TAG, "Install ST7789 panel driver");
    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));

    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, true));
    ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true));
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
}

void bsp_brightness_init(void)
{
    gpio_set_direction(PIN_NUM_BK_LIGHT, GPIO_MODE_OUTPUT);
    gpio_set_level(PIN_NUM_BK_LIGHT, 1);

    // Prepare and then apply the LEDC PWM timer configuration
    ledc_timer_config_t ledc_timer = {
        .speed_mode = LCD_BL_LEDC_MODE,
        .timer_num = LCD_BL_LEDC_TIMER,
        .duty_resolution = LCD_BL_LEDC_DUTY_RES,
        .freq_hz = LCD_BL_LEDC_FREQUENCY, // Set output frequency at 5 kHz
        .clk_cfg = LEDC_AUTO_CLK};
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

    // Prepare and then apply the LEDC PWM channel configuration
    ledc_channel_config_t ledc_channel = {
        .speed_mode = LCD_BL_LEDC_MODE,
        .channel = LCD_BL_LEDC_CHANNEL,
        .timer_sel = LCD_BL_LEDC_TIMER,
        .intr_type = LEDC_INTR_DISABLE,
        .gpio_num = PIN_NUM_BK_LIGHT,
        .duty = 0, // Set duty to 0%
        .hpoint = 0};
    ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
}

void bsp_brightness_set_level(uint8_t level)
{
    if (level > 100)
    {
        ESP_LOGE(TAG, "Brightness value out of range");
        return;
    }

    uint32_t duty = (level * (LCD_BL_LEDC_DUTY - 1)) / 100;

    ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty));
    ESP_ERROR_CHECK(ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL));

    ESP_LOGI(TAG, "LCD brightness set to %d%%", level);
}

// 这个函数没有使用
static void camera_task(void *param)
{
    ESP_LOGI(TAG, "camera task");
    
    camera_fb_t *pic;
    bool last_frame_has_qr = false;
    
    while (1)
    {
        // 获取摄像头帧
        pic = esp_camera_fb_get();
        
        if (pic != NULL)
        {
            ESP_LOGI(TAG, "Frame size: %zu bytes, Resolution: %dx%d, Format: %d",
                    pic->len, pic->width, pic->height, pic->width);
            
            // 如果不是连续显示二维码帧,则更新为普通帧
            if (!last_frame_has_qr && xSemaphoreTake(xDrawSemaphore, portMAX_DELAY) == pdTRUE)
            {
                // 直接显示原始图像
                esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, pic->width, pic->height, pic->buf);
                
                // 释放信号量
                xSemaphoreGive(xDrawSemaphore);
            }
            
            // 释放摄像头帧
            esp_camera_fb_return(pic);
        }
        
        // 控制任务执行频率
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

static void qrcode_process_task(void *param)
{
    ESP_LOGI("QRCODE", "QRCODE process task start");

    // 创建二维码解码器
    struct quirc *qr_coder = quirc_new();
    assert(qr_coder);

    int err = quirc_resize(qr_coder,  LCD_H_RES, LCD_V_RES);
    if (err != 0)
    {
        ESP_LOGE(TAG, "quirc_resize failed ,error code:%d", err);
        return;
    }

    camera_fb_t *pic;
    
    while (1)
    {
        // 获取一帧图片
        pic = esp_camera_fb_get();
        
        if (pic == NULL)
        {
            ESP_LOGE(TAG, "Failed to get camera frame");
            vTaskDelay(pdMS_TO_TICKS(10));
            continue;
        }
        
        // 处理图片缓冲区
        int w = (int)pic->width;
        int h = (int)pic->height;
        uint8_t *qr_buf = quirc_begin(qr_coder, &w, &h);
        if (qr_buf == NULL)
        {
            ESP_LOGE("QRCODE", "Failed to get QR code buffer");
            esp_camera_fb_return(pic);
            continue;
        }

        // 将图像转换为灰度图像并复制到缓冲区
        for (int y = 0; y < pic->height; y++)
        {
            for (int x = 0; x < pic->width; x++)
            {
                uint16_t pixel = ((uint16_t*)pic->buf)[(y * pic->width + x)];
                uint8_t r = (pixel >> 11) & 0x1F; // 获取红色
                uint8_t g = (pixel >> 5) & 0x3F; // 获取绿色
                uint8_t b = (pixel & 0x1F); // 获取蓝色
                
                // 转化为RGB888
                r = (r << 3) | (r >> 2);
                g = (g <<2) | (g >> 4);
                b = (b << 3) | (b >> 2);

                // 转化为灰度
                qr_buf[y * pic->width + x] = (uint8_t)(r * 0.299 + g * 0.587 + b * 0.114);
            }
        }

        // 结束缓冲区处理
        quirc_end(qr_coder);

        // 开始解码
        bool has_qr_code = false;
        int qr_count = quirc_count(qr_coder);
        ESP_LOGI(TAG, "QRCODE COUNT: %d", qr_count);  // 添加调试信息:打印检测到的二维码数量

        for (int i = 0; i < qr_count; i++)
        {
            struct quirc_code code;
            struct quirc_data data;

            quirc_extract(qr_coder, i, &code);

            quirc_decode_error_t err = quirc_decode(&code, &data);
            if (err == (quirc_decode_error_t)QUIRC_SUCCESS)
            {
                // 获取二维码数据
                char *qr_data = (char *)data.payload;
                ESP_LOGI(TAG, "QRCODE DATA: %s", qr_data);

                // 绘制矩形框
                draw_rect(code.corners, (uint16_t*)pic->buf, pic->width, pic->height);
                has_qr_code = true;
            }
            else
            {
                if (err != (quirc_decode_error_t)QUIRC_ERROR_DATA_ECC)
                {
                    // 如果识别失败,尝试翻转二维码
                    quirc_flip(&code);
                    err = quirc_decode(&code, &data);
                }
                else
                {
                    // 获取二维码数据失败
                    ESP_LOGW(TAG, "QRCODE DECODE FAILED");
                }
            }
        }
        
        // 总是尝试获取信号量以确保独占访问
        if (xSemaphoreTake(xDrawSemaphore, portMAX_DELAY) == pdTRUE)
        {
            // 提交带有二维码框的图像到显示
            esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, pic->width, pic->height, pic->buf);
            
            // 设置标志表示当前显示的是包含二维码的帧
            ((void**)param)[0] = (void*)(uintptr_t)has_qr_code;
            
            // 释放信号量
            xSemaphoreGive(xDrawSemaphore);
        }
        
        // 释放资源
        esp_camera_fb_return(pic);
        pic = NULL;
        qr_buf = NULL;

        // 控制任务执行频率
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

void app_main(void)
{
    camera_init();
    display_init();
    bsp_brightness_init();
    bsp_brightness_set_level(80);
    
    // 创建互斥信号量用于显示同步
    xDrawSemaphore = xSemaphoreCreateMutex();
    assert(xDrawSemaphore != NULL);
    
    // 创建用于任务间通信的状态变量
    bool *p_has_qr_code = (bool*)calloc(1, sizeof(bool));
    assert(p_has_qr_code != NULL);
    
    // xTaskCreatePinnedToCore(camera_task, "camera_task", 1024 * 20, p_has_qr_code, 1, NULL, 0);
    xTaskCreatePinnedToCore(qrcode_process_task, "qrcode_process_task", 1024 * 15, p_has_qr_code, 2, NULL, 1);
}


void draw_rect(struct quirc_point* points, uint16_t* frame_buffer, int width, int height)
{
    // 定义矩形框的颜色(RGB565格式:0xRRGGBB)
    uint16_t color = 0xF800; // 红色

    // 设置矩形框的宽度
    uint8_t rect_width = 3;

    // 绘制四条边
    for (int i = 0; i < 4; i++) {
        int x1 = points[i].x;
        int y1 = points[i].y;
        int x2 = points[(i + 1) % 4].x;
        int y2 = points[(i + 1) % 4].y;

        // 计算线段的方向
        int dx = abs(x2 - x1);
        int dy = abs(y2 - y1);
        int sx = (x1 < x2) ? 1 : -1;
        int sy = (y1 < y2) ? 1 : -1;
        int err = dx - dy;
        
        // 使用Bresenham算法绘制线段
        while (1) {
            // 绘制线段周围的像素,形成矩形框
            for (int r = -rect_width; r <= rect_width; r++) {
                for (int s = -rect_width; s <= rect_width; s++) {
                    int px = x1 + r;
                    int py = y1 + s;
                    
                    // 确保坐标在屏幕范围内
                    if (px >= 0 && px < width && py >= 0 && py < height) {
                        // 在帧缓冲区中绘制像素点
                        frame_buffer[py * width + px] = color;
                    }
                }
            }
            
            if (x1 == x2 && y1 == y2) break;
            
            int e2 = 2 * err;
            if (e2 > -dy) {
                err -= dy;
                x1 += sx;
            }
            if (e2 < dx) {
                err += dx;
                y1 += sy;
            }
        }
    }
}

Who is online

Users browsing this forum: No registered users and 3 guests