前言
最近在负责一个辅助设备的开发,核心需求是:在连拍的多张图片中,实时筛选出最清晰的一张上传至云端进行 YOLO 识别。
然而众所周知的原因,传统的ov系列摄像头,在光照度稍微差点的环境,拍出来的照片需要比较长的曝光时间,对于客户群体,经常能收到图片过于模糊的投诉,因此,在基于esp32s3基础上,想实现使用一个简单的算法,进行清晰度筛选
最初,我遵循了经典的计算机视觉理论,在 YUV 域使用拉普拉斯算子进行边缘检测。理论上这是完美的,但实际是在落地不可用。本文将记录从"算法流"到"工程流"的演变过程,深入探讨拉普拉斯算子的数学本质、YUV 转 JPEG 的性能瓶颈,以及如何利用信息论原理实现另一种取巧式的优化。
第一部分:回顾拉普拉斯算子
由于学的太久已经忘记,又重温了一次基本原理
1.1 计算机怎么看图像
在计算机视觉中,不像人眼一样,把图像看作二维平面,而是将其视为一个三维地形表面。
-
(x,y)(x, y)(x,y) 是坐标。
-
亮度值 I(x,y)I(x, y)I(x,y) 是该坐标的海拔高度。
计算机理解的所谓的"模糊"与"清晰",其实是对应着地形的平坦 与陡峭。
1.2 怎么找到边缘?
通过以下方式找到边缘,首先是一阶导数求斜率,然后二阶导数求曲率。
-
一阶导数(梯度) :告诉我们地形的坡度。
- Gradient>0Gradient > 0Gradient>0:说明这里是个斜坡。
-
二阶导数(曲率) :告诉我们地形的弯曲程度(或者说坡度的变化率)。
- Laplacian≫0Laplacian \gg 0Laplacian≫0:说明这里发生了剧烈的突变。
对于人眼而言,清晰的图像边缘,意味着亮度的剧烈跳变。 ,事实上在物理层面也是这样的,这种跳变在二阶导数上表现为极大的数值震荡(过零点)。而模糊的图像,其边缘被"抹平"成了缓坡,二阶导数接近于 0。
1.3 离散化推导
在 xxx 方向上,二阶差分公式为:
∂2I∂x2≈I(x+1,y)+I(x−1,y)−2I(x,y)\frac{\partial^2 I}{\partial x^2} \approx I(x+1, y) + I(x-1, y) - 2I(x, y)∂x2∂2I≈I(x+1,y)+I(x−1,y)−2I(x,y)
(即:左边像素 + 右边像素 - 2倍中间像素)
同理,在 yyy 方向上:
∂2I∂y2≈I(x,y+1)+I(x,y−1)−2I(x,y)\frac{\partial^2 I}{\partial y^2} \approx I(x, y+1) + I(x, y-1) - 2I(x, y)∂y2∂2I≈I(x,y+1)+I(x,y−1)−2I(x,y)
拉普拉斯算子 ∇2I\nabla^2 I∇2I 是两个方向之和:
∇2I=∂2I∂x2+∂2I∂y2≈[上下左右四点之和]−4×I(x,y)\nabla^2 I = \frac{\partial^2 I}{\partial x^2} + \frac{\partial^2 I}{\partial y^2} \approx [上下左右四点之和] - 4 \times I(x, y)∇2I=∂x2∂2I+∂y2∂2I≈[上下左右四点之和]−4×I(x,y)
将系数提取出来,就构成了那个经典的卷积核:
0101−41010\]\\begin{bmatrix} 0 \& 1 \& 0 \\\\ 1 \& -4 \& 1 \\\\ 0 \& 1 \& 0 \\end{bmatrix} 0101−41010 #### 1.4 为何清晰度评分是光照强度的函数? 我也被这个困惑了很久,当初上学听课不认真唉。 在直观认知中,"清晰度"应当是一个描述物体几何边界的属性,与环境光照无关。然而,在基于拉普拉斯算子(以及大多数基于梯度的算子)的算法中,清晰度评分本质上是**边缘梯度的幅度统计**。 事实上可以通过建立一个离散边缘模型来从数学上严谨地证明:**清晰度评分与信号幅度(即光照强度)呈正线性相关,甚至在方差计算中呈平方级相关。** ##### 1.4.1 理想阶跃边缘模型 设图像为一维离散信号 f\[x\]f\[x\]f\[x\]。定义一个理想的、绝对清晰的边缘,即信号在 x=0x=0x=0 处发生瞬时跳变,没有过渡像素。 f\[x\]={Lbgx\<0(背景亮度)Lobjx≥0(物体亮度)f\[x\] = \\begin{cases} L_{bg} \& x \< 0 \\quad (\\text{背景亮度}) \\\\ L_{obj} \& x \\ge 0 \\quad (\\text{物体亮度}) \\end{cases}f\[x\]={LbgLobjx\<0(背景亮度)x≥0(物体亮度) 其中,图像的局部对比度定义为 ΔL=∣Lobj−Lbg∣\\Delta L = \|L_{obj} - L_{bg}\|ΔL=∣Lobj−Lbg∣。 ##### 1.4.2 拉普拉斯响应计算 使用标准的一维离散拉普拉斯算子 h=\[1,−2,1\]h = \[1, -2, 1\]h=\[1,−2,1\] 对信号进行卷积运算: g\[x\]=f\[x\]∗h=f\[x−1\]−2f\[x\]+f\[x+1\]g\[x\] = f\[x\] \* h = f\[x-1\] - 2f\[x\] + f\[x+1\]g\[x\]=f\[x\]∗h=f\[x−1\]−2f\[x\]+f\[x+1
我们分别考察两种光照环境下的计算结果。
场景 A:高光照环境
假设背景亮度 Lbg=50L_{bg}=50Lbg=50,物体亮度 Lobj=200L_{obj}=200Lobj=200(高对比度,ΔL=150\Delta L = 150ΔL=150)。
信号序列为:
...,50,50,50,200,200,200,...\dots, 50, 50, \mathbf{50}, \mathbf{200}, 200, 200, \dots...,50,50,50,200,200,200,...
计算边缘处的拉普拉斯响应:
-
在 x=−1x=-1x=−1 处(边缘左侧):50−2(50)+200=15050 - 2(50) + 200 = \mathbf{150}50−2(50)+200=150
-
在 x=0x=0x=0 处(边缘右侧):50−2(200)+200=−15050 - 2(200) + 200 = \mathbf{-150}50−2(200)+200=−150
-
其余平坦区域响应均为 000。
清晰度绝对评分:
Shigh=∣150∣+∣−150∣=300S_{high} = |150| + |-150| = \mathbf{300}Shigh=∣150∣+∣−150∣=300
场景 B:低光照环境
假设环境光变暗 10 倍,传感器线性响应,整体亮度下降为原来的 1/10。
背景亮度 Lbg=5L_{bg}=5Lbg=5,物体亮度 Lobj=20L_{obj}=20Lobj=20(低对比度,ΔL=15\Delta L = 15ΔL=15)。
注意:几何清晰度未变,边缘依然是瞬时跳变,没有模糊过渡。
信号序列为:
...,5,5,5,20,20,20,...\dots, 5, 5, \mathbf{5}, \mathbf{20}, 20, 20, \dots...,5,5,5,20,20,20,...
计算边缘处的拉普拉斯响应:
-
在 x=−1x=-1x=−1 处:5−2(5)+20=155 - 2(5) + 20 = \mathbf{15}5−2(5)+20=15
-
在 x=0x=0x=0 处:5−2(20)+20=−155 - 2(20) + 20 = \mathbf{-15}5−2(20)+20=−15
清晰度绝对评分:
Slow=∣15∣+∣−15∣=30S_{low} = |15| + |-15| = \mathbf{30}Slow=∣15∣+∣−15∣=30
1.4.3 线性度与方差
通过对比 Shigh=300S_{high} = 300Shigh=300 与 Slow=30S_{low} = 30Slow=30,我们得出结论:
结论 1:算子的线性性
由于拉普拉斯算子 ∇2\nabla^2∇2 是线性算子,若输入信号幅度缩放 kkk 倍(fnew=k⋅ff_{new} = k \cdot ffnew=k⋅f),则输出响应也严格缩放 kkk 倍。
∇2(k⋅f)=k⋅(∇2f)\nabla^2 (k \cdot f) = k \cdot (\nabla^2 f)∇2(k⋅f)=k⋅(∇2f)
在本例中,光照降低 10 倍 (k=0.1k=0.1k=0.1),清晰度基础得分也直接降低 10 倍。这导致原本清晰的边缘,在数值上看起来和高光下的噪声(Noise)幅度相当。
结论 2:方差的平方级衰减
在实际cv算法中(如 cv2.Laplacian(img).var()),我们通常使用拉普拉斯响应的方差来代表清晰度。
根据方差性质 Var(kX)=k2Var(X)Var(kX) = k^2 Var(X)Var(kX)=k2Var(X):
若光照强度衰减为原来的 1/101/101/10,则清晰度评分(方差)会衰减为原来的 1/1001/1001/100。
Scorevariance∝(Illumination)2\text{Score}_{variance} \propto (\text{Illumination})^2Scorevariance∝(Illumination)2
这就是为什么在黄昏或夜间,即便摄像头完美对焦,未经归一化处理的拉普拉斯评分也会呈指数级下跌,从而导致算法失效。
第二部分:从实现到放弃
最初的设计逻辑是典型的思维:为了获取最精确的清晰度量化指标,必须在未经压缩的 Raw 图像进行像素级操作。基于此,我制定了如下的数据流处理方案:
- 配置 CMOS 传感器输出 YUV422 格式的原始位流,此时每两个像素共享一组色度信息,但亮度信息保持全分辨率采样,这为拉普拉斯算子提供了灰度数据源。
- 在内存中直接对 Y 通道执行改进型的拉普拉斯卷积运算,通过三帧缓冲区的对比,锁定评分最高的一帧。
- 调用软件编码库,将这一帧 YUV 数据重新编码为 JPEG 格式。
- 将编码后的二进制流进行 Base64 封装并经由串口使用4G 模组上传。
为了在有限的主频下实现实时检测,我针对算子本身进行了极高强度的优化,将单帧计算耗时压缩至 6ms 的极致水平。然而,随后的系统日志不直接让我git回了旧版代码
2.1 日志数据取样分析
以下是设备在真实负载下的 UART 输出日志片段:
// 连拍与计算阶段(算力冗余充足)
I (14279) camera_driver: Frame[0]: Score=18676, Time=6214 us
I (14489) camera_driver: Frame[1]: Score=20973, Time=6491 us
I (14899) camera_driver: Frame[2]: Score=19897, Time=6492 us
// 选中最佳帧,进入编码阶段(流水线阻塞)
I (14909) camera_driver: Selected Best Frame (Score: 20973). Converting to JPEG...
I (15679) camera_driver: JPEG Converted. Size: 14232 bytes.
数据维度的解读:
- 检测阶段耗时 (~6.5ms):这得益于空间降采样策略的成功。
- 编码阶段耗时 (770ms) :15679−14909=770ms15679 - 14909 = \mathbf{770ms}15679−14909=770ms。这意味着在一次简单的"拍照-上传"事务中,CPU 有超过 99% 的时间被阻塞在格式转换上,而非业务逻辑上。
2.2 ROI 与跳步采样
这是基于图像统计学特性的有意设计。
在处理 SVGA (800x600) 分辨率的图像时,全像素遍历涉及 480,000 次卷积运算及相应的内存寻址。然而,在清晰度评价这一特定任务中,全像素遍历是算力上的极大浪费。
首先,我引入了 ROI 机制,仅计算图像中心 50% 的区域。这一决策基于摄影光学的"中心构图偏差"原理以及镜头的 MTF曲线特性------绝大多数成像系统的边缘解析力均弱于中心,且目标物体大概率位于视场中央。忽略边缘像素不仅减少了大量的数据处理量,规避了镜头畸变和边缘暗角带来的噪声干扰。
其次,采用了 跳步采样 策略,步长设为 4。从信号处理的角度看,自然图像具有极高的空间相关性 。相邻像素间的亮度变化通常是平滑连续的,这意味着像素 P(x)P(x)P(x) 与 P(x+1)P(x+1)P(x+1) 在二阶导数特征上具有高度的信息冗余。通过以低于奈奎斯特频率但足以维持统计显著性的间隔进行采样,我们将内存带宽的占用降低了一个数量级,同时并未显著牺牲方差统计的准确性。这种通过降低空间分辨率换取时间分辨率的策略,是嵌入式视觉中的标准范式。
2.3 YUV 转 JPEG
既然算子已经优化到了极致,为何系统依然卡顿?核心矛盾在于 ESP32-S3 的硬件架构与 JPEG 编码算法复杂度之间的不匹配。
JPEG 编码标准并非简单的内存拷贝,而是一个包含多级复杂数学变换的流水线。当我们在 ESP32 上调用 frame2jpg 时,实际上是在通用 CPU 上用软件模拟如下过程:
- 色彩空间转换与分块 :将 YUV 平面数据切割为 8×88 \times 88×8 的像素块(MCU, Minimum Coded Unit)。
- **离散余弦变换 :这是最耗时的步骤。每一个 8×88 \times 88×8 块都需要进行二维 DCT 变换,将空间域的像素强度转换为频率域的系数。这涉及大量的浮点乘加运算(或定点近似运算)。
- 量化:利用人类视觉心理学模型,将高频系数除以量化表并取整。这涉及密集的除法运算。
- 熵编码:对量化后的系数进行 Zig-Zag 扫描,并执行霍夫曼编码或算术编码。这涉及大量的位操作和查表操作。
与全志 T113 或瑞芯微 RK 系列等应用处理器不同,ESP32-S3 是一颗MCU。它缺乏硬编码,也没有专用的核来处理原始内存数据。
尽管内核具备一定的 DSP 指令扩展,但要依靠 CPU 串行地对 48 万个像素点逐块执行上述复杂的数学变换,还是压力太大了。此外,SVGA 分辨率的原始 YUV 数据量接近 1MB,远超 ESP32 的内部 SRAM 容量,这迫使系统必须频繁访问外部 PSRAM。SPI 总线的带宽限制(即便在 Octal 模式下)导致了严重的内存 I/O 瓶颈------CPU 不仅算得慢,而且在等待数据搬运的过程中消耗了大量时钟周期。
结论: 从实现到放弃。
第三部分:一种取巧做法以及为什么可以这样做
既然在通用 MCU 架构上通过软件模拟 JPEG 编码器是导致系统延迟的根本症结,工程上的最优解便是直接利用 OV5640 传感器内置的 DSP 硬件流水线输出 JPEG 码流。然而,这一架构变更带来了一个新的数学难题:我们获得的是经过熵编码后的二进制压缩流,而非空间域的像素矩阵,这意味着经典的基于梯度的卷积算子无法直接应用。为了在压缩域内评估图像质量,我们需要引入一个新的度量维度:信息熵。
3.1清晰度与压缩比特率的谱相关性
在数字图像处理的语境下,"清晰度"在数学上等价于信号的高频分量占比。根据香农信息论,一个系统的信息熵 H(X)H(X)H(X) 定义为该系统不确定性的度量:
H(X)=−∑iP(xi)log2P(xi)H(X) = - \sum_{i} P(x_i) \log_2 P(x_i)H(X)=−i∑P(xi)log2P(xi)
其中 P(xi)P(x_i)P(xi) 是符号出现的概率。在图像压缩中,数据量是图像信息熵的直接映射。我们可以通过分析 JPEG 压缩标准的内部机制,证明图像锐度与压缩后的比特流长度之间存在严格的正相关性。
首先,从频域能量分布的角度分析。
JPEG 压缩的核心是 8×88 \times 88×8 分块的离散余弦变换(DCT)。DCT 将空间域的像素强度 f(x,y)f(x,y)f(x,y) 转换为频率域的系数 F(u,v)F(u,v)F(u,v)。
根据信号处理原理,图像中的平坦区域(即模糊或低频区域)主要由直流分量(DC)和低频交流分量(AC)构成,其能量集中在 DCT 矩阵的左上角;而锐利的边缘(即清晰区域)在数学上表现为阶跃信号。根据吉布斯现象及傅里叶级数的收敛特性,一个理想的阶跃信号在频域上具有无限的带宽,这意味着清晰图像在 DCT 矩阵的右下角依然具有显著的非零系数幅度。
其次,从量化与编码的统计特性分析。
JPEG 算法随后的步骤是量化,即是用 DCT 系数除以量化表 Q(u,v)Q(u,v)Q(u,v) 并取整。由于人眼对高频细节不敏感,量化表通常对高频系数施加较大的除数。
-
对于模糊图像:由于物体边缘被物理低通滤波,其原始的高频 DCT 系数本身幅度极低。经过量化除法后,这些高频系数几乎全部截断为零。在随后的 Zig-Zag 扫描中,这将产生极长的连续零序列。
-
对于清晰图像:由于其保留了锐利的边缘,高频 DCT 系数幅度较高,能够幸存于量化过程并保持非零值。这经常性地打断了零序列的连续性。
最终,导致文件大小差异的决定性因素是熵编码。
JPEG 使用行程编码结合霍夫曼编码来压缩量化后的系数。
-
模糊图像产生的长串零序列(如"连续 50 个 0")可以通过 RLE 极高效地表示,仅占用极少的符号位。
-
清晰图像由于高频非零系数的存在,破坏了零行程的连续性,迫使编码器必须单独记录这些非零系数及其幅值。根据霍夫曼编码原理,需要记录的唯一符号越多,且符号概率分布越均匀,所需的平均码长就越长。
综上所述,定理成立:
在固定量化表和固定分辨率的前提下,图像文件的大小(Size)是其高频信息量的单调递增函数。即:
Size(I)∝∑u,v∈HighFreq∣F(u,v)∣∝Sharpness(I)\text{Size}(I) \propto \sum_{u,v \in \text{HighFreq}} |F(u,v)| \propto \text{Sharpness}(I)Size(I)∝u,v∈HighFreq∑∣F(u,v)∣∝Sharpness(I)
3.2 实测验证与数据显著性
为了验证这一理论模型在嵌入式工程中的有效性,我们在 ESP32-S3 平台上构建了受控实验。我们将 OV2640 传感器配置为硬件压缩模式,输出分辨率锁定为 SVGA (800x600),JPEG 质量因子 Q=30Q=30Q=30。在保持视场内容基本不变的情况下,通过人为引入物理震动来模拟"模糊"样本,通过静止拍摄获取"清晰"样本。
实验数据表现出了极高的统计显著性:
在连续采样的图像序列中,由于物理运动导致的运动模糊帧,其高频信息大量丢失,量化后的 DCT 矩阵极其稀疏,导致最终生成的 JPEG 比特流大小收敛于 10KB 左右。
反之,在运动间隙捕获的清晰帧,由于完整保留了边缘的阶跃特征,迫使霍夫曼编码器输出了更多的比特来记录这些细节,其文件大小稳定在 14KB 以上。
高达 40% 的数据量差异((14−10)/10=40%(14-10)/10 = 40\%(14−10)/10=40%)提供了一个鲁棒的阈值空间。这证明了在受限的计算资源下,我们完全可以利用压缩后的比特率 作为空间清晰度 的有效代理变量(Proxy Variable),从而在 O(1)O(1)O(1) 的时间复杂度下完成图像质量的判别。。
第四部分:函数实现
4.1 方案 A:最初的拉普拉斯实现roi和跳步采样版本
c
/**
* @brief 快速拉普拉斯清晰度检测 (针对 YUV422 格式)
* @note 利用了 Stride (跳步) 和 ROI (感兴趣区域) 进行极速计算
*/
long calculate_sharpness_yuv_test(const uint8_t *img_data, int w, int h) {
long score = 0;
int stride = 4; // 降采样因子,提升速度
// 只计算中心 50% 区域
int start_y = h / 4;
int end_y = (h * 3) / 4;
int bytes_per_row = w * 2; // YUV422 每个像素 2 字节
for (int y = start_y; y < end_y; y += stride) {
int row_offset = y * bytes_per_row;
int row_up = (y - 1) * bytes_per_row;
int row_down = (y + 1) * bytes_per_row;
// X 方向也要乘 2,且必须对齐到偶数位 (Y分量)
for (int x = (w/4)*2; x < (w*3/4)*2; x += stride*2) {
// 获取上下左右中的 Y 分量 (直接操作内存,无浮点运算)
uint8_t center = img_data[row_offset + x];
uint8_t up = img_data[row_up + x];
uint8_t down = img_data[row_down + x];
uint8_t left = img_data[row_offset + x - 2];
uint8_t right = img_data[row_offset + x + 2];
// 拉普拉斯卷积: 4*Center - (Up+Down+Left+Right)
int val = (4 * center) - (up + down + left + right);
score += (val > 0) ? val : -val; // abs()
}
}
return score;
}
4.2 取巧版本
这是在 ESP32 上实现的最终方案。零计算量,零转换延迟。
c
/**
* @brief 完整的测试流程:模拟连拍与筛选
*/
void test_clever_selection_logic() {
printf("--- [TEST] Starting JPEG Heuristic Selection ---\n");
// 模拟 3 张连拍的 JPEG 数据 (仅模拟大小和指针)
// 假设场景:正在快速移动,前两张糊了,第三张清晰
size_t sizes[] = {9800, 10200, 14500};
const char* descs[] = {"Blurry(Motion)", "Blurry(Focus)", "Sharp"};
int best_index = -1;
size_t max_size = 0;
// 模拟连拍循环
for (int i = 0; i < 3; i++) {
// 1. 硬件直接输出 JPEG (耗时 0ms)
size_t current_len = sizes[i];
printf("Frame[%d] (%s): Size = %zu Bytes\n", i, descs[i], current_len);
// 2. 核心算法:比大小
if (current_len > max_size) {
max_size = current_len;
best_index = i;
}
// 3. 释放非最佳帧的内存 (esp_camera_fb_return)
}
printf(">>> RESULT: Selected Frame[%d] (%zu Bytes)\n", best_index, max_size);
if (best_index == 2) {
printf(">>> TEST PASSED: Successfully selected the sharpest image based on entropy.\n");
} else {
printf(">>> TEST FAILED.\n");
}
}
4.3 事实上的理论版本
这是最标准的做法。为了消除光照影响,我们必须先进行直方图均衡化,强行拉伸图像的对比度,然后再进行边缘检测当然这也是esp最冒烟的一集
c
/**
* @note 步骤 1: 统计直方图构建 LUT (去除光照影响)
* @note 步骤 2: 应用 LUT 映射并计算拉普拉斯
* @return 理论上最鲁棒的分数,但计算最慢
*/
long calculate_sharpness_scheme_b_academic(const uint8_t *img_data, int w, int h) {
long score = 0;
int stride = 4; // 依然保留跳步优化,否则更慢
int start_y = h / 4;
int end_y = (h * 3) / 4;
int start_x = (w / 4) * 2;
int end_x = (w * 3 / 4) * 2;
int bytes_per_row = w * 2;
// --- 阶段 1: 统计直方图 (Histogram) ---
uint32_t hist[256] = {0};
int pixel_count = 0;
for (int y = start_y; y < end_y; y += stride) {
int row_offset = y * bytes_per_row;
for (int x = start_x; x < end_x; x += stride * 2) {
uint8_t val = img_data[row_offset + x];
hist[val]++;
pixel_count++;
}
}
// --- 阶段 2: 构建累积分布函数 (CDF) 映射表 ---
uint8_t lut[256];
uint32_t sum = 0;
float scale = 255.0f / pixel_count;
for (int i = 0; i < 256; i++) {
sum += hist[i];
lut[i] = (uint8_t)(sum * scale);
}
// --- 阶段 3: 映射 + 卷积 ---
for (int y = start_y; y < end_y; y += stride) {
int row_offset = y * bytes_per_row;
int row_up = (y - 1) * bytes_per_row;
int row_down = (y + 1) * bytes_per_row;
for (int x = start_x; x < end_x; x += stride * 2) {
// 注意:这里读取的每一个像素,都必须通过 LUT 查表
uint8_t center = lut[img_data[row_offset + x]];
uint8_t up = lut[img_data[row_up + x]];
uint8_t down = lut[img_data[row_down + x]];
uint8_t left = lut[img_data[row_offset + x - 2]];
uint8_t right = lut[img_data[row_offset + x + 2]];
int val = (4 * center) - (up + down + left + right);
score += (val > 0) ? val : -val;
}
}
return score;
}
4.4 测试基准函数
c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 模拟获取当前时间微秒数
long get_time_us() {
return (long)clock(); // 在 ESP32 上应使用 esp_timer_get_time()
}
void run_benchmark() {
printf("========== ALGORITHM BENCHMARK ==========\n");
// 1. 准备伪造的 YUV 数据 (SVGA 800x600)
int width = 800;
int height = 600;
// 分配 YUV422 内存
uint8_t *fake_yuv = (uint8_t*)malloc(width * height * 2);
// 填充一些随机噪点模拟图像
for(int i=0; i<width*height*2; i++) fake_yuv[i] = rand() % 50;
// 在中心画一个"边缘"
for(int i=300*width*2; i<305*width*2; i++) fake_yuv[i] = 200;
// --- 测试方案 A ---
long t0 = get_time_us();
long score_a = calculate_sharpness_scheme_a(fake_yuv, width, height);
long t1 = get_time_us();
printf("[Scheme A] Fast Laplacian\n");
printf(" Score: %ld\n", score_a);
printf(" Time : %ld ticks (Approx 6ms on ESP32)\n", t1 - t0);
// --- 测试方案 B ---
t0 = get_time_us();
long score_b = calculate_sharpness_scheme_b_academic(fake_yuv, width, height);
t1 = get_time_us();
printf("[Scheme B] Histogram Eq + Laplacian\n");
printf(" Score: %ld (Normalized & Robust)\n", score_b);
printf(" Time : %ld ticks (Approx 25ms+ on ESP32)\n", t1 - t0);
printf(" NOTE : High CPU load due to LUT lookup & 2-pass scan.\n");
// --- 测试方案 C ---
// 模拟连拍 3 张 JPEG 的大小
size_t jpeg_sizes[] = {10500, 9800, 14200};
t0 = get_time_us();
int best_idx = select_best_jpeg_heuristic(jpeg_sizes, 3);
t1 = get_time_us();
printf("[Scheme C] JPEG Entropy Heuristic\n");
printf(" Selected Frame Index: %d (Size: %zu)\n", best_idx, jpeg_sizes[best_idx]);
printf(" Time : %ld ticks (Instant)\n", t1 - t0);
printf(" NOTE : Zero computational cost. Requires Hardware JPEG Encoder.\n");
free(fake_yuv);
printf("=========================================\n");
}
总结
在有限的性能上要根据实际业务做出取舍,不必为了所谓的完美而硬来,不如加钱上更好芯片