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 输出缓冲区。

具体目标包括:

  1. 在最终降采样前进行简单低通滤波;
  2. 降低亮度细节产生的摩尔纹和混叠;
  3. 平滑斜边与轮廓,减轻阶梯锯齿;
  4. 使用固定 2:1 比例降低算法复杂度;
  5. 在原有 YUV420P 缓冲区内原地处理,避免额外申请整帧内存;
  6. 保持原有 MSI 输出接口和 framebuff 生命周期不变。

需要特别说明:当前实现不会降低 Scale3 帧池最初申请的 PSRAM 容量,因为缓冲区仍按照中间分辨率创建。它降低的是送往下游的有效图像尺寸和 fb->len


3. 修改前后的数据链路

3.1 修改前

1
2
3
4
5
6
7
8
9
10
Camera / VPP
|
v
Scale3 硬件缩放
| 直接生成最终尺寸 YUV420P
v
framebuff
|
v
MSI 输出 -> R_VIDEO_P0 / R_VIDEO_P1 -> LCD

如果 Scale3 直接把高分辨率图像缩到较小尺寸,硬件缩放过程中对高频纹理的预滤波不足,就可能产生混叠。

3.2 修改后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Camera / VPP
|
v
Scale3 硬件缩放
| 生成较高分辨率中间帧,例如 800×480
v
YUV420P 中间缓冲区
|
| Y:2×2 Box Average
| U/V:固定位置抽样
v
原地 2:1 降采样
| 输出 400×240
v
更新 framebuff 元数据
|
v
MSI 输出 -> R_VIDEO_P0 / R_VIDEO_P1 -> LCD

以当前配套配置为例:

阶段 分辨率 YUV420P 有效数据量
Scale3 中间输出 800×480 576000 字节
软件降采样输出 400×240 144000 字节

宽、高各缩小一半,因此最终像素数量和有效数据量均变为原来的四分之一。


4. 为什么均值降采样能改善摩尔纹和锯齿

4.1 问题本质:缩小过程中的混叠

缩小图像意味着目标图像的一个像素要代表源图中的一片区域。如果直接选择区域内的某一个像素,源图中比目标分辨率更细的纹理会被错误地映射成较低频率的条纹,这就是空间采样中的混叠。

摩尔纹是混叠的一种典型表现。它并不是原始场景中真实存在的大条纹,而是细密纹理与目标像素网格相互干涉后形成的伪影。

4.2 2×2 均值相当于简单低通滤波

当前亮度分量的计算公式为:

1
2
3
4
5
6
Yout(x, y) = (
Yin(2x, 2y) +
Yin(2x + 1, 2y) +
Yin(2x, 2y + 1) +
Yin(2x + 1, 2y + 1)
) / 4

它先把 2×2 区域内的亮度变化平均,再生成一个输出像素。这相当于使用下面的 Box Filter 卷积核:

1
2
1/4 × [ 1  1 ]
[ 1 1 ]

均值会削弱单像素级的明暗跳变和细密纹理,然后再执行 2:1 抽取,因此比直接隔点取样更不容易产生错误条纹。

4.3 对锯齿的改善

斜线穿过像素网格时,每个 2×2 区域内通常同时包含前景与背景。均值计算会生成介于二者之间的亮度值,使边缘产生有限的灰度过渡,而不是只有完全亮或完全暗两个状态。

因此,最终显示出来的斜边会更平滑。这种效果属于缩放阶段的预滤波抗锯齿,不等同于 3D 渲染中的 MSAA,但二者都利用了区域覆盖信息改善边缘质量。


5. YUV420P 内存布局

算法处理的是三平面 YUV420P,缓冲区布局如下:

1
2
3
4
5
6
7
buf
|
+-- Y 平面:W × H
|
+-- U 平面:(W / 2) × (H / 2)
|
+-- V 平面:(W / 2) × (H / 2)

各平面的地址计算为:

1
2
3
src_y_size = (uint32_t)src_w * src_h;
src_u = buf + src_y_size;
src_v = src_u + src_y_size / 4;

YUV420P 的整帧数据量为:

1
2
W × H + W × H / 4 + W × H / 4
= W × H × 3 / 2

降采样后,目标平面被紧凑地放到同一个缓冲区前部:

1
2
3
dst_y_size = (uint32_t)out_w * out_h;
dst_u = buf + dst_y_size;
dst_v = dst_u + dst_y_size / 4;

6. 核心实现分析

6.1 函数接口

新增函数为:

