STM32N6的开发日记(6):用ISP中间件点亮IMX335相机的专业画质

博主主要在WX写作,C站消息不能及时看见,如有需要联系请关注:《实在太懒于是不想取名》获取联系方式。

STM32N6作为意法半导体推出的首款集成自研神经处理单元的STM32产品以"MCU+NPU"的异构架构重新定义了边缘AI的算力边界,是意法半导体的MCU最前沿技术栈,不过由于其高难度技术应用以及需要的极其深厚的STM32使用经验以及神经网络基础概念,因此上手难度非常的高。

自从STM32N6发布以来,博主有幸获得一块STM32N6570-DK开发板,闲暇之余陆陆续续折腾如何开发。因此将会陆陆续续发表一些使用STM32N6的使用笔记,以供将来的使用者参考。

回顾学习历程,踩了很多很多的坑,在后续使用STM32N6的文章中也会向大家陆续介绍这些点。

上一期我们介绍了如何在 STM32N6 上使用 DCMIPP 读取 IMX335 摄像头的数据,并通过 LCD 显示屏实时显示摄像头图像。这已经是一个非常重要的基础步骤,让我们成功地把IMX335的原始Bayer数据流送进了MCU并显示了出来。

但是,正如大家所知,IMX335是一款专业级5MP CMOS图像传感器,它直接输出的RAW Bayer数据如果不经过适当的图像信号处理(ISP),画面通常会呈现出严重的偏色、对比度不足、噪声明显、动态范围有限等问题。直接显示的画面往往是"绿绿的"或"偏粉",这正是典型的未经过demosaic、白平衡、gamma校正等处理的 RAW 图表现。

要让IMX335真正发挥出专业水准的画质,我们必须对图像进行完整的ISP(Image Signal Processing)处理链路,包括但不限于:黑电平校正(Black Level Compensation)、坏点校正(Defective Pixel Correction)、去马赛克(Demosaicing)------从Bayer RGGB格局还原RGB、自动白平衡(AWB)、色彩校正(Color Correction Matrix, CCM)、伽马校正(Gamma Correction)、色彩增强/饱和度调整、降噪(Noise Reduction)、锐化(Sharpening)、镜头畸变校正(Lens Shading Correction / 周边光量补偿)、自动曝光(AE)与自动增益(AGC)控制

幸运的是,STM32N6系列集成了功能强大的硬件ISP模块,并且ST官方提供了配套的STM32 ISP中间件(STM32-MW-ISP),可以帮助我们快速实现上述大部分处理功能,而无需从零编写复杂的ISP算法。

本期重点:让IMX335的画面"正常且好看"

STM32_ISP_IQTune

ST提供了专业的ISP软件:STM32_ISP_IQTune来帮我我们设置CMOS相机参数:

我们可以从ST官网找到STM32_IQTune的Windows版本和用于STM32N6的程序。

利用STM32CubeProgrammer将ISP程序烧入到STM32N6570-DK开发板中,将开发板的USB线连接到PC上。

接着我们打开STM32_ISP_IQTune软件,可以连接到STM32N6570-DK开发板上:

进入配置界面,我们可以读取到STM32N6570-DK官方推荐的ISP参数,这里我们可以点击左边的Start tuning from scratch重置参数(非专业不推荐)。

设置完各个参数之后,调节到我们满意的图像界面,接着我们就可以导出我们的参数配置:

导出成.h版本后,就可以获得参数.h文件

代码修改

复制代码
int32_tIMX335_Init(I2C_HandleTypeDef *hi2c, uint16_t DevAddr);
int32_tIMX335_ReadID(I2C_HandleTypeDef *hi2c, uint16_t DevAddr, uint32_t *Id);
int32_tIMX335_SetGain(I2C_HandleTypeDef *hi2c, uint16_t DevAddr, int32_t gain);
int32_tIMX335_SetExposure(I2C_HandleTypeDef *hi2c, uint16_t DevAddr, int32_t exposure);
int32_tIMX335_SetFrequency(I2C_HandleTypeDef *hi2c, uint16_t DevAddr, int32_t frequency);
int32_tIMX335_SetFramerate(I2C_HandleTypeDef *hi2c, uint16_t DevAddr, int32_t framerate);
int32_tIMX335_MirrorFlipConfig(I2C_HandleTypeDef *hi2c, uint16_t DevAddr, uint32_t Config);
int32_tIMX335_GetSensorInfo(IMX335_SensorInfo_t *Info);
int32_tIMX335_SetTestPattern(I2C_HandleTypeDef *hi2c, uint16_t DevAddr, int32_t mode);

