Files
esp-idf/examples/peripherals/isp/multi_pipelines/main/example_pipelines.c

509 lines
14 KiB
C

/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <math.h>
#include "example_pipelines.h"
#include "esp_log.h"
#include "driver/isp.h"
#include "example_config.h"
static const char *TAG = "isp_pipeline";
/**
* @brief Default gamma correction curve
*/
static uint32_t s_gamma_correction_curve(uint32_t x)
{
return pow((double)x / 256, 0.7) * 256;
}
esp_err_t example_create_isp_processor(const esp_isp_processor_cfg_t *config, isp_proc_handle_t *isp_proc)
{
if (config == NULL || isp_proc == NULL) {
ESP_LOGE(TAG, "Invalid arguments");
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = esp_isp_new_processor(config, isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to create ISP processor: %d", ret);
return ret;
}
ret = esp_isp_enable(*isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable ISP processor: %d", ret);
return ret;
}
ESP_LOGI(TAG, "ISP processor initialized");
return ESP_OK;
}
/**
* @brief Configure BLC (Black Level Correction) module
*/
static esp_err_t example_isp_pipelines_config_blc(isp_proc_handle_t isp_proc)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
#if CONFIG_ESP32P4_REV_MIN_FULL >= 300
/**
* This piece of BLC code is to show how to use the BLC related APIs.
* Suggested way to calibrate the BLC is by covering the lens and record the raw data.
* Then, use the recorded data to calibrate the BLC.
*/
esp_isp_blc_config_t blc_config = {
.window = {
.top_left = {
.x = 0,
.y = 0,
},
.btm_right = {
.x = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES,
.y = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES,
},
},
.filter_enable = true,
.filter_threshold = {
.top_left_chan_thresh = 128,
.top_right_chan_thresh = 128,
.bottom_left_chan_thresh = 128,
.bottom_right_chan_thresh = 128,
},
.stretch = {
.top_left_chan_stretch_en = true,
.top_right_chan_stretch_en = true,
.bottom_left_chan_stretch_en = true,
.bottom_right_chan_stretch_en = true,
},
};
esp_err_t ret = esp_isp_blc_configure(isp_proc, &blc_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure BLC: %d", ret);
return ret;
}
ret = esp_isp_blc_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable BLC: %d", ret);
return ret;
}
esp_isp_blc_offset_t blc_offset = {
.top_left_chan_offset = 20,
.top_right_chan_offset = 20,
.bottom_left_chan_offset = 20,
.bottom_right_chan_offset = 20,
};
ret = esp_isp_blc_set_correction_offset(isp_proc, &blc_offset);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set BLC offset: %d", ret);
return ret;
}
ESP_LOGI(TAG, "BLC module configured");
#else
ESP_LOGW(TAG, "BLC not supported on this chip revision");
return ESP_ERR_NOT_SUPPORTED;
#endif
return ESP_OK;
}
/**
* @brief Configure BF (Bayer Filter) module
*/
static esp_err_t example_isp_pipelines_config_bf(isp_proc_handle_t isp_proc)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_isp_bf_config_t bf_config = {
.denoising_level = 5,
.padding_mode = ISP_BF_EDGE_PADDING_MODE_SRND_DATA,
.bf_template = {
{1, 2, 1},
{2, 4, 2},
{1, 2, 1},
},
.padding_line_tail_valid_start_pixel = 0,
.padding_line_tail_valid_end_pixel = 0,
};
esp_err_t ret = esp_isp_bf_configure(isp_proc, &bf_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure BF: %d", ret);
return ret;
}
ret = esp_isp_bf_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable BF: %d", ret);
return ret;
}
ESP_LOGI(TAG, "BF module configured");
return ESP_OK;
}
/**
* @brief Configure LSC (Lens Shading Correction) module
*/
static esp_err_t example_isp_pipelines_config_lsc(isp_proc_handle_t isp_proc)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
#if CONFIG_ESP32P4_REV_MIN_FULL >= 100
esp_isp_lsc_gain_array_t gain_array = {};
esp_isp_lsc_config_t lsc_config = {
.gain_array = &gain_array,
};
size_t gain_size = 0;
esp_err_t ret = esp_isp_lsc_allocate_gain_array(isp_proc, &gain_array, &gain_size);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to allocate LSC gain array: %d", ret);
return ret;
}
isp_lsc_gain_t gain_val = {
.decimal = 204,
.integer = 0,
};
for (int i = 0; i < gain_size; i++) {
gain_array.gain_r[i].val = gain_val.val;
gain_array.gain_gr[i].val = gain_val.val;
gain_array.gain_gb[i].val = gain_val.val;
gain_array.gain_b[i].val = gain_val.val;
}
ret = esp_isp_lsc_configure(isp_proc, &lsc_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure LSC: %d", ret);
return ret;
}
ret = esp_isp_lsc_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable LSC: %d", ret);
return ret;
}
ESP_LOGI(TAG, "LSC module configured");
#else
ESP_LOGW(TAG, "LSC not supported on this chip revision");
return ESP_ERR_NOT_SUPPORTED;
#endif
return ESP_OK;
}
/**
* @brief Configure DEMOSAIC module
*/
static esp_err_t example_isp_pipelines_config_demosaic(isp_proc_handle_t isp_proc)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_isp_demosaic_config_t demosaic_config = {
.grad_ratio = {
.integer = 2,
.decimal = 5,
},
};
esp_err_t ret = esp_isp_demosaic_configure(isp_proc, &demosaic_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure DEMOSAIC: %d", ret);
return ret;
}
ret = esp_isp_demosaic_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable DEMOSAIC: %d", ret);
return ret;
}
ESP_LOGI(TAG, "DEMOSAIC module configured");
return ESP_OK;
}
/**
* @brief Configure CCM (Color Correction Matrix) module
*/
static esp_err_t example_isp_pipelines_config_ccm(isp_proc_handle_t isp_proc, const float (*matrix)[3])
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
/**
* CCM is used for color correction and white balance adjustment.
* It should be configured after demosaic and before gamma correction.
*
* The matrix format is:
* [R_out] [RR RG RB] [R_in]
* [G_out] = [GR GG GB] [G_in]
* [B_out] [BR BG BB] [B_in]
*
* For ESP32P4 ECO5:
* - Matrix coefficients range: ±15.996 (4-bit integer + 8-bit fraction)
* - For earlier versions: ±3.999 (2-bit integer + 10-bit fraction)
*/
esp_isp_ccm_config_t ccm_config = {
.matrix = {
// Default identity matrix (no color correction)
{1.0, 0.0, 0.0}, // R channel: R = 1.0*R + 0.0*G + 0.0*B
{0.0, 1.0, 0.0}, // G channel: G = 0.0*R + 1.0*G + 0.0*B
{0.0, 0.0, 1.0} // B channel: B = 0.0*R + 0.0*G + 1.0*B
},
.saturation = false // Don't use saturation for out-of-range values
};
// Use provided matrix if available
if (matrix != NULL) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
ccm_config.matrix[i][j] = matrix[i][j];
}
}
}
esp_err_t ret = esp_isp_ccm_configure(isp_proc, &ccm_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure CCM: %d", ret);
return ret;
}
ret = esp_isp_ccm_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable CCM: %d", ret);
return ret;
}
ESP_LOGI(TAG, "CCM module configured");
return ESP_OK;
}
/**
* @brief Configure GAMMA correction module
*/
static esp_err_t example_isp_pipelines_config_gamma(isp_proc_handle_t isp_proc, uint32_t (*gamma_curve)(uint32_t))
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Use provided gamma curve or default
uint32_t (*curve_func)(uint32_t) = gamma_curve;
if (curve_func == NULL) {
curve_func = s_gamma_correction_curve;
}
isp_gamma_curve_points_t pts = {};
esp_err_t ret = esp_isp_gamma_fill_curve_points(curve_func, &pts);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to fill gamma curve points: %d", ret);
return ret;
}
ret = esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_R, &pts);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure gamma R: %d", ret);
return ret;
}
ret = esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_G, &pts);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure gamma G: %d", ret);
return ret;
}
ret = esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_B, &pts);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure gamma B: %d", ret);
return ret;
}
ret = esp_isp_gamma_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable gamma: %d", ret);
return ret;
}
ESP_LOGI(TAG, "GAMMA module configured");
return ESP_OK;
}
/**
* @brief Configure SHARPEN module
*/
static esp_err_t example_isp_pipelines_config_sharpen(isp_proc_handle_t isp_proc)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_isp_sharpen_config_t sharpen_config = {
.h_freq_coeff = {
.integer = 2,
.decimal = 0,
},
.m_freq_coeff = {
.integer = 2,
.decimal = 0,
},
.h_thresh = 255,
.l_thresh = 0,
.padding_mode = ISP_SHARPEN_EDGE_PADDING_MODE_SRND_DATA,
.sharpen_template = {
{1, 2, 1},
{2, 4, 2},
{1, 2, 1},
},
.padding_line_tail_valid_start_pixel = 0,
.padding_line_tail_valid_end_pixel = 0,
};
esp_err_t ret = esp_isp_sharpen_configure(isp_proc, &sharpen_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure SHARPEN: %d", ret);
return ret;
}
ret = esp_isp_sharpen_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable SHARPEN: %d", ret);
return ret;
}
ESP_LOGI(TAG, "SHARPEN module configured");
return ESP_OK;
}
/**
* @brief Configure COLOR adjustment module
*/
static esp_err_t example_isp_pipelines_config_color(isp_proc_handle_t isp_proc)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_isp_color_config_t color_config = {
.color_contrast = {
.integer = 1,
.decimal = 0,
},
.color_saturation = {
.integer = 1,
.decimal = 0,
},
.color_hue = 0,
.color_brightness = 0,
};
esp_err_t ret = esp_isp_color_configure(isp_proc, &color_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure COLOR: %d", ret);
return ret;
}
ret = esp_isp_color_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable COLOR: %d", ret);
return ret;
}
ESP_LOGI(TAG, "COLOR module configured");
return ESP_OK;
}
#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE
/**
* @brief Configure CROP module
*/
static esp_err_t example_isp_pipelines_config_crop(isp_proc_handle_t isp_proc,
int crop_left,
int crop_top,
int crop_right,
int crop_bottom)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_isp_crop_config_t crop_config = {
.window = {
.top_left = {
.x = crop_left,
.y = crop_top
},
.btm_right = {
.x = crop_right,
.y = crop_bottom
}
}
};
esp_err_t ret = esp_isp_crop_configure(isp_proc, &crop_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure CROP: %d", ret);
return ret;
}
ret = esp_isp_crop_enable(isp_proc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable CROP: %d", ret);
return ret;
}
ESP_LOGI(TAG, "CROP module configured: (%d,%d) to (%d,%d)",
crop_left, crop_top, crop_right, crop_bottom);
return ESP_OK;
}
#endif
esp_err_t example_isp_init_all_pipelines(isp_proc_handle_t isp_proc)
{
if (isp_proc == NULL) {
return ESP_ERR_INVALID_ARG;
}
ESP_ERROR_CHECK(example_isp_pipelines_config_blc(isp_proc));
ESP_ERROR_CHECK(example_isp_pipelines_config_bf(isp_proc));
ESP_ERROR_CHECK(example_isp_pipelines_config_lsc(isp_proc));
ESP_ERROR_CHECK(example_isp_pipelines_config_demosaic(isp_proc));
ESP_ERROR_CHECK(example_isp_pipelines_config_ccm(isp_proc, NULL));
ESP_ERROR_CHECK(example_isp_pipelines_config_gamma(isp_proc, NULL));
ESP_ERROR_CHECK(example_isp_pipelines_config_sharpen(isp_proc));
ESP_ERROR_CHECK(example_isp_pipelines_config_color(isp_proc));
#ifdef CONFIG_EXAMPLE_ISP_CROP_ENABLE
ESP_ERROR_CHECK(example_isp_pipelines_config_crop(isp_proc,
CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_H,
CONFIG_EXAMPLE_ISP_CROP_TOP_LEFT_V,
CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_H,
CONFIG_EXAMPLE_ISP_CROP_BOTTOM_RIGHT_V));
#endif
ESP_LOGI(TAG, "All ISP pipeline modules initialized");
return ESP_OK;
}