1
2
3
4
5
6
static uint8_t scale3_yuv420p_downsample_2x2(
uint8_t *buf,
uint16_t src_w,
uint16_t src_h,
uint16_t *dst_w,
uint16_t *dst_h);

参数含义:

参数 说明
buf YUV420P 源缓冲区,同时作为目标缓冲区
src_wsrc_h Scale3 中间输出宽高
dst_wdst_h 返回实际降采样后的宽高
返回值 1 表示成功,0 表示输入不满足处理条件

6.2 尺寸检查与偶数约束

1
2
3
4
5
6
7
8
if (!buf || src_w < 4 || src_h < 4 ||
(src_w & 1) || (src_h & 1))
{
return 0;
}

out_w = (src_w >> 1) & ~1U;
out_h = (src_h >> 1) & ~1U;

YUV420P 要求宽高适合 2×2 色度采样,因此输出宽高继续保持偶数。

当源尺寸除以 2 后为奇数时,代码会向下对齐到偶数。例如:

1
322×242 -> 160×120

这种情况下会舍弃最右侧和最下侧各一个 2 像素块。常用的 800×480320×240168×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
2
3
4
5
6
7
8
9
10
11
uint32_t row0 = *(uint32_t *)(src_y0 + (x << 1));
uint32_t row1 = *(uint32_t *)(src_y1 + (x << 1));

sum = (row0 & 0x00ff00ff) +
((row0 >> 8) & 0x00ff00ff) +
(row1 & 0x00ff00ff) +
((row1 >> 8) & 0x00ff00ff);

*(uint16_t *)(out_y + x) =
(uint16_t)(((sum >> 2) & 0xff) |
((sum >> 10) & 0xff00));

该实现使用 32 位读取和位运算同时累计两组 2×2 像素,减少逐像素循环、除法和存储次数。除以 4 通过右移 2 位完成。

6.5 U/V 平面处理

当前色度分量没有计算 2×2 均值,而是从每个源色度块中选择左上角样本:

1
2
dst_u[dst_row + x] = src_u[src_index];
dst_v[dst_row + x] = src_v[src_index];

由于 YUV420P 的 U/V 本来就只有 Y 平面四分之一的采样数,Y 再缩小 2 倍后,U/V 也必须继续缩小 2 倍,才能形成合法的目标 YUV420P 布局。

这种实现速度较快,但意味着当前的“均值缩放”主要作用于亮度:

  • 明暗细节造成的摩尔纹和锯齿能够得到改善;
  • 彩色细纹造成的色度摩尔纹仍可能保留;
  • 如果后续需要进一步提升质量,可将 U/V 也改为 2×2 均值。

6.6 为什么可以原地处理

处理顺序是 Y 平面在前、U/V 平面在后,目标图像又比源图更小。目标数据始终写入缓冲区更靠前的位置:

1
2
源布局:[           Y           ][    U    ][    V    ]
目标布局:[ Y ][ U ][ V ]

Y 平面从前向后压缩时,写指针不会追上后续仍需读取的源行。完成整个 Y 平面后,再从原始 U/V 区域读取色度数据并写到前部空出的区域,因此不需要第二块整帧缓冲区。


7. 接入 Scale3 工作队列

降采样发生在 scale3_normal_msi_work() 从消息队列取出普通帧之后、调用 msi_output_fb() 之前:

1
2
3
4
5
6
7
8
9
10
11
12
uint16_t output_w = scale3->ow;
uint16_t output_h = scale3->oh;

scale3_yuv420p_downsample_2x2(
fb->data,
scale3->ow,
scale3->oh,
&output_w,
&output_h);

ow = output_w;
oh = output_h;

处理完成后同步更新 framebuff 的描述信息:

1
2
3
4
arg->yuv_arg.y_size = ow * oh;
arg->yuv_arg.out_w = ow;
arg->yuv_arg.out_h = oh;
fb->len = ow * oh * 3 / 2;

这一步非常关键。下游不能继续使用中间分辨率解释已经压缩后的数据,否则会发生以下问题:

  • Y/U/V 平面地址计算错误;
  • 下游读取超出有效目标帧的数据;
  • LCD 显示花屏、错色或图像撕裂;
  • 帧长度统计和带宽计算错误。

8. 普通帧与额外拍照帧的区别

工作队列通过 datatag 区分普通连续帧和临时申请的额外帧:

1
2
3
4
5
6
7
8
if (fb->datatag != (uint8_t)~0)
{
// 普通帧:执行 2×2 降采样
}
else
{
// 额外拍照/缩略图帧:保持请求尺寸,直接输出
}