上一期我们实现了IMX335的功能函数,包括传感器的基本初始化、ID 读取、增益/曝光控制、时钟频率与帧率设置、镜像翻转、测试图案输出,以及获取传感器基本信息等操作。

接着我们使用ST提供的ISP库,将库文件(.c/.h/.a)添加到我们的工程中:

ISP的API对接

复制代码
#define IMX335_I2C_ADDRESS    (0x1A << 1)
staticint32_t isp_gain     = 0;    // mdb 单位 (×1000)
staticint32_t isp_exposure = 0;    // 微秒 us
int NbMainFrames = 0;
ISP_HandleTypeDef hcamera_isp;
/**
 * @brief ISP Middleware helper: 获取传感器信息
 */
ISP_StatusTypeDef GetSensorInfoHelper(uint32_t Instance, ISP_SensorInfoTypeDef *SensorInfo)
{
    (void)Instance;  // 未使用参数
    IMX335_SensorInfo_t info = {0};
    if (IMX335_GetSensorInfo(&info) != IMX335_OK)
    {
        return ISP_ERR_SENSORINFO;  
    }
    strncpy(SensorInfo->name, info.name, ISP_SENSOR_INFO_MAX_LENGTH - 1);
    SensorInfo->name[ISP_SENSOR_INFO_MAX_LENGTH - 1] = '\0';
    SensorInfo->bayer_pattern  = info.bayer_pattern;  
    SensorInfo->color_depth    = info.color_depth;     // 10
    SensorInfo->width          = info.width;           // 2592
    SensorInfo->height         = info.height;          // 1944
    SensorInfo->gain_min       = info.gain_min;        // mdb 单位
    SensorInfo->gain_max       = info.gain_max;
    SensorInfo->exposure_min   = info.exposure_min;    // us
    SensorInfo->exposure_max   = info.exposure_max;
    return ISP_OK;
}
/**
 * @brief ISP Middleware helper: 设置传感器模拟增益
 */
ISP_StatusTypeDef SetSensorGainHelper(uint32_t Instance, int32_t Gain)
{
    (void)Instance;
    isp_gain = Gain;
    if (IMX335_SetGain(&hi2c1, IMX335_I2C_ADDRESS, Gain) != IMX335_OK)
    {
        return ISP_ERR_SENSORGAIN;
    }
    return ISP_OK;
}
/**
 * @brief ISP Middleware helper: 获取当前增益值(软件缓存)
 */
ISP_StatusTypeDef GetSensorGainHelper(uint32_t Instance, int32_t *Gain)
{
    (void)Instance;
    *Gain = isp_gain;
    return ISP_OK;
}
/**
 * @brief ISP Middleware helper: 设置传感器曝光时间(单位:微秒)
 */
ISP_StatusTypeDef SetSensorExposureHelper(uint32_t Instance, int32_t Exposure)
{
    (void)Instance;

    isp_exposure = Exposure;
    if (IMX335_SetExposure(&hi2c1, IMX335_I2C_ADDRESS, Exposure) != IMX335_OK)
    {
        return ISP_ERR_SENSOREXPOSURE;
    }
    return ISP_OK;
}
/*
 * @brief ISP Middleware helper: 获取当前曝光值(软件缓存)
 */
ISP_StatusTypeDef GetSensorExposureHelper(uint32_t Instance, int32_t *Exposure)
{
    (void)Instance;
    *Exposure = isp_exposure;
    return ISP_OK;
}

接着将ISP的API和我们的IMX335的API对接,包括获取各类信息,设置各类参数的函数。

复制代码
voidHAL_DCMIPP_PIPE_VsyncEventCallback(DCMIPP_HandleTypeDef *hdcmipp, uint32_t Pipe)
{
  UNUSED(hdcmipp);
/* Update the frame counter and call the ISP statistics handler */
switch (Pipe)
  {
case DCMIPP_PIPE0 :
      ISP_IncDumpFrameId(&hcamera_isp);
break;
case DCMIPP_PIPE1 :
      ISP_IncMainFrameId(&hcamera_isp);
      ISP_GatherStatistics(&hcamera_isp);
      NbMainFrames++;
break;
case DCMIPP_PIPE2 :
      ISP_IncAncillaryFrameId(&hcamera_isp);

break;
  }
}

添加HAL_DCMIPP_PIPE_VsyncEventCallback函数用来为DCMIPP实现VSYNC中断,在每帧画面接收完成后就会调用这个中断,更新帧计数、采集 ISP 统计数据、为自动曝光/白平衡等算法提供实时输入。

NbMainFrames用于帧计数。

