mirror of
https://github.com/espressif/esp-idf.git
synced 2026-01-14 15:45:53 +00:00
Merge branch 'feat/isp_awb_example' into 'master'
Feat/isp awb example Closes IDF-10498 See merge request espressif/esp-idf!43946
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
This example demonstrates how to use the ISP (image signal processor) to work with esp_driver_cam component. This example will auto-detect camera sensors via [ESP camera sensor driver](https://components.espressif.com/components/espressif/esp_cam_sensor/versions/0.5.3) and capture camera sensor signals via CSI interface and display it via DSI interface. This example enables following ISP functions:
|
||||
|
||||
- ISP AWB (auto white balance) & WBG (white balance gain) feature
|
||||
- ISP AF (auto-focus) feature
|
||||
- ISP BF (bayer denoise) feature
|
||||
- ISP BLC (black level correction) feature
|
||||
@@ -160,23 +161,37 @@ To exit the serial monitor, use `Ctrl` + `]`.
|
||||
If you see the following console output, your example should be running correctly:
|
||||
|
||||
```
|
||||
I (1425) main_task: Calling app_main()
|
||||
I (1425) example_dsi_init: Allocating DSI resources with 2 frame buffer(s)
|
||||
I (1485) example_dsi_init: Frame buffer[0] allocated at: 0x48000a40
|
||||
I (1485) example_dsi_init: Frame buffer[1] allocated at: 0x481f4a80
|
||||
I (1485) isp_dsi: Original CSI resolution: 800x640
|
||||
I (1485) isp_dsi: Display resolution: 800x640, bits per pixel: 16
|
||||
I (1495) isp_dsi: frame_buffer_size: 2048000
|
||||
I (1495) isp_dsi: Frame buffers: fb0=0x48000a40, fb1=0x481f4a80
|
||||
I (1515) ov5647: Detected Camera sensor PID=0x5647
|
||||
I (1535) sensor_init: fmt[0].name:MIPI_2lane_24Minput_RAW8_800x1280_50fps
|
||||
I (1535) sensor_init: fmt[1].name:MIPI_2lane_24Minput_RAW8_800x640_50fps
|
||||
I (1535) sensor_init: fmt[2].name:MIPI_2lane_24Minput_RAW8_800x800_50fps
|
||||
I (1535) sensor_init: fmt[3].name:MIPI_2lane_24Minput_RAW10_1920x1080_30fps
|
||||
I (1545) sensor_init: fmt[4].name:MIPI_2lane_24Minput_RAW10_1280x960_binning_45fps
|
||||
I (2025) sensor_init: Format in use:MIPI_2lane_24Minput_RAW8_800x640_50fps
|
||||
I (2045) isp_dsi: ISP Crop not configured
|
||||
I (2105) ili9881c: ID1: 0x98, ID2: 0x81, ID3: 0x5c
|
||||
I (1457) main_task: Calling app_main()
|
||||
I (1457) example_dsi_init: Allocating DSI resources with 2 frame buffer(s)
|
||||
I (1517) isp_dsi: Original CSI resolution: 800x640
|
||||
I (1517) isp_dsi: Display resolution: 800x640, bits per pixel: 16
|
||||
I (1517) isp_dsi: Frame buffers: fb0=0x48000a40, fb1=0x481f4a80
|
||||
I (1517) ov5647: Detected Camera sensor PID=0x5647
|
||||
I (1537) sensor_init: fmt[0].name:MIPI_2lane_24Minput_RAW8_800x1280_50fps
|
||||
I (1537) sensor_init: fmt[1].name:MIPI_2lane_24Minput_RAW8_800x640_50fps
|
||||
I (1537) sensor_init: fmt[2].name:MIPI_2lane_24Minput_RAW8_800x800_50fps
|
||||
I (1547) sensor_init: fmt[3].name:MIPI_2lane_24Minput_RAW10_1920x1080_30fps
|
||||
I (1547) sensor_init: fmt[4].name:MIPI_2lane_24Minput_RAW10_1280x960_binning_45fps
|
||||
I (1617) sensor_init: Format in use:MIPI_2lane_24Minput_RAW8_800x640_50fps
|
||||
I (1627) isp_pipeline: ISP processor initialized
|
||||
I (1627) isp_pipeline: BLC module configured
|
||||
I (1627) isp_pipeline: BF module configured
|
||||
I (1627) isp_pipeline: LSC module configured
|
||||
I (1627) isp_pipeline: DEMOSAIC module configured
|
||||
I (1637) isp_pipeline: CCM module configured
|
||||
I (1637) isp_pipeline: GAMMA module configured
|
||||
I (1637) isp_pipeline: SHARPEN module configured
|
||||
I (1647) isp_pipeline: COLOR module configured
|
||||
I (1647) isp_pipeline: All ISP pipeline modules initialized
|
||||
W (1657) ISP_AWB: subwindow size (480 x 384) is not divisible by AWB subwindow blocks grid (5 x 5). Resolution will be floored to the nearest divisible value.
|
||||
I (1667) isp_wb: AWB and WBG module initialized
|
||||
I (1677) isp_wb: White balance task started
|
||||
I (1677) isp_wb: WB enabled: AWB statistics started, WBG module enabled, processing task created
|
||||
I (1687) isp_af: AF module initialized
|
||||
I (1687) isp_af: AF task started
|
||||
I (1697) isp_af: AF task created
|
||||
I (1757) ili9881c: ID1: 0x98, ID2: 0x81, ID3: 0x5c
|
||||
I (1877) isp_dsi: ISP DSI example started
|
||||
```
|
||||
|
||||
Below picture is from the video stream of OV5647 and ILI9881C. The camera module is auto-focused and calibrated by ESP on-chip ISP hardware. The edge is over-sharpened as example code configured.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
set(srcs "isp_dsi_main.c"
|
||||
"example_buffer.c"
|
||||
"example_af.c"
|
||||
"example_awb.c"
|
||||
"example_pipelines.c")
|
||||
|
||||
if(CONFIG_EXAMPLE_ISP_CROP_ENABLE)
|
||||
|
||||
@@ -187,8 +187,7 @@ static void example_af_task(void *arg)
|
||||
}
|
||||
|
||||
esp_err_t example_isp_af_init(isp_proc_handle_t isp_proc,
|
||||
esp_sccb_io_handle_t dw9714_io_handle,
|
||||
const example_isp_af_config_t *config)
|
||||
esp_sccb_io_handle_t dw9714_io_handle)
|
||||
{
|
||||
if (isp_proc == NULL || dw9714_io_handle == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid arguments");
|
||||
|
||||
@@ -38,20 +38,6 @@ typedef struct {
|
||||
esp_sccb_io_handle_t dw9714_io_handle; // DW9714 VCM SCCB handle
|
||||
} example_isp_af_task_param_t;
|
||||
|
||||
/**
|
||||
* @brief AF configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
int window_size; // AF window size (centered window)
|
||||
int edge_thresh; // Edge threshold for definition calculation
|
||||
int env_interval; // Environment detector interval (frames)
|
||||
int first_step_val; // First step value for SA scheme
|
||||
int first_approx_cycles; // First approximation cycles
|
||||
int second_step_val; // Second step value for SA scheme
|
||||
int second_approx_cycles; // Second approximation cycles
|
||||
int focus_val_max; // Maximum focus value
|
||||
} example_isp_af_config_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize AF module
|
||||
*
|
||||
@@ -60,15 +46,13 @@ typedef struct {
|
||||
*
|
||||
* @param[in] isp_proc ISP processor handle
|
||||
* @param[in] dw9714_io_handle DW9714 VCM SCCB handle
|
||||
* @param[in] config AF configuration (can be NULL for default values)
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid arguments
|
||||
* - ESP_ERR_NO_MEM: Out of memory
|
||||
*/
|
||||
esp_err_t example_isp_af_init(isp_proc_handle_t isp_proc,
|
||||
esp_sccb_io_handle_t dw9714_io_handle,
|
||||
const example_isp_af_config_t *config);
|
||||
esp_sccb_io_handle_t dw9714_io_handle);
|
||||
|
||||
/**
|
||||
* @brief Start AF task
|
||||
|
||||
344
examples/peripherals/isp/multi_pipelines/main/example_awb.c
Normal file
344
examples/peripherals/isp/multi_pipelines/main/example_awb.c
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "driver/isp_awb.h"
|
||||
#include "driver/isp_wbg.h"
|
||||
#include "driver/isp_types.h"
|
||||
#include "example_config.h"
|
||||
#include "example_awb.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/idf_additions.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
static const char *TAG = "isp_awb";
|
||||
|
||||
static isp_awb_ctlr_t s_awb_ctlr = NULL;
|
||||
static isp_proc_handle_t s_isp_proc = NULL;
|
||||
static QueueHandle_t s_awb_queue = NULL;
|
||||
static TaskHandle_t s_awb_task_handle = NULL;
|
||||
|
||||
// Configuration for gain printing
|
||||
#define AWB_GAIN_UPDATE_COUNT 5
|
||||
|
||||
// White balance gain normalization value (1.0 = neutral gain)
|
||||
#define AWB_GAIN_NORM 256 // Normalization value for gain calculation
|
||||
|
||||
// PI Controller parameters for smooth gain convergence
|
||||
#define AWB_P_GAIN 0.5f // Proportional gain (0.0-1.0)
|
||||
|
||||
/**
|
||||
* @brief Get default AWB configuration based on image resolution
|
||||
*/
|
||||
static void s_get_default_awb_config(esp_isp_awb_config_t *config, uint32_t h_res, uint32_t v_res)
|
||||
{
|
||||
// Set sample point: after CCM by default
|
||||
config->sample_point = ISP_AWB_SAMPLE_POINT_AFTER_CCM;
|
||||
|
||||
// Configure window: middle 80% of image to avoid edge overexposure
|
||||
config->window.top_left.x = h_res * 0.2f;
|
||||
config->window.top_left.y = v_res * 0.2f;
|
||||
config->window.btm_right.x = h_res * 0.8f - 1;
|
||||
config->window.btm_right.y = v_res * 0.8f - 1;
|
||||
|
||||
// Configure subwindow: same size as main window
|
||||
config->subwindow = config->window;
|
||||
|
||||
// Configure white patch detection thresholds
|
||||
// Luminance range: [0, 220*3] to avoid overexposed pixels
|
||||
config->white_patch.luminance.min = 0;
|
||||
config->white_patch.luminance.max = 220 * 3;
|
||||
|
||||
// Color ratio ranges: wide range to include all possible white patches
|
||||
config->white_patch.red_green_ratio.min = 0.5f;
|
||||
config->white_patch.red_green_ratio.max = 1.999f;
|
||||
config->white_patch.blue_green_ratio.min = 0.5f;
|
||||
config->white_patch.blue_green_ratio.max = 1.999f;
|
||||
|
||||
// Interrupt priority: 0 means auto-allocate
|
||||
config->intr_priority = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief AWB statistics callback (runs in ISR context)
|
||||
*
|
||||
* This callback receives AWB statistics and sends them to the processing task via queue.
|
||||
* It should be lightweight as it runs in ISR context.
|
||||
*/
|
||||
static bool IRAM_ATTR s_awb_statistics_callback(isp_awb_ctlr_t awb_ctlr, const esp_isp_awb_evt_data_t *edata, void *user_data)
|
||||
{
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
// Send statistics to queue (from ISR)
|
||||
if (s_awb_queue != NULL) {
|
||||
if (xQueueSendFromISR(s_awb_queue, &edata->awb_result, &xHigherPriorityTaskWoken) != pdTRUE) {
|
||||
ESP_DRAM_LOGE(TAG, "Failed to send AWB statistics to queue (queue full)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return xHigherPriorityTaskWoken == pdTRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PI controller for smooth white balance gain update
|
||||
*
|
||||
* This function implements a PI (Proportional-Integral) controller to smoothly update white balance gains, preventing oscillation and ensuring convergence.
|
||||
*
|
||||
* Currently we just do the P control, the I control is not used and could be extended in the future.
|
||||
*
|
||||
* @param[in] target_gain Target gain calculated from statistics
|
||||
* @param[in] current_gain Currently applied gain
|
||||
* @param[out] new_gain Calculated new gain to apply
|
||||
*/
|
||||
static void s_pi_controller_update(isp_wbg_gain_t target_gain,
|
||||
isp_wbg_gain_t current_gain,
|
||||
isp_wbg_gain_t *new_gain)
|
||||
{
|
||||
// Calculate error (target - current)
|
||||
float error_r = (float)target_gain.gain_r - (float)current_gain.gain_r;
|
||||
float error_b = (float)target_gain.gain_b - (float)current_gain.gain_b;
|
||||
|
||||
// Calculate new gain: current + P*error + I*integral
|
||||
float new_gain_r = (float)current_gain.gain_r + AWB_P_GAIN * error_r;
|
||||
float new_gain_b = (float)current_gain.gain_b + AWB_P_GAIN * error_b;
|
||||
|
||||
// Round and clamp to valid range
|
||||
new_gain->gain_r = (uint32_t)(new_gain_r + 0.5f);
|
||||
new_gain->gain_g = AWB_GAIN_NORM; // G channel is reference
|
||||
new_gain->gain_b = (uint32_t)(new_gain_b + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate white balance gain from statistics
|
||||
*
|
||||
* @param[in] stat_result AWB statistics result
|
||||
* @param[out] gain Calculated white balance gain
|
||||
* @return true if calculation successful, false otherwise
|
||||
*/
|
||||
static bool s_calculate_awb_gain(const isp_awb_stat_result_t *stat_result, isp_wbg_gain_t *gain)
|
||||
{
|
||||
if (stat_result == NULL || gain == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we have enough white patches
|
||||
if (stat_result->white_patch_num == 0) {
|
||||
ESP_LOGD(TAG, "No white patches detected, keeping current gain");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if sum values are valid (avoid division by zero)
|
||||
if (stat_result->sum_r == 0 || stat_result->sum_b == 0) {
|
||||
ESP_LOGW(TAG, "Invalid sum values (R=%lu, B=%lu), keeping current gain",
|
||||
stat_result->sum_r, stat_result->sum_b);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate gains: use G channel as reference
|
||||
float gain_r_float = ((float)stat_result->sum_g / (float)stat_result->sum_r) * (float)AWB_GAIN_NORM;
|
||||
float gain_b_float = ((float)stat_result->sum_g / (float)stat_result->sum_b) * (float)AWB_GAIN_NORM;
|
||||
|
||||
// Clamp gains to valid range
|
||||
gain->gain_r = (uint32_t)(gain_r_float + 0.5f); // Round to nearest
|
||||
gain->gain_g = AWB_GAIN_NORM;
|
||||
gain->gain_b = (uint32_t)(gain_b_float + 0.5f);
|
||||
|
||||
ESP_LOGD(TAG, "Calculated AWB gain: R=%lu, G=%lu, B=%lu (patches=%lu, sum_r=%lu, sum_g=%lu, sum_b=%lu)",
|
||||
gain->gain_r, gain->gain_g, gain->gain_b,
|
||||
stat_result->white_patch_num, stat_result->sum_r, stat_result->sum_g, stat_result->sum_b);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief White balance processing task
|
||||
*
|
||||
* This task receives AWB statistics from the queue, calculates white balance gains,
|
||||
* accumulates them over multiple periods, and applies the smoothed gain through PI controller
|
||||
* to prevent oscillation and ensure convergence.
|
||||
*/
|
||||
static void s_awb_task(void *pvParameters)
|
||||
{
|
||||
isp_awb_stat_result_t stat_result;
|
||||
isp_wbg_gain_t gain;
|
||||
uint32_t gain_count = 0;
|
||||
uint64_t sum_r = 0, sum_g = 0, sum_b = 0;
|
||||
|
||||
// Current applied gain (maintained by PI controller)
|
||||
isp_wbg_gain_t current_gain = {
|
||||
.gain_r = AWB_GAIN_NORM,
|
||||
.gain_g = AWB_GAIN_NORM,
|
||||
.gain_b = AWB_GAIN_NORM,
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "White balance task started");
|
||||
|
||||
while (1) {
|
||||
// Wait for statistics from queue
|
||||
if (xQueueReceive(s_awb_queue, &stat_result, portMAX_DELAY) == pdTRUE) {
|
||||
// Calculate white balance gain
|
||||
if (s_calculate_awb_gain(&stat_result, &gain)) {
|
||||
// Accumulate gain values for averaging
|
||||
sum_r += gain.gain_r;
|
||||
sum_g += gain.gain_g;
|
||||
sum_b += gain.gain_b;
|
||||
gain_count++;
|
||||
|
||||
// When we have enough samples, calculate average and apply with PI controller
|
||||
if (gain_count >= AWB_GAIN_UPDATE_COUNT) {
|
||||
// Calculate average target gain
|
||||
isp_wbg_gain_t target_gain = {};
|
||||
target_gain.gain_r = (uint32_t)(sum_r / AWB_GAIN_UPDATE_COUNT);
|
||||
target_gain.gain_g = (uint32_t)(sum_g / AWB_GAIN_UPDATE_COUNT);
|
||||
target_gain.gain_b = (uint32_t)(sum_b / AWB_GAIN_UPDATE_COUNT);
|
||||
|
||||
// Apply PI controller to calculate new gain
|
||||
isp_wbg_gain_t new_gain = {};
|
||||
s_pi_controller_update(target_gain, current_gain, &new_gain);
|
||||
|
||||
// Apply new gain through WBG module
|
||||
esp_err_t ret = esp_isp_wbg_set_wb_gain(s_isp_proc, new_gain);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set AWB gain: %d", ret);
|
||||
} else {
|
||||
// Update current gain
|
||||
current_gain = new_gain;
|
||||
|
||||
// Print final updated gain values
|
||||
float gain_r_norm = (float)new_gain.gain_r / (float)AWB_GAIN_NORM;
|
||||
float gain_g_norm = (float)new_gain.gain_g / (float)AWB_GAIN_NORM;
|
||||
float gain_b_norm = (float)new_gain.gain_b / (float)AWB_GAIN_NORM;
|
||||
|
||||
ESP_LOGD(TAG, "AWB Gain Updated - R: %lu (%.3f), G: %lu (%.3f), B: %lu (%.3f) [PI controlled]",
|
||||
new_gain.gain_r, gain_r_norm,
|
||||
new_gain.gain_g, gain_g_norm,
|
||||
new_gain.gain_b, gain_b_norm);
|
||||
}
|
||||
|
||||
// Reset for next cycle
|
||||
gain_count = 0;
|
||||
sum_r = 0;
|
||||
sum_g = 0;
|
||||
sum_b = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "White balance task exiting");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
esp_err_t example_isp_awb_init(isp_proc_handle_t isp_proc)
|
||||
{
|
||||
if (isp_proc == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid arguments: isp_proc is NULL");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (s_awb_ctlr != NULL) {
|
||||
ESP_LOGW(TAG, "AWB controller already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_isp_proc = isp_proc;
|
||||
|
||||
esp_isp_awb_config_t awb_config = {};
|
||||
s_get_default_awb_config(&awb_config, CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES, CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES);
|
||||
|
||||
// Create AWB controller for statistics
|
||||
esp_err_t ret = esp_isp_new_awb_controller(isp_proc, &awb_config, &s_awb_ctlr);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to create AWB controller: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Configure WBG module
|
||||
esp_isp_wbg_config_t wbg_config = {};
|
||||
ret = esp_isp_wbg_configure(isp_proc, &wbg_config);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure WBG: %d", ret);
|
||||
ESP_ERROR_CHECK(esp_isp_del_awb_controller(s_awb_ctlr));
|
||||
s_awb_ctlr = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "AWB and WBG module initialized");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t example_isp_awb_start(isp_proc_handle_t isp_proc)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (isp_proc == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid arguments: isp_proc is NULL");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (s_awb_ctlr == NULL) {
|
||||
ESP_LOGE(TAG, "AWB not initialized, call example_isp_awb_init() first");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// Create queue for statistics (use internal RAM for ISR compatibility)
|
||||
// Ignore the results of frames that are not received (if any)
|
||||
s_awb_queue = xQueueCreateWithCaps(1, sizeof(isp_awb_stat_result_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
if (s_awb_queue == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to create AWB queue");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Register AWB statistics callback
|
||||
esp_isp_awb_cbs_t awb_cbs = {
|
||||
.on_statistics_done = s_awb_statistics_callback,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(esp_isp_awb_register_event_callbacks(s_awb_ctlr, &awb_cbs, NULL), err, TAG, "Failed to register AWB callbacks");
|
||||
|
||||
ESP_GOTO_ON_ERROR(esp_isp_wbg_enable(isp_proc), err, TAG, "Failed to enable WBG");
|
||||
|
||||
// Set initial gain (neutral: all channels equal)
|
||||
isp_wbg_gain_t initial_gain = {
|
||||
.gain_r = AWB_GAIN_NORM,
|
||||
.gain_g = AWB_GAIN_NORM,
|
||||
.gain_b = AWB_GAIN_NORM,
|
||||
};
|
||||
ret = esp_isp_wbg_set_wb_gain(isp_proc, initial_gain);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set initial AWB gain: %d", ret);
|
||||
}
|
||||
|
||||
// Enable AWB controller
|
||||
ESP_GOTO_ON_ERROR(esp_isp_awb_controller_enable(s_awb_ctlr), err, TAG, "Failed to enable AWB controller");
|
||||
|
||||
// Start continuous statistics
|
||||
ESP_GOTO_ON_ERROR(esp_isp_awb_controller_start_continuous_statistics(s_awb_ctlr), err, TAG, "Failed to start continuous statistics");
|
||||
|
||||
// Create white balance processing task
|
||||
ESP_GOTO_ON_FALSE(pdPASS == xTaskCreate(s_awb_task, "awb_task", 4096, NULL, 5, &s_awb_task_handle), ESP_FAIL, err, TAG, "Failed to create AWB task");
|
||||
|
||||
ESP_LOGI(TAG, "AWB enabled: AWB statistics started, WBG module enabled, processing task created");
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
ESP_LOGE(TAG, "AWB start failed: %s (0x%x)", esp_err_to_name(ret), ret);
|
||||
esp_isp_awb_controller_stop_continuous_statistics(s_awb_ctlr);
|
||||
esp_isp_awb_controller_disable(s_awb_ctlr);
|
||||
esp_isp_wbg_disable(isp_proc);
|
||||
if (s_awb_queue != NULL) {
|
||||
vQueueDeleteWithCaps(s_awb_queue);
|
||||
s_awb_queue = NULL;
|
||||
}
|
||||
if (s_awb_task_handle != NULL) {
|
||||
vTaskDelete(s_awb_task_handle);
|
||||
s_awb_task_handle = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
68
examples/peripherals/isp/multi_pipelines/main/example_awb.h
Normal file
68
examples/peripherals/isp/multi_pipelines/main/example_awb.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file example_awb.h
|
||||
* @brief Auto White Balance (AWB) Functionality
|
||||
*
|
||||
* This module implements ISP white balance functionality using hardware AWB controller
|
||||
* for statistics and WBG module for gain adjustment.
|
||||
*
|
||||
* Please note that there are two methods to achieve auto white balance tuning:
|
||||
* 1. use AWB module to get white balance statistics + tune camera sensor gain registers
|
||||
* 2. use AWB module to get white balance statistics + tune the output gain of ISP pipeline by WBG module
|
||||
*
|
||||
* This example uses method 2 to achieve auto white balance tuning.
|
||||
*
|
||||
* How it works:
|
||||
* - ISP hardware AWB controller detects white patches in the configured window
|
||||
* - White patches are identified based on luminance and color ratio thresholds
|
||||
* - AWB controller provides statistics (sum_r, sum_g, sum_b) through continuous mode
|
||||
* - Statistics are sent to a FreeRTOS task via queue
|
||||
* - The task calculates white balance gains based on statistics
|
||||
* - Gains are applied through the WBG (White Balance Gain) module
|
||||
*
|
||||
* @note Please note that `AWB` and `WBG` refers to 2 different modules in the ISP pipeline, while this example uses both of them to achieve auto white balance tuning.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "driver/isp.h"
|
||||
#include "driver/isp_awb.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize White Balance module
|
||||
*
|
||||
* This function creates and configures the AWB controller for statistics and WBG module
|
||||
* for gain adjustment. It uses default configuration for AWB controller and WBG module.
|
||||
*
|
||||
* @param[in] isp_proc ISP processor handle
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid arguments
|
||||
* - ESP_ERR_NO_MEM: Out of memory
|
||||
*/
|
||||
esp_err_t example_isp_awb_init(isp_proc_handle_t isp_proc);
|
||||
|
||||
/**
|
||||
* @brief Enable White Balance module
|
||||
*
|
||||
* @param[in] isp_proc ISP processor handle
|
||||
* @return
|
||||
* - ESP_OK: Success
|
||||
* - ESP_ERR_INVALID_ARG: Invalid arguments
|
||||
* - ESP_ERR_INVALID_STATE: Invalid state
|
||||
*/
|
||||
esp_err_t example_isp_awb_start(isp_proc_handle_t isp_proc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -26,6 +26,7 @@
|
||||
// Include modular ISP components
|
||||
#include "example_buffer.h"
|
||||
#include "example_af.h"
|
||||
#include "example_awb.h"
|
||||
#include "example_pipelines.h"
|
||||
#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE
|
||||
#include "example_crop.h"
|
||||
@@ -222,8 +223,23 @@ void app_main(void)
|
||||
return;
|
||||
}
|
||||
|
||||
#if CONFIG_ESP32P4_REV_MIN_FULL >= 300
|
||||
//---------------White Balance Init------------------//
|
||||
ret = example_isp_awb_init(isp_proc);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "AWB init fail[%d]", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = example_isp_awb_start(isp_proc);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "AWB enable fail[%d]", ret);
|
||||
return;
|
||||
}
|
||||
#endif /* CONFIG_ESP32P4_REV_MIN_FULL >= 300 */
|
||||
|
||||
//---------------AF Init and Start------------------//
|
||||
ret = example_isp_af_init(isp_proc, dw9714_io_handle, NULL);
|
||||
ret = example_isp_af_init(isp_proc, dw9714_io_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "AF init fail[%d]", ret);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user