因此,本次算法不会处理 datatag == 0xff 的额外拍照帧。这样可以避免拍照命令请求的特定分辨率被再次减半。

不过,所有通过本模块帧池产生的普通帧都会进入降采样分支。该行为不仅影响 scale3_normal_msi2(),也可能影响调用 scale3_normal_msi() 的连续输出场景。后续若只希望特定 UI 使用该优化,建议增加独立使能标志,而不是仅依赖普通帧/额外帧分类。


9. 当前 UI 配套参数

当前工作区中,Pocket Camera UI 在主界面预创建 Scale3 时使用:

1
2
3
4
5
scale3_normal_msi2(
S_PREVIEW_SCALE3,
FSTYPE_YUV_P0,
800,
480);

经过固定 2×2 降采样后,下游得到:

1
400×240 YUV420P

这里的 800×480 已不再代表最终 MSI 输出尺寸,而是“Scale3 中间尺寸”。这属于本次改动带来的接口语义变化。

Pocket Camera 的各页面之后再次调用同名 S_PREVIEW_SCALE3 时,msi_new() 会复用已经创建的 MSI,因此实际尺寸由第一次创建时的 800×480 决定。这种行为依赖组件创建顺序,维护时需要格外注意。

建议后续把两个概念显式区分:

1
2
scale_w / scale_h:Scale3 硬件中间输出尺寸
out_w / out_h:均值降采样后的最终输出尺寸

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
2
800 × 480 × 3 / 2 × 3
= 1,728,000 字节

降采样完成后虽然 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 尺寸语义发生变化

原先调用者传入 owoh,下游就收到相同尺寸;修改后普通帧实际输出约为:

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 测试场景

建议准备以下固定测试目标:

  1. 黑白等间距细条纹;
  2. 横向、纵向及 30°/45° 斜线;
  3. 棋盘格和同心圆测试图;
  4. 布料、纱窗、键盘和印刷网点;
  5. 红绿或蓝黄相间的彩色细条纹;
  6. 镜头缓慢平移时的动态纹理。

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
2
Uout = (U00 + U01 + U10 + U11) / 4
Vout = (V00 + V01 + V10 + V11) / 4

由于 U/V 总采样数较少,额外计算量小于 Y 平面。

13.2 增加显式开关

把固定行为改为可配置行为:

1
scale3_normal_msi2(..., mean_downsample_enable)

或者通过 MSI 命令动态控制,使拍照、录像、预览和不同客户配置可以独立选择。

13.3 明确中间尺寸与最终尺寸

可把 API 改为同时传入最终输出尺寸,内部自动计算中间尺寸:

1
2
3
最终需要 400×240
-> Scale3 自动配置为 800×480
-> 软件输出固定为 400×240

这样调用者不必了解“传入尺寸会被减半”的隐含规则。

13.4 根据画质与性能选择滤波核

2×2 Box Filter 的优势是实现简单、速度快,但频率响应并不理想。如果平台性能允许,可以比较:

  • 2×2 Box Average:当前方案,速度最快;
  • 3×3 Gaussian:边缘更自然,计算和边界处理更复杂;
  • 双线性缩放:适合任意比例,但抗混叠能力取决于实现;
  • 多级缩放:适合更大倍率缩小,避免一次跨越过多源像素。

14. 总结

本次修改在 TXW82x Scale3 普通 YUV420P 输出链路中加入了原地 2×2 降采样。其核心价值不是简单地把宽高减半,而是在抽取像素前对 Y 分量进行区域均值,使高频亮度信息先被平滑,从而在一定程度上降低摩尔纹、斜边锯齿和动态闪烁。

当前方案的典型链路为:

1
2
3
4
5
6
7
VPP
-> Scale3 硬件生成 800×480 YUV420P
-> CPU 对 Y 分量执行 2×2 Box Average
-> U/V 分量固定位置抽样
-> 原地压缩为 400×240 YUV420P
-> 更新 out_w、out_h、y_size 和 fb->len
-> MSI 输出到 LCD 链路

它在不增加第二块整帧缓冲区的前提下提供了低成本的缩放预滤波,但目前仍存在 U/V 未均值、API 尺寸语义变化、公共输出路径影响范围较宽等限制。后续应以固定测试图进行 A/B 对比,并结合帧率、CPU 占用与 PSRAM 带宽,决定是否进一步加入色度均值和独立功能开关。