复制代码
voidDCMIPP_ISP_Init(void)
{
    ISP_AppliHelpersTypeDef appliHelpers = {0};
    ISP_StatAreaTypeDef statArea = {0};
    // 填充 ISP middleware 需要的回调函数
    appliHelpers.GetSensorInfo    = GetSensorInfoHelper;
    appliHelpers.SetSensorGain    = SetSensorGainHelper;
    appliHelpers.GetSensorGain    = GetSensorGainHelper;
    appliHelpers.SetSensorExposure = SetSensorExposureHelper;
    appliHelpers.GetSensorExposure = GetSensorExposureHelper;
    // 统计区域(全画幅)
    statArea.X0    = 0;
    statArea.Y0    = 0;
    statArea.XSize = 2592;
    statArea.YSize = 1944;
    // 初始化 ISP 中间件
    if (ISP_Init(&hcamera_isp, &hdcmipp, 0, &appliHelpers, &statArea, ISP_IQParamCacheInit[0]) != ISP_OK)
    {
        Error_Handler();
    }

    // 启动 DCMIPP CSI PIPE
    if (HAL_DCMIPP_CSI_PIPE_Start(&hdcmipp, DCMIPP_PIPE1, DCMIPP_VIRTUAL_CHANNEL0,
                                  (uint8_t *)lcd_bg_buffer, DCMIPP_MODE_CONTINUOUS) != HAL_OK)
    {
        Error_Handler();
    }
    // 启动 ISP 处理
    if (ISP_Start(&hcamera_isp) != ISP_OK)
    {
        Error_Handler();
    }
    while (NbMainFrames < 60)
    {
        if (ISP_BackgroundProcess(&hcamera_isp) != ISP_OK)
        {
            HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_10);
        }
    }
}

接着开始STM32的ISP处理,统计60帧的数据送入ISP处理算法。

至此,我们终于完成了从"原始 Bayer 数据"到"可观看彩色图像"的完整链路:IMX335 传感器 → DCMIPP 数据接收 → ISP 中间件处理 → LCD 显示输出。

通过对接 IMX335 的控制接口与 ST 提供的 ISP 中间件,我们实现了传感器信息获取、增益/曝光的动态设置,并在每一帧到来时通过 VSYNC 中断可靠地更新帧计数和采集统计数据。这一系列工作,让画面从最初的"绿油油""偏粉""噪点严重"逐步走向了"能看""基本正常"的阶段。

当然,目前的画面效果仍然主要依赖于 ST 官方示例中的默认IQ参数,距离"专业级""讨喜""适合实际应用"的水准还有一定距离。真正的画质飞跃,通常需要在 STM32_ISP_IQTune 软件中进行针对性的调参:调整黑电平、去马赛克算法、白平衡参考点、gamma 曲线、降噪强度、锐化程度等,并反复对比实拍效果,才能让 IMX335 在不同光照、不同场景下都呈现出令人满意的表现。

但无论如何,走到这一步已经是一个非常重要的里程碑------我们不再只是"把数据搬运到屏幕上",而是真正拥有了对图像信号处理链路的控制能力。这为后续更高级的功能奠定了基础。

相关推荐
天狼IoT2 小时前
STM32开发速查笔记
stm32·单片机·嵌入式硬件
济6172 小时前
FreeRTOS 控制任务设计 (1)--- 双模式闭环控制:IDLE/RUN 状态机与任务通知机制
stm32·单片机·嵌入式·freertos
balance_rui11 小时前
FreeRTOS
笔记·stm32
LCG元11 小时前
STM32实战案例:基于HC-SR04的超声波测距与倒车雷达系统
stm32·单片机·嵌入式硬件
华清远见IT开放实验室12 小时前
智能手表完整项目实现,比赛求职双向加分,基于嵌入式大赛推荐开发板(STM32U5)
stm32·单片机·嵌入式硬件·学习·智能手表·嵌入式大赛
BackCatK Chen12 小时前
STM32保姆级入门教程|第8章:PT100高精度测温实战 + ADS1232驱动 + 24位ADC数据解析(功能超详细+CubeIDE手把手)
stm32·stm32cubeide·高精度测温·ads1232·pt100·24位adc·工业实战
危桥带雨12 小时前
FLASH理论基础
stm32·单片机·嵌入式硬件
进击的小头13 小时前
第18篇:嵌入式电机控制专用外设:正交编码脉冲模块原理与闭环控制应用
arm开发·单片机·嵌入式硬件
feifeigo12313 小时前
STM32 LCD彩色液晶屏显示汉字、英文、数字
stm32·单片机·嵌入式硬件