4. 图像采集与预处理:OV2640/OV7670摄像头模块接入、DCMI接口配置、图像格式(RGB565/JPEG)转换、缩放、灰度化、二值化等预处理方法
在TinyML工业仪表读数识别系统中,图像采集与预处理是决定后续模型推理精度的第一道关口。STM32F4系列微控制器内置的DCMI(Digital Camera Interface)外设,能够以DMA方式直接接收来自OV2640或OV7670等摄像头模块的并行数据,从而在不占用CPU大量运算的情况下完成原始图像数据的搬运。本章将围绕摄像头选型、硬件连接、DCMI寄存器配置、图像格式转换以及经典预处理流水线展开,帮助你在资源受限的MCU上构建一条高效、稳定的视觉数据通路。
4.1 摄像头模块选型与硬件接入
OV2640与OV7670是两款在嵌入式视觉领域广泛使用的CMOS图像传感器。OV2640最高支持200万像素(1600x1200),内置JPEG压缩引擎,输出格式灵活;OV7670则为30万像素(640x480),仅支持RGB565/YUV422等原始格式,但价格更低、驱动资料丰富。在工业仪表读数场景中,由于表盘区域通常固定且光照可控,OV2640的JPEG输出能力可以大幅降低存储与传输带宽压力,但JPEG解码需要额外消耗CPU周期;OV7670的RGB565输出则更适合直接进行像素级预处理。
硬件连接上,STM32F4的DCMI接口支持8位并行数据输入,典型接线如下:
OV2640/7670 STM32F407
----------------------------
D0-D7 -> PC6-PC13 (DCMI_D0-D7)
PCLK -> PA6 (DCMI_PIXCK)
VSYNC -> PB7 (DCMI_VSYNC)
HSYNC -> PA4 (DCMI_HSYNC)
SCCB_SCL -> PB6 (I2C1_SCL)
SCCB_SDA -> PB7 (I2C1_SDA)
XCLK -> PC4 (MCO1输出12MHz)
RESET -> PD3 (GPIO控制)
PWDN -> PD4 (GPIO控制,低电平工作)
4.2 DCMI接口配置与DMA传输
DCMI外设的核心配置包括:同步方式(硬件VSYNC/HSYNC)、像素时钟极性、数据宽度(8/10/12/14位)、帧捕获模式(连续/快照)以及嵌入式同步码(用于BT.656等串行同步)。对于OV2640/7670这类独立同步信号传感器,我们采用硬件同步模式,配置流程如下:
- 使能时钟:RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB, ENABLE); 同时使能DCMI与DMA2时钟。
- GPIO复用配置:将PC6-PC13、PA6、PB7、PA4全部设置为AF13(DCMI复用功能)。
- DCMI参数初始化:设置VSYNC/HSYNC极性为低有效(OV系列默认),PCLK上升沿采样,连续模式。
- DMA双缓冲配置:使用DMA2 Stream1,通道1,外设地址为DCMI_DR,内存地址指向两个帧缓冲,数据宽度32位,循环模式。
- 启动捕获:使能DCMI捕获,等待VSYNC中断或DMA传输完成中断。
DCMI_InitTypeDef dcmi;
DMA_InitTypeDef dma;
dcmi.DCMI_CaptureMode = DCMI_CaptureMode_Continuous;
dcmi.DCMI_SynchroMode = DCMI_SynchroMode_Hardware;
dcmi.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising;
dcmi.DCMI_VSPolarity = DCMI_VSPolarity_Low;
dcmi.DCMI_HSPolarity = DCMI_HSPolarity_Low;
dcmi.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame;
dcmi.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_8b;
DCMI_Init(&dcmi);
dma.DMA_Channel = DMA_Channel_1;
dma.DMA_PeripheralBaseAddr = (uint32_t)&DCMI->DR;
dma.DMA_Memory0BaseAddr = (uint32_t)frameBuf[0];
dma.DMA_DIR = DMA_DIR_PeripheralToMemory;
dma.DMA_BufferSize = 640*480*2/4; // RGB565每像素2字节
dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
dma.DMA_Mode = DMA_Mode_Circular;
dma.DMA_Priority = DMA_Priority_High;
dma.DMA_FIFOMode = DMA_FIFOMode_Enable;
dma.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
dma.DMA_MemoryBurst = DMA_MemoryBurst_Single;
dma.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream1, &dma);
4.3 图像格式转换:RGB565与JPEG
OV2640/7670默认输出RGB565格式,每个像素占用2字节,排列为高字节低5位R、中间6位G、低字节低5位B。在STM32的32位内存中,连续两个像素会打包为一个32位字,因此DMA传输时以字为单位效率最高。RGB565转RGB888或灰度图时,需要位掩码与移位操作:
uint16_t rgb565 = *(uint16_t*)src;
uint8_t r = (rgb565 >> 11) & 0x1F;
uint8_t g = (rgb565 >> 5) & 0x3F;
uint8_t b = rgb565 & 0x1F;
// 扩展至8位
r = (r << 3) | (r >> 2);
g = (g << 2) | (g >> 4);
b = (b << 3) | (b >> 2);
// 灰度化(加权平均)
uint8_t gray = (r*30 + g*59 + b*11) / 100;
若使用OV2640的JPEG输出模式,DCMI接收到的是一段压缩码流,无法直接进行像素操作。此时需要先通过硬件JPEG解码器(STM32F4部分型号具备)或软件解码库(如TJpgDec)还原为RGB565。考虑到MCU资源,建议在工业仪表场景中直接使用RGB565输出,因为表盘图像通常颜色单一、背景固定,JPEG的压缩优势并不明显,反而增加解码延迟。
4.4 预处理流水线:缩放、灰度化与二值化
TinyML模型输入尺寸通常较小(如96x96、48x48),而摄像头输出为640x480或更大。直接缩放需要双线性插值,但MCU上更常用的是区域采样(像素丢弃)或最近邻插值。对于仪表读数,表盘数字区域往往只占画面中心部分,建议先通过ROI裁剪再缩放,既减少计算量又避免背景干扰。
| 预处理步骤 | 方法 | 计算量(96x96输出) | 适用场景 |
|---|---|---|---|
| ROI裁剪 | 固定坐标截取中心区域 | 无乘法 | 表盘位置固定 |
| 缩放 | 最近邻/双线性 | 9K~36K次乘法 | 实时性要求高 |
| 灰度化 | 加权平均 | 9K次乘法 | 颜色信息无意义 |
| 二值化 | Otsu/固定阈值 | 9K次比较 | 数字与背景对比强 |
| 归一化 | (x-mean)/std | 9K次减法+除法 | 神经网络输入 |
灰度化后,二值化能有效分离数字与表盘背景。固定阈值适用于光照稳定的环境,而Otsu自适应阈值则能应对轻微光照变化。Otsu算法在MCU上实现时,需先统计256级灰度直方图,再遍历阈值计算类间方差,计算量约256次乘加,完全可接受。二值化后的图像可直接作为单通道Tensor输入TinyML模型,或进一步进行形态学开运算去除噪点。
// Otsu二值化核心代码
uint32_t hist[256] = {0};
for (int i = 0; i < imgSize; i++) {
hist[gray[i]]++;
}
float sum = 0;
for (int i = 0; i < 256; i++) sum += i * hist[i];
float sumB = 0, wB = 0, wF = 0, maxVar = 0;
int threshold = 0;
for (int t = 0; t < 256; t++) {
wB += hist[t];
if (wB == 0) continue;
wF = imgSize - wB;
if (wF == 0) break;
sumB += t * hist[t];
float mB = sumB / wB;
float mF = (sum - sumB) / wF;
float var = wB * wF * (mB - mF) * (mB - mF);
if (var > maxVar) { maxVar = var; threshold = t; }
}
4.5 案例:指针式压力表读数识别预处理
假设我们需要识别一个量程0-1.6MPa的指针式压力表,表盘直径为200像素,指针旋转中心位于(100,100)。实际部署时,摄像头固定于表盘正前方30cm处,光照为LED恒亮。预处理流程设计如下:
- ROI裁剪:从640x480原图中截取(80,80)到(560,400)区域,去除表盘外背景。
- 缩放:将480x320的ROI双线性缩放到128x128,保留表盘细节。
- 灰度化:使用加权平均公式,得到单通道灰度图。
- 高斯滤波:3x3核卷积,抑制传感器噪声(约需128x128x9次乘加)。
- Otsu二值化:自动计算阈值,将指针与刻度线分割为白色前景。
- 形态学细化:对二值图执行一次腐蚀+膨胀,去除孤立噪点。
最终得到的128x128二值图直接送入一个轻量级CNN(如MobileNetV2的简化版)进行指针角度回归。实测在STM32F407@168MHz下,整个预处理耗时约12ms,模型推理约85ms,总帧率可达10fps,满足工业实时监测需求。
uint8_t数组而非int,利用CMSIS-DSP库的arm_mean_q15等函数加速统计计算;二值化后的图像按位打包(8像素/字节),可减少后续模型输入的数据搬运量。若使用浮点运算,建议开启FPU并启用-O3优化。
4.6 常见误区与调试复盘
调试DCMI时,建议先用逻辑分析仪抓取VSYNC、HSYNC和PCLK波形,确认时序正确后再检查数据线。常见故障现象与排查方向:
- 全黑图像:检查PWDN引脚是否拉低、XCLK是否输出12MHz、DCMI捕获是否使能。
- 图像错位:检查HSYNC/VSYNC极性配置,OV系列通常为低有效。
- 颜色异常:检查RGB565字节序,确认高字节在前还是低字节在前。
- 帧率不足:降低分辨率至VGA或QVGA,关闭OV2640的压缩模式。
通过以上步骤,你已经能够从摄像头获取干净的、可直接输入TinyML模型的图像张量。下一阶段将围绕特征提取与模型推理展开,但请务必确保本章的预处理流水线稳定可靠——因为无论模型多么优秀,输入数据的质量始终是精度的上限。