TXW82x Scale3 均值缩放优化:降低摩尔纹与锯齿
TXW82x Scale3 均值缩放优化:降低摩尔纹与锯齿
1. 优化背景
TXW82x 的摄像头预览链路可以通过 Scale3 硬件将 VPP 图像直接缩放到 LCD 所需尺寸。直接缩放速度快、CPU 占用低,但当输入画面包含以下高频内容时,低分辨率输出容易出现视觉伪影:
- 细密布料、纱窗、网格和条纹产生彩色或明暗相间的摩尔纹;
- 斜线、文字边缘和物体轮廓出现阶梯状锯齿;
- 细节随镜头轻微移动而闪烁或跳变;
- 硬件一次性大比例缩小后,部分像素信息被直接跳过。
本次优化不改变摄像头、VPP 和 Scale3 的基础结构,而是在 Scale3 生成 YUV420P 帧后增加一次固定比例的软件降采样。核心思路是:
先让 Scale3 输出较高分辨率的中间图像,再对相邻 2×2 像素做均值滤波并缩小一半,从而在抽取像素前抑制容易产生混叠的高频信息。
对应提交:
1 | 6820c0d scale3均值缩放 |
核心修改文件:
1 | sdk/app/scale_msi/scale3_normal_msi.c |
2. 优化目标
本次修改的主要目标是改善缩小图像的主观质量,而不是节省 Scale3 输出缓冲区。
具体目标包括:
- 在最终降采样前进行简单低通滤波;
- 降低亮度细节产生的摩尔纹和混叠;
- 平滑斜边与轮廓,减轻阶梯锯齿;
- 使用固定 2:1 比例降低算法复杂度;
- 在原有 YUV420P 缓冲区内原地处理,避免额外申请整帧内存;
- 保持原有 MSI 输出接口和 framebuff 生命周期不变。
需要特别说明:当前实现不会降低 Scale3 帧池最初申请的 PSRAM 容量,因为缓冲区仍按照中间分辨率创建。它降低的是送往下游的有效图像尺寸和 fb->len。
3. 修改前后的数据链路
3.1 修改前
1 | Camera / VPP |
如果 Scale3 直接把高分辨率图像缩到较小尺寸,硬件缩放过程中对高频纹理的预滤波不足,就可能产生混叠。
3.2 修改后
1 | Camera / VPP |
以当前配套配置为例:
| 阶段 | 分辨率 | YUV420P 有效数据量 |
|---|---|---|
| Scale3 中间输出 | 800×480 | 576000 字节 |
| 软件降采样输出 | 400×240 | 144000 字节 |
宽、高各缩小一半,因此最终像素数量和有效数据量均变为原来的四分之一。
4. 为什么均值降采样能改善摩尔纹和锯齿
4.1 问题本质:缩小过程中的混叠
缩小图像意味着目标图像的一个像素要代表源图中的一片区域。如果直接选择区域内的某一个像素,源图中比目标分辨率更细的纹理会被错误地映射成较低频率的条纹,这就是空间采样中的混叠。
摩尔纹是混叠的一种典型表现。它并不是原始场景中真实存在的大条纹,而是细密纹理与目标像素网格相互干涉后形成的伪影。
4.2 2×2 均值相当于简单低通滤波
当前亮度分量的计算公式为:
1 | Yout(x, y) = ( |
它先把 2×2 区域内的亮度变化平均,再生成一个输出像素。这相当于使用下面的 Box Filter 卷积核:
1 | 1/4 × [ 1 1 ] |
均值会削弱单像素级的明暗跳变和细密纹理,然后再执行 2:1 抽取,因此比直接隔点取样更不容易产生错误条纹。
4.3 对锯齿的改善
斜线穿过像素网格时,每个 2×2 区域内通常同时包含前景与背景。均值计算会生成介于二者之间的亮度值,使边缘产生有限的灰度过渡,而不是只有完全亮或完全暗两个状态。
因此,最终显示出来的斜边会更平滑。这种效果属于缩放阶段的预滤波抗锯齿,不等同于 3D 渲染中的 MSAA,但二者都利用了区域覆盖信息改善边缘质量。
5. YUV420P 内存布局
算法处理的是三平面 YUV420P,缓冲区布局如下:
1 | buf |
各平面的地址计算为:
1 | src_y_size = (uint32_t)src_w * src_h; |
YUV420P 的整帧数据量为:
1 | W × H + W × H / 4 + W × H / 4 |
降采样后,目标平面被紧凑地放到同一个缓冲区前部:
1 | dst_y_size = (uint32_t)out_w * out_h; |
6. 核心实现分析
6.1 函数接口
新增函数为:
1 | static uint8_t scale3_yuv420p_downsample_2x2( |
参数含义:
| 参数 | 说明 |
|---|---|
buf |
YUV420P 源缓冲区,同时作为目标缓冲区 |
src_w、src_h |
Scale3 中间输出宽高 |
dst_w、dst_h |
返回实际降采样后的宽高 |
| 返回值 | 1 表示成功,0 表示输入不满足处理条件 |
6.2 尺寸检查与偶数约束
1 | if (!buf || src_w < 4 || src_h < 4 || |
YUV420P 要求宽高适合 2×2 色度采样,因此输出宽高继续保持偶数。
当源尺寸除以 2 后为奇数时,代码会向下对齐到偶数。例如:
1 | 322×242 -> 160×120 |
这种情况下会舍弃最右侧和最下侧各一个 2 像素块。常用的 800×480、320×240、168×128 均不会触发额外裁边。
6.3 Cache 一致性
Scale3 通过 DMA 向缓冲区写入数据,CPU 随后读取并修改该缓冲区,因此处理前先失效 D-Cache:
1 | sys_dcache_invalid_range((uint32_t *)buf, src_y_size * 3 / 2); |
处理完成后清理目标范围,确保下游 DMA 或其他硬件模块读到 CPU 写入的新数据:
1 | sys_dcache_clean_range((uint32_t *)buf, dst_y_size * 3 / 2); |
这一对操作是硬件缩放输出与 CPU 图像处理之间的重要边界,不能省略。
6.4 Y 平面均值
Y 平面按照两行、四列源数据一组处理,每次生成两个目标像素:
1 | uint32_t row0 = *(uint32_t *)(src_y0 + (x << 1)); |
该实现使用 32 位读取和位运算同时累计两组 2×2 像素,减少逐像素循环、除法和存储次数。除以 4 通过右移 2 位完成。
6.5 U/V 平面处理
当前色度分量没有计算 2×2 均值,而是从每个源色度块中选择左上角样本:
1 | dst_u[dst_row + x] = src_u[src_index]; |
由于 YUV420P 的 U/V 本来就只有 Y 平面四分之一的采样数,Y 再缩小 2 倍后,U/V 也必须继续缩小 2 倍,才能形成合法的目标 YUV420P 布局。
这种实现速度较快,但意味着当前的“均值缩放”主要作用于亮度:
- 明暗细节造成的摩尔纹和锯齿能够得到改善;
- 彩色细纹造成的色度摩尔纹仍可能保留;
- 如果后续需要进一步提升质量,可将 U/V 也改为 2×2 均值。
6.6 为什么可以原地处理
处理顺序是 Y 平面在前、U/V 平面在后,目标图像又比源图更小。目标数据始终写入缓冲区更靠前的位置:
1 | 源布局:[ Y ][ U ][ V ] |
Y 平面从前向后压缩时,写指针不会追上后续仍需读取的源行。完成整个 Y 平面后,再从原始 U/V 区域读取色度数据并写到前部空出的区域,因此不需要第二块整帧缓冲区。
7. 接入 Scale3 工作队列
降采样发生在 scale3_normal_msi_work() 从消息队列取出普通帧之后、调用 msi_output_fb() 之前:
1 | uint16_t output_w = scale3->ow; |
处理完成后同步更新 framebuff 的描述信息:
1 | arg->yuv_arg.y_size = ow * oh; |
这一步非常关键。下游不能继续使用中间分辨率解释已经压缩后的数据,否则会发生以下问题:
- Y/U/V 平面地址计算错误;
- 下游读取超出有效目标帧的数据;
- LCD 显示花屏、错色或图像撕裂;
- 帧长度统计和带宽计算错误。
8. 普通帧与额外拍照帧的区别
工作队列通过 datatag 区分普通连续帧和临时申请的额外帧:
1 | if (fb->datatag != (uint8_t)~0) |
因此,本次算法不会处理 datatag == 0xff 的额外拍照帧。这样可以避免拍照命令请求的特定分辨率被再次减半。
不过,所有通过本模块帧池产生的普通帧都会进入降采样分支。该行为不仅影响 scale3_normal_msi2(),也可能影响调用 scale3_normal_msi() 的连续输出场景。后续若只希望特定 UI 使用该优化,建议增加独立使能标志,而不是仅依赖普通帧/额外帧分类。
9. 当前 UI 配套参数
当前工作区中,Pocket Camera UI 在主界面预创建 Scale3 时使用:
1 | scale3_normal_msi2( |
经过固定 2×2 降采样后,下游得到:
1 | 400×240 YUV420P |
这里的 800×480 已不再代表最终 MSI 输出尺寸,而是“Scale3 中间尺寸”。这属于本次改动带来的接口语义变化。
Pocket Camera 的各页面之后再次调用同名 S_PREVIEW_SCALE3 时,msi_new() 会复用已经创建的 MSI,因此实际尺寸由第一次创建时的 800×480 决定。这种行为依赖组件创建顺序,维护时需要格外注意。
建议后续把两个概念显式区分:
1 | scale_w / scale_h:Scale3 硬件中间输出尺寸 |
10. 性能与内存分析
10.1 CPU 运算量
以 800×480 -> 400×240 为例:
- Y 平面需要生成 96000 个目标像素;
- U 平面需要生成 24000 个目标样本;
- V 平面需要生成 24000 个目标样本;
- 每帧需额外遍历约 144000 个目标采样值。
算法没有乘法和通用除法,亮度平均主要使用加法、掩码与移位,适合算力有限的嵌入式平台。
10.2 PSRAM 占用
scale3_common() 仍按照 ow × oh × 3 / 2 为每个帧池节点分配缓冲区。默认 MAX_COUNT 为 3,因此 800×480 配置下仅像素缓冲区约占:
1 | 800 × 480 × 3 / 2 × 3 |
降采样完成后虽然 fb->len 只有 144000 字节,但底层 buffer 容量仍为 576000 字节。这是原地处理能够正常工作的前提,也是当前方案的内存代价。
10.3 带宽变化
| 位置 | 单帧有效数据量 | 相对中间帧 |
|---|---|---|
| Scale3 DMA 写入 PSRAM | 576000 字节 | 100% |
| CPU 处理后的下游有效帧 | 144000 字节 | 25% |
所以该方案会降低下游 MSI 传递的有效数据量,但不会降低 Scale3 写入中间帧所需的 DMA 带宽,反而增加了一次 CPU 读写与 Cache 维护。
11. 当前实现的边界与风险
11.1 U/V 没有做均值滤波
当前只对 Y 平面执行 2×2 均值。对于彩色网格、彩色织物等场景,色度混叠仍可能形成彩色摩尔纹。若实测仍有明显色彩条纹,应优先评估 U/V 2×2 均值。
11.2 API 尺寸语义发生变化
原先调用者传入 ow、oh,下游就收到相同尺寸;修改后普通帧实际输出约为:
1 | (ow / 2 对齐到偶数) × (oh / 2 对齐到偶数) |
现有调用者如果仍把参数理解为最终分辨率,可能得到尺寸减半的画面。
11.3 优化范围较宽
降采样直接放在公共工作队列的普通帧输出分支中,没有独立配置开关。凡是使用该帧池的普通输出都会被处理。更稳妥的工程化方式是增加例如:
1 | uint8_t mean_downsample_enable : 1; |
并只在明确需要抗混叠的 UI 或产品配置中开启。
11.4 实时性需要实机验证
该算法运行在 workqueue 中,不在 Scale3 ISR 内直接执行,这避免了在中断上下文进行整帧处理。但 800×480 连续预览时,每帧都会增加 CPU 和 PSRAM 访问开销,需要结合实际帧率确认:
- workqueue 是否出现积压;
- Scale3 帧池是否频繁耗尽并打印
D; - LCD 是否掉帧或延迟增加;
- CPU 占用和功耗是否可接受。
11.5 视觉改善需要固定场景对比
代码原理能够解释预期改善,但最终效果与摄像头传感器、ISP 锐化、Scale3 算法、LCD 二次缩放及场景纹理有关。不能只凭实现判断“摩尔纹已经消失”,应通过同机位 A/B 测试确认。
12. 建议验证方案
12.1 测试场景
建议准备以下固定测试目标:
- 黑白等间距细条纹;
- 横向、纵向及 30°/45° 斜线;
- 棋盘格和同心圆测试图;
- 布料、纱窗、键盘和印刷网点;
- 红绿或蓝黄相间的彩色细条纹;
- 镜头缓慢平移时的动态纹理。
12.2 A/B 对照组
| 组别 | 处理链路 |
|---|---|
| A:原始方案 | Scale3 直接输出最终尺寸 |
| B:当前方案 | Scale3 输出 2 倍宽高,再做 Y 平面 2×2 均值 |
| C:完整均值方案 | Scale3 输出 2 倍宽高,Y/U/V 全部做 2×2 均值 |
应确保三组测试使用相同传感器参数、曝光、增益、锐化、LCD 显示区域和拍摄距离。
12.3 功能检查
- 检查
arg->yuv_arg.out_w/out_h是否为实际输出尺寸; - 检查
arg->yuv_arg.y_size == out_w × out_h; - 检查
fb->len == out_w × out_h × 3 / 2; - 检查 Y/U/V 平面地址和颜色是否正常;
- 检查额外拍照帧是否保持请求分辨率;
- 检查进入、退出和反复切换 UI 后 MSI 是否仍正常复用;
- 检查连续运行时是否掉帧、花屏或出现帧池耗尽。
12.4 视觉评价
重点观察:
- 高频区域是否仍出现大尺度明暗条纹;
- 斜线边缘阶梯是否减轻;
- 镜头移动时纹理是否闪烁;
- 画面是否因 Box Filter 变得过于柔和;
- 彩色条纹是否仍产生色度摩尔纹。
13. 后续优化方向
13.1 对 U/V 同样执行 2×2 均值
这是当前实现最直接的质量增强项。色度平面可使用与 Y 相同的计算思想:
1 | Uout = (U00 + U01 + U10 + U11) / 4 |
由于 U/V 总采样数较少,额外计算量小于 Y 平面。
13.2 增加显式开关
把固定行为改为可配置行为:
1 | scale3_normal_msi2(..., mean_downsample_enable) |
或者通过 MSI 命令动态控制,使拍照、录像、预览和不同客户配置可以独立选择。
13.3 明确中间尺寸与最终尺寸
可把 API 改为同时传入最终输出尺寸,内部自动计算中间尺寸:
1 | 最终需要 400×240 |
这样调用者不必了解“传入尺寸会被减半”的隐含规则。
13.4 根据画质与性能选择滤波核
2×2 Box Filter 的优势是实现简单、速度快,但频率响应并不理想。如果平台性能允许,可以比较:
- 2×2 Box Average:当前方案,速度最快;
- 3×3 Gaussian:边缘更自然,计算和边界处理更复杂;
- 双线性缩放:适合任意比例,但抗混叠能力取决于实现;
- 多级缩放:适合更大倍率缩小,避免一次跨越过多源像素。
14. 总结
本次修改在 TXW82x Scale3 普通 YUV420P 输出链路中加入了原地 2×2 降采样。其核心价值不是简单地把宽高减半,而是在抽取像素前对 Y 分量进行区域均值,使高频亮度信息先被平滑,从而在一定程度上降低摩尔纹、斜边锯齿和动态闪烁。
当前方案的典型链路为:
1 | VPP |
它在不增加第二块整帧缓冲区的前提下提供了低成本的缩放预滤波,但目前仍存在 U/V 未均值、API 尺寸语义变化、公共输出路径影响范围较宽等限制。后续应以固定测试图进行 A/B 对比,并结合帧率、CPU 占用与 PSRAM 带宽,决定是否进一步加入色度均值和独立功能开关。