一种在 ESP32-S3 上取巧的清晰度检测方案

前言

最近在负责一个辅助设备的开发,核心需求是:在连拍的多张图片中,实时筛选出最清晰的一张上传至云端进行 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 图像进行像素级操作。基于此,我制定了如下的数据流处理方案:

  1. 配置 CMOS 传感器输出 YUV422 格式的原始位流,此时每两个像素共享一组色度信息,但亮度信息保持全分辨率采样,这为拉普拉斯算子提供了灰度数据源。
  2. 在内存中直接对 Y 通道执行改进型的拉普拉斯卷积运算,通过三帧缓冲区的对比,锁定评分最高的一帧。
  3. 调用软件编码库,将这一帧 YUV 数据重新编码为 JPEG 格式。
  4. 将编码后的二进制流进行 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 上用软件模拟如下过程:

  1. 色彩空间转换与分块 :将 YUV 平面数据切割为 8×88 \times 88×8 的像素块(MCU, Minimum Coded Unit)。
  2. **离散余弦变换 :这是最耗时的步骤。每一个 8×88 \times 88×8 块都需要进行二维 DCT 变换,将空间域的像素强度转换为频率域的系数。这涉及大量的浮点乘加运算(或定点近似运算)。
  3. 量化:利用人类视觉心理学模型,将高频系数除以量化表并取整。这涉及密集的除法运算。
  4. 熵编码:对量化后的系数进行 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)log⁡2P(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");
}

总结

在有限的性能上要根据实际业务做出取舍,不必为了所谓的完美而硬来,不如加钱上更好芯片

相关推荐
wdfk_prog1 小时前
[Linux]学习笔记系列 -- [block]fops
linux·笔记·学习
丝斯20111 小时前
AI学习笔记整理(23)—— AI核心技术(深度学习7)
人工智能·笔记·学习
烤麻辣烫1 小时前
黑马程序员苍穹外卖(新手)DAY10
java·开发语言·学习·spring·intellij-idea
dlwlrma_5161 小时前
STM32使用TIM定时触发ADC通过HAL库实现采样 模拟ADC使用DMA数据传输时发生OVR溢出和恢复DMA
stm32
●VON1 小时前
Flutter vs React Native vs 原生开发:有何不同?
学习·flutter·react native·react.js·openharmony
Freshman小白1 小时前
《英文科技论文写作与学术报告》网课答案(雨课堂、学堂在线...)
网络·学习·答案
Bigan(安)1 小时前
【奶茶Beta专项】【LVGL9.4源码分析】03-显示框架-图层管理
linux·c语言·mcu·arm·unix
云雾J视界1 小时前
51单片机信号处理实战:C语言A/D与D/A转换应用,从传感器采集到PWM控制全解析
c语言·51单片机·信号处理·pwm·模拟信号·数字信号·a/d
WongKyunban1 小时前
使用Valgrind检测内存问题(C语言)
c语言·开发语言