title: AINR 黑光全彩实测教程
aliases:
- 黑光全彩怎么测
- sample_ainr 暗光实测
- AI-ISP 低照全彩验证
- GK7206 AINR 调优
tags:
- 嵌入式
- GK7206
- AINR
- AI-ISP
- NPU
- 图像质量
created: 2026-10-25
updated: 2026-11-04
AINR 黑光全彩实测教程
[!abstract] 一句话 黑光全彩不是"AINR 打开就完事",而是 进光量(光圈)+ 曝光时间(1/fps)+ 高增益 + AINR 对 RAW 降噪 四件事同时成立。这套 SDK 里 AINR 在
sdk/sample/ainr,单 sensor 跑./sample_ainr 0,强度写死成MANUAL + pos_gain=64 + gau_gain=64(不随 ISO 变化);但"跑不跑"另有一道亮度门控——室内灯光下 NPU 中断恒 0,关灯遮镜头才 53 次/秒(6.4 实测)。真正卡住画质的是缺sc465sl_linear_isp_vpss_ainr.bin这份 TNR/AE/CCM 调优参数,而不是 AI 通路通不通。
一、结论先行
问题 | 结论 | 依据 |
|---|---|---|
AINR 在哪个位置起作用 | BNR 后置,不是前置;必须先开 TNR |
|
AINR 是常开还是随光照切换 | 不是常开:亮度不够时被 ISP 旁路;强度则恒定,不随 ISO 变 | 强度写死在 |
| 管不到 | 用例全程 |
怎么确认 AINR 真在跑 | 暗光下看 NPU 中断,满速约 53 次/秒(15 fps、约 3.5 次/帧);亮光下恒 0 属正常 | 6.4 三组对照实测 |
那 | 最直接但时机敏感:单点读 0 不算证据,必须暗光下两点采样(启动瞬间 + 退出前) |
|
提高夜间亮度的最有效手段 | 降 fps 换曝光时间 |
|
为什么默认 15fps | 用例写死 |
|
出图彩色但不"黑光" | 缺 PQ bin,跑的是驱动里的编译期默认参数 |
|
想按 ISO 分别调降噪强度 | 只能走 PQ bin / PQ 工具 | 运行时 |
FPN / BPC 能不能一起开 | 不能 |
|
拿不到当前 ISO / 增益 | 公开头文件没有 AE 结果查询接口 |
|
mmz 够不够 | 够,AINR 稳态约 22.5 MiB,80 MB 池跑完剩 32 MB | 见第十节 VB 尺寸表(数值来自代码 + 实测日志) |
二、黑光全彩的边界:先算清物理账
2.1 它到底是什么
不靠红外灯、不靠白光补光,在肉眼接近全黑的场景(照度 0.1 lux 以下)输出可辨识颜色的图像。做到这一点只有三条路叠加:
进光量:大光圈镜头。f/1.0 相对 f/1.6 是 (1.6/1.0)^2 = 2.56 倍光通量,约 +8.2 dB。
积分时间:降帧率换长曝光。30fps → 15fps 是 2 倍,+6 dB;降到 5fps 是 6 倍,+15.6 dB。
降噪:长曝光和高增益必然带出散粒噪声 + 读出噪声,AINR 就是把这部分噪声从 RAW 域压下去,让 ISP 后级(CCM/饱和度/锐化)不至于把噪声放大成彩色噪点。
前两条是光学/时序,AINR 只能救第三条。这也是为什么很多项目"AINR 打开了还是不黑光"——光圈 f/2.0 加 33 ms 曝光,堆多少 AI 都补不回来。
2.2 与常见夜视方案的区别
方案 | 光源 | 图像 | 适用 | 代价 |
|---|---|---|---|---|
红外夜视 | 850/940 nm 红外灯 | 黑白 | 全黑、要看得见就行 | 无颜色、红外灯功耗与红曝 |
白光补光全彩 | 白光 LED | 彩色 | 门口、庭院 | 光污染、招虫、暴露位置 |
黑光全彩 | 无 | 彩色 | 微光环境、隐蔽安装 | 依赖大光圈 + 长曝光,怕运动 |
双 sensor 融合(彩色 + 黑白) | 无 | 彩色 | 高端机 | 成本、体积、同步 |
2.3 运动拖影是硬天花板
长曝光的代价直接用几何算:
视野宽 4 m、水平 2560 px → 每像素 1.56 mm
目标横向速度 1 m/s
曝光 66.6 ms(15fps)→ 位移 66.6 mm → 约 43 px 拖影
曝光 100 ms(10fps)→ 位移 100 mm → 约 64 px 拖影
结论:黑光全彩的场景假设是"慢速或静止"。要拍快车/奔跑,只能提高照度或接受拖影,降帧率这条路的收益和代价是线性对冲的。写进验收标准里,别等现场被投诉。
三、数据通路:AINR 卡在哪一环
比普通 sample_venc 多出来的 5 处,全在 sdk/sample/ainr/sample_ainr.c:
# | 位置 | 作用 | 不开会怎样 |
|---|---|---|---|
1 |
| VB 池带 | VI 不会把 RAW 转给 NPU,AI 通路不存在 |
2 |
|
| ISP 不知道要接 AI 结果 |
3 |
| 申请 NPU workspace(必须在 |
|
4 |
|
| 图像退回普通 ISP |
5 |
| 导入 | 画质是驱动默认值(黑光全彩效果就在这里丢的) |
对黑光全彩最关键的是第 4、5 项。第 4 项决定"有没有 AI",第 5 项决定"AI 之后那些参数配不配"。
四、曝光能力:这套硬件到底能压到多暗
4.1 曝光上限由帧率决定,且行时间恒定
sc465sl 线性 4M 4lane 的 VMAX 是 0xa6a = 2666 行(sc465sl.h:79),AE 默认值在 sc465sl.c:863-895:
ae_sns_dft->max_int_time = sns_ctx->fl_std - EXP_OFFSET_LINEAR; // 8 行余量
ae_sns_dft->max_again = 109993; // 107x,1024 = 1x
ae_sns_dft->max_dgain = 16256; // 15.875x,1024 = 1x
ae_sns_dft->max_ispdgain_target = 32 << 8; // ISP 数字增益上限 32x帧率一变,sc465sl.c:1354 重算 full_lines = vmax * 30 / fps,所以行时间恒为 12.50 us(1/(2666*30)),只有总行数在变:
ISP fps | full_lines | 行时间 | 最大曝光 | 相对 30fps |
|---|---|---|---|---|
30 | 2666 | 12.50 us | 33.2 ms | 0 dB |
15(用例默认) | 5332 | 12.50 us | 66.5 ms | +6.0 dB |
10 | 7998 | 12.50 us | 99.9 ms | +9.6 dB |
5 | 15996 | 12.50 us | 199.9 ms | +15.6 dB |
sc465sl_get_min_fps() 给的线性模式下限是 0.1 fps(sc465sl.c:1299-1302),所以理论曝光天花板是 10 s 级;实际到 5~10 fps 就被人眼可察的拖影否掉了。
4.2 ISO 档位决定"抹多狠"
sc465sl_ex.h 里 BNR/TNR 的自动表按 12 档 ISO(100…204800) 组织,不是按亮度。以 tnr_mot_noise_scl(:126)为例:ISO 100~3200 恒定 256,ISO 6400 起 320,ISO 12800 起 384;tnr_dp_luma_thd_low(:132)从 128 一路抬到 384,是 3 倍。本质是"越暗越允许抹"。这解释了为什么同一套参数白天看着"糊"、夜里正好。
五、前置条件与运行
5.1 环境
项 | 值 | 来源 |
|---|---|---|
板子 | GK7206( |
|
Sensor |
|
|
模型 |
|
|
mmz |
|
|
编译 | 容器内,SDK 在 | 见《SDK编译产物与运行指南》 |
SD 卡 |
| rootfs 仅 10.9 MB,二进制必须放卡上 |
容器内先设变量,后面命令直接粘:
export BOARD=192.168.31.55
SSH_OPT="-o ControlMaster=auto -o ControlPath=/tmp/b-%r@%h:%p -o ControlPersist=600"5.2 编译(改动过哪层就编哪层)
cd /opt/sdk
make -C sample/common clean
make -C sample/live_rtsp_server clean
make -C sample/ainr clean
# 改了 sample/common/sample_comm_isp.c(AINR 参数就在这)必须先编 common
make -C sample/common -j"$(nproc)"
make -C sample/live_rtsp_server -j"$(nproc)"
make -C sample/ainr -j"$(nproc)"
ls -l sample/ainr/sample_ainr # 参考 4105908 字节,静态链接 xmedia 全套库[!warning] 只改 sample_ainr.c 时 不用重编 common。但只要动过 AINR_PQ_BIN_DIR / AINR_MODEL_DIR 宏或 sample_comm_isp_ainr_init(),必须连 common 一起编,否则静态库里还是旧的。
5.3 部署(路径由代码里的相对路径决定)
#define AINR_PQ_BIN_DIR "../scene_auto"
#define AINR_MODEL_DIR "../../tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/configs"两条都相对进程 cwd,所以板端必须是这个形状,且运行前 cd 进 sample/ainr:
/mnt/sd/ainr/
├── sample/
│ ├── ainr/sample_ainr <- cwd,在这里执行
│ └── scene_auto/sc465sl/sc465sl_linear_isp_vpss_ainr.bin <- 缺,见 8.6
└── tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/configs/
└── sc465sl/neuron_network.xmm <- 必须cd /opt/sdk
ssh $SSH_OPT root@$BOARD 'mkdir -p /mnt/sd && mount /dev/mmcblk1p1 /mnt/sd && df -h /mnt/sd'
ssh $SSH_OPT root@$BOARD 'mkdir -p /mnt/sd/ainr/sample/ainr \
/mnt/sd/ainr/sample/scene_auto/sc465sl \
/mnt/sd/ainr/tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/configs/sc465sl'
scp $SSH_OPT sample/ainr/sample_ainr root@$BOARD:/mnt/sd/ainr/sample/ainr/
scp $SSH_OPT tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/configs/sc465sl/neuron_network.xmm \
root@$BOARD:/mnt/sd/ainr/tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/configs/sc465sl/# 校验:两端 md5 必须一致
md5sum sample/ainr/sample_ainr tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/configs/sc465sl/neuron_network.xmm
ssh $SSH_OPT root@$BOARD 'md5sum /mnt/sd/ainr/sample/ainr/sample_ainr \
/mnt/sd/ainr/tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/configs/sc465sl/neuron_network.xmm'模型参考值:1706790 字节,md5 d144ea96871832f424cf4c251fb8923f(本机实测)。
[!warning] 容器内 scp 不能加 -O 容器是 OpenSSH 7.6p1,-O 是 9.0 才有的参数,写了直接 unknown option -- O。宿主机新版 scp 才需要 -O(板端 dropbear 不支持 SFTP)。
5.4 运行
ssh $SSH_OPT -t root@$BOARD 'killall factory_server 2>/dev/null; \
cd /mnt/sd/ainr/sample/ainr && pwd && ./sample_ainr 0'三条硬要求:
必须
cd进去:模型和 bin 都是相对 cwd 的,路径错时不会报"文件不存在",而是mmz_mmb_alloc(AIworkspace, 0, ...) failed(size=0 说明 querysize 没拿到,不是内存不够)。先
killall factory_server:VI 独占。必须
-t:否则 stdout 全缓冲,终端一片空白。
预期关键行:
[sample_ainr_vb_pool_init]-161: PIPE 0 : vi-npu 2VB, size : 5529600
[sample_ainr_vb_pool_init]-169: PIPE 0 : npu output 1VB, size : 6912000
[sample_comm_isp_get_sensor_effect_index]-1157: sensor type 71 with id : 3
[SENSOR][Func]:sc465sl_calc_fps [Line]:1358 [Info]:sc465sl set fps = 15.000000
[ISP]isp_init[1113]: isp[0] init success
[ISP]isp_run[771]: isp run.
[sample_comm_isp_ainr_init]-1329: ainr model : ../../tools/linux/pq_board/.../sc465sl/neuron_network.xmm
xmm model version : 6e08acaf9 Jun 19 2025 model type : high
[ISP]isp_start[503]: isp0 start.
rtsp://192.168.31.55:554/livestream/0
rtsp://192.168.31.55:554/livestream/1
[sample_comm_pq_bin_import]-292: No such file.
[sample_ainr_process_run]-501: isp load pq bin failed!
[sample_comm_isp_enable_fpn]-833: FPN.bin not exist ./FPN.bin.
[sample_ainr_process_run]-507: isp enable fpn failed!
---------------press any key to exit!---------------pq bin failed 和 fpn failed 是预期内的非致命打印(前者缺 bin,后者与 AINR 互斥)。真正判断模型加载成功的是 xmm model version 那行。
六、先证明确实在跑 AI,再谈画质
出图 ≠ AINR 生效。6.1、6.2 只证明"具备条件",6.4 才证明"真在跑",主判据是 6.4;6.3 是可选的定位手段,不靠它下日常结论。6.4 必须在暗光下做,亮光下永远过不了,别在那里卡半天。
6.1 模型加载成功
日志里有 xmm model version : ... 且没有 isp load model failed。这一行由 ISP 内部解析模型头打印,模型文件损坏会直接 check_model_header assert。
[!note] 日志里模型路径少几个字符不是错 arm-gcc1ux-uclibceabi、arm-glinux-uclibceabi 这种是串口传输或复制时掉字符,真路径是 .../pq_board/arm-gcc12.2.0-linux-uclibceabi/configs/...(编译期宏,sample_comm_isp.c:58)。路径真错的表现是 cannot open 或 check_model_header assert,不会只是打印缺字。
6.2 workspace 已分配
ssh $SSH_OPT root@$BOARD 'cat /proc/umap/media-mem | grep -E "AIworkspace|total size"'|-MMB: phys(0x447E0000, 0x44B4FFFF), kvirt=0x00000000, flags=0x00000000, length=3520KB, name="AIworkspace"
total size=81920KB(80MB),used=48888KB(47MB + 760KB),free=33032KB(32MB + 264KB)[!note] /proc/media-mem 不存在是正常的 proc 根目录 umap 是 osal_proc_mkdir("umap", ...) 建的(sdk/source/gmp/drv/osal/linux/kernel/osal_proc.c:302),ISP/MMZ 节点都挂在这个子目录下,正确路径是 /proc/umap/media-mem。
[!warning] 这块内存不能用来判断用例还在不在跑 AIworkspace 在 sample_ainr.c:277 就分配好了,早于任何一次推理;进程退出时如果没走到 sample_comm_sys_npu_release_workspace()(:562),块会留在 proc 里。实测遇到过"已经退回 shell、proc 里还有 3520 KB 的 AIworkspace"。 判断进程活没活只认 ps | grep -c "[s]ample_ainr",别拿 /proc/umap/media-mem 当存活信号。
6.3 ainr_state:最直接,但必须两点采样
先明确它的地位:它读的是 ISP 内部状态(xmedia_isp.h:4725:TRUE = "AINR已激活"),证据等级高于日志和 ps。但它是个瞬时布尔量,只在采样点成立。
sample_ainr_process_run() 建完链就返回、不阻塞(sample_ainr.c:464-529),插在它后面读到的是启动瞬间——AE 还没收敛、门控还没判断。实测室内灯光下就是 ainr_state = 0,这个 0 既可能是"门控没让跑",也可能只是"还没轮到跑",区分不了两者,不能当作 AINR 没跑的结论。
只有"暗光下、跑了几十秒、拆链之前"读到 0 才算证据。ISP 是进程内资源,另起进程拿不到状态,必须在用例里打:
/* 读取 ISP 内部 AINR 状态:TRUE 表示 ISP 真的把 RAW 送去 NPU 并取回结果。
* 比 NPU 中断计数更直接,但它是瞬时量,单点读 0 不代表没在跑。 */
static xmedia_void sample_ainr_dump_state(sample_ainr_param *ai_param)
{
xmedia_s32 ret;
xmedia_bool ainr_state = XMEDIA_FALSE;
ret = xmedia_isp_get_ainr_state(ai_param->vi_pipe[0], &ainr_state);
if (ret != XMEDIA_SUCCESS) {
SAMPLE_ERR("get ainr state failed, ret = %d\n", ret);
return;
}
SAMPLE_PRT("ainr_state = %d (pipe %d)\n", ainr_state, ai_param->vi_pipe[0]);
}两个调用点,一次运行拿两个数据点。第一个是 t≈0 基线(sample_ainr.c:679 之后):
ret = sample_ainr_process_run(&ai_param);
if (ret != XMEDIA_SUCCESS) {
sample_ainr_isp_unprepare(&ai_param);
sample_ainr_sys_unprepare(&ai_param);
return ret;
}
sample_ainr_dump_state(&ai_param); /* t≈0 基线:亮光下必为 0,不作判据 */
sample_ainr_process_exit(&ai_param);第二个是稳态,放在 sample_ainr_process_exit() 里 PAUSE() 之后、拆链之前——此时已经跑完整场,ISP 还活着:
static xmedia_void sample_ainr_process_exit(sample_ainr_param *ai_param)
{
xmedia_u32 i;
PAUSE(g_ainr_force_exit);
sample_ainr_dump_state(ai_param); /* 稳态:已跑整场,拆链之前 */
sample_comm_venc_stop_get_stream();判读(必须暗光,否则两行都是 0):
t≈0 | 退出前 | 结论 |
|---|---|---|
0 | 1 | 正常:激活要等 AE 收敛、增益到位,和 6.4 的中断曲线吻合 |
0 | 0 | AINR 确实没激活,与 6.4 的中断对照着看 |
0 | 0(亮光下) | 正常,门控旁路 |
FALSE 的排查顺序:pipe_info.ainr_en 是否为 TRUE(sample_ainr.c:609)→ VB 池有没有带 ainr_en(:156)→ sample_comm_isp_ainr_init() 是否返回成功。
6.4 NPU 中断在涨(必须在暗光下测)
用例在前台跑(一路 SSH 或串口),另开一路采样:
# 前提:进程活着,返回 1 才算数
ssh $SSH_OPT root@$BOARD 'ps | grep -c "[s]ample_ainr"'
# 暗光下采样,两次之间隔 5 s
ssh $SSH_OPT root@$BOARD 'grep -i npu /proc/interrupts; sleep 5; grep -i npu /proc/interrupts' 60: 431 0 GIC-0 78 Level 11340000.npu
60: 697 0 GIC-0 78 Level 11340000.npu11340000 就是 dts 里 npu@11340000 的寄存器基址,这条中断确实是 NPU 的(各芯片 interrupts 编号不同,本板是 78,别拿别的板子的编号套)。
关键前提:AINR 有亮度门控,亮光下 NPU 一次都不跑。 同一个进程、同一份用例,三组条件实测:
条件 | 5 s 中断增量 | 折算速率 | 说明 |
|---|---|---|---|
室内开灯 | 0 | 0 | 增益远低于门控阈值,AINR 被旁路 |
手遮镜头但灯亮 | +12 | ≈2.4/s | 有漏光,AE 增益在阈值边缘反复穿越,断续触发 |
关灯 + 手遮镜头 | +266 | ≈53/s | 每帧都推理,15 fps × 约 3.5 次中断/帧 |
[!warning] 亮光下 0 次不能判死 第一次实测就是在室内灯光下做的,连续三次采样全是 0,差点误判成"AINR 没激活"。判据只有一条:关灯之后有没有涨。不关灯这条永远过不了。
判读表:
| 暗光下中断 | 结论 |
|---|---|---|
1 | 明显增长(约 50 次/秒) | AINR 在跑,问题只在画质,直接进第七节 |
1 | 恒为 0 | AINR 未激活,回 6.3 做两点采样定位 |
0 | 恒为 0 | 采样无效,用例已退出,重来 |
基线:满速约 53 次/秒(60 秒约 3200 次)。以后同一场景下掉到 15 次/秒左右,说明每帧只有一次中断,管线可能退化,值得复查。
[!note] 门控在阈值附近会抖,这是验收项 第二行那 2.4 次/秒不是"降频推理",更可能是 AE 增益在门控阈值上下反复穿越造成的断续开关(推测,验证方法见下)。真实现场对应黄昏、凌晨这种照度缓变时段,画面上会看到"降噪一会儿强、一会儿弱"的跳变,比稳定的噪声更难看。 验证:把照度推到"刚好开始变暗"的区间,每 5 s 采一次,增量在 0 和 260 之间来回跳即成立。 门控在预编译 ISP 内,用户态改不到(switch_thd_low/high 在 xmedia_isp.h:2394-2395 只有字段、无注释,用例又走 MANUAL),只能记进验收标准,或问 FAE 要阈值接口。
[!tip] 主判据是 6.4,6.3 用来定性 6.4 可远程、可重复、能算出速率,作为日常主判据;6.3 要改代码重编、单点时机敏感,只在"暗光下中断也不涨"时用来定位卡在哪一环,或做回归。 别用单次 ainr_state == 0 给 AINR 判死,包括刚启动那一下——process_run 返回时 ISP 才刚 start。
七、黑光全彩实测方法
7.1 场景与照度标定
项 | 要求 | 说明 |
|---|---|---|
暗室 | 可完全关灯,无窗光 | 门缝漏光会直接毁掉 0.01 lux 档 |
照度计 | 量程覆盖 0.001~10 lux | 手机 App 在 0.1 lux 以下基本是编的,用带低照档的专业表 |
测点 | 贴镜头前平面、朝被摄方向 | 不要测墙面,墙面反射率差异会让不同人测出 3 倍差 |
光源 | 可调光白光,色温 4000~5000 K | 黑光全彩通常假设宽谱环境光 |
目标 | 含肤色、红/绿/蓝布料、灰阶卡 | 只放一个白墙等于只测亮度,测不出色彩还原 |
镜头 | IR-CUT 保留(不拆滤光片) | 黑光全彩不用红外灯,去掉 IR 滤片会让颜色整体偏 |
三个照度档,每档等 AE 稳定 15 s 以上再采:
档位 | 照度 | 用途 |
|---|---|---|
亮场基线 | 50 lux | 确认色彩/AE 正常,AINR 不应拖累画质 |
中暗 | 0.1 lux | 多数"黑光全彩"宣称档,主战场 |
极暗 | 0.01 lux | 找极限,看是噪声还是彻底糊 |
[!warning] 录流前先确认这一档 AINR 到底跑没跑 三档里只有中暗和极暗会让 AINR 真正介入;亮场 50 lux 那档会被亮度门控旁路(6.4 第一组数据:中断恒 0),它只能当"不拖累画质"的对照。 每档等 AE 稳定后,先按 6.4 采一次 5 s 中断并记录增量,再开 ffmpeg 录流。否则很可能录了 30 s 的普通 ISP 输出却当成"AINR 效果"去分析——这是这套 SDK 上最容易产生的一类假数据。
7.2 对照组:同一个程序里关 AINR
唯一严谨的 A/B 是除 AINR 外全部条件不变。加环境变量开关,不引入第二份代码分支:
/* AINR_OFF=1 时关闭 AINR,用于同场景 A/B 对照;其余情况保持用例默认(enable=TRUE,暗光下由门控决定是否介入) */
static xmedia_void sample_ainr_ab_switch(sample_ainr_param *ai_param)
{
const xmedia_char *env_value = XMEDIA_NULL;
xmedia_isp_ainr_attr ainr_attr = {0};
xmedia_s32 ret;
env_value = getenv("AINR_OFF");
if (env_value != XMEDIA_NULL && env_value[0] == '1') {
ret = xmedia_isp_get_ainr_attr(ai_param->vi_pipe[0], &ainr_attr);
if (ret != XMEDIA_SUCCESS) {
SAMPLE_ERR("get ainr attr failed, ret = %d\n", ret);
return;
}
ainr_attr.enable = XMEDIA_FALSE;
ret = xmedia_isp_set_ainr_attr(ai_param->vi_pipe[0], &ainr_attr);
if (ret != XMEDIA_SUCCESS) {
SAMPLE_ERR("set ainr attr failed, ret = %d\n", ret);
return;
}
SAMPLE_PRT("AB group: AINR forced OFF\n");
}
sample_ainr_dump_state(ai_param); /* 打开状态一起打出来,便于对表 */
return;
}调用点用 6.3 的第一个(sample_ainr_process_run() 之后),把 sample_ainr_dump_state 换成 sample_ainr_ab_switch。必须在这一刻关:process_run 之后马上开始录流,放到退出前再关等于录了一段 AINR 在跑的片子。
[!warning] 关 AINR 后必须确认还有正常图像 VI 的 ainr_en=TRUE 意味着 RAW 走的是 vi-npu → npu output 这条 buffer 链(sample_ainr.c:156-169)。关掉 AINR 后 ISP 是否仍从 npu output 取数、还是画面变绿/卡死,取决于 ISP 内部实现,本文未逐帧验证。 旁证:6.4 第一组实测里,亮光下 AINR 被门控旁路(NPU 中断恒 0),用例仍正常建链并打印两路 RTSP 地址——说明"AINR 未介入"本身不会打断出图。跑一次看:出图正常 → A/B 有效;不出图 → 改用"两份二进制对照"(sample_venc 作无 AI 基线),此时要在结论里注明"AE 参数不同源,仅作参考"。
7.3 先把码流质量提上去,否则测的是编码器不是 ISP
sample_ainr.c:386、:395 用的是 VENC_RC_MODE_H265CBR,码率由 sample_comm_venc.c:325-328 按分辨率给:
case PIC_2560x1440: //360W
h265_cbr.bit_rate = (1024 * 3 * framerate)/30; // framerate=15 → 1536按 kbps 理解就是 1.5 Mbps @ 2560x1440。这个码率下 H265 会把暗部噪点当冗余抹掉,你测出来的"AINR 降噪 40%"很可能是编码器的功劳。
改法:把两处 VENC_RC_MODE_H265CBR 改成 VENC_RC_MODE_H265FIXQP(sample_comm_venc.c:344-354 给的是 i=25/p=30/b=32,码率不封顶、暗部细节明显好于 1.5 Mbps CBR),只影响本用例:
/* 评测用固定 QP:码率不再压制暗部噪点,避免把编码器降噪算进 AINR 头上 */
ai_param->venc_config.chn_info[ai_param->venc_chnl].rc_mode = VENC_RC_MODE_H265FIXQP;改完用 ffprobe 验证实际码率(别照抄我的推算,以实测为准):
ffprobe -v error -select_streams v:0 \
-show_entries stream=width,height,r_frame_rate,bit_rate -of default=nw=1 out_on.mp4[!note] r_frame_rate 可能是 30,不是异常 ISP 设 15fps(sample_ainr.c:665),但 VENC 建链用的是 ai_param->framerate = 30(由 sample_comm_vi.c:704 的 *framerate = 30 给出),所以容器按 30fps 打时间戳、其中一半是重复帧。第七节的脚本会跳过重复帧。
7.4 抓流与抽帧
容器或宿主机上执行(需 ffmpeg):
# 录 30 s:AINR 开 / 关各一段,场景和照度必须完全一致
ffmpeg -rtsp_transport tcp -i rtsp://192.168.31.55:554/livestream/0 -t 30 -c copy -y out_on.mp4
ffmpeg -rtsp_transport tcp -i rtsp://192.168.31.55:554/livestream/0 -t 30 -c copy -y out_off.mp4
# 抽帧(PNG 无损,供脚本分析)
ffmpeg -i out_on.mp4 -vsync 0 -y frames_on/%04d.png
ffmpeg -i out_off.mp4 -vsync 0 -y frames_off/%04d.png板端录到 SD 卡,别写 /tmp——这套板子 /tmp 在 jffs2 上,会吃 flash。
7.5 量化指标脚本
只看"感觉噪点少了"没法验收。用同一份脚本算四项:亮度、时域噪声、空域噪声、色度活性。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""暗光画质指标统计:亮度、时域噪声、空域噪声、色度活性。
用法:
python3 aq_metrics.py out_on.mp4
python3 aq_metrics.py frames_on/
输出:
终端一行汇总 + 同名 .csv 明细(逐帧)
依赖:
pip install opencv-python numpy
"""
import csv
import glob
import os
import sys
import cv2
import numpy as np
def load_frames(path):
"""目录按文件名顺序读 PNG;其它按视频解码。"""
if os.path.isdir(path):
files = sorted(glob.glob(os.path.join(path, "*.png")) +
glob.glob(os.path.join(path, "*.jpg")))
for one_file in files:
img = cv2.imread(one_file, cv2.IMREAD_COLOR)
if img is not None:
yield img
else:
cap = cv2.VideoCapture(path)
while True:
ok, img = cap.read()
if not ok:
break
yield img
cap.release()
def main():
if len(sys.argv) != 2:
print(__doc__)
return 1
src = sys.argv[1]
prev_y = None
rows = []
dup_cnt = 0
for idx, bgr in enumerate(load_frames(src)):
ycc = cv2.cvtColor(bgr, cv2.COLOR_BGR2YCrCb)
y = ycc[:, :, 0].astype(np.float32)
cr = ycc[:, :, 1].astype(np.float32)
cb = ycc[:, :, 2].astype(np.float32)
# 空域噪声:拉普拉斯响应标准差,越大越"吵"
lap_std = float(cv2.Laplacian(y, cv2.CV_32F, ksize=3).std())
# 色度活性:只统计中间亮度区,死黑和过曝会把均值拉偏
mask = (y > 40.0) & (y < 200.0)
if mask.sum() > 0:
chroma = (np.abs(cr[mask] - 128.0) + np.abs(cb[mask] - 128.0)) / 2.0
chroma_mean = float(chroma.mean())
chroma_std = float(chroma.std())
else:
chroma_mean = 0.0
chroma_std = 0.0
# 时域噪声:相邻帧亮度差的均值;完全相同的帧是编码器补帧,跳过
temp_noise = None
if prev_y is not None:
diff = np.abs(y - prev_y)
if float(diff.max()) == 0.0:
dup_cnt += 1
else:
temp_noise = float(diff.mean())
prev_y = y
rows.append({
"frame": idx,
"y_mean": round(float(y.mean()), 3),
"lap_std": round(lap_std, 3),
"chroma_mean": round(chroma_mean, 3),
"chroma_std": round(chroma_std, 3),
"t_noise": "" if temp_noise is None else round(temp_noise, 3),
})
if not rows:
print("没有读到任何帧,检查路径: %s" % src)
return 1
def avg_of(key):
vals = [r[key] for r in rows if r[key] != ""]
return float(np.mean(vals)) if vals else 0.0
print("来源 : %s" % src)
print("帧数/重复帧 : %d / %d" % (len(rows), dup_cnt))
print("亮度均值 Y : %.2f (目标 90~140)" % avg_of("y_mean"))
print("时域噪声 mean|dY|: %.3f (越小越稳)" % avg_of("t_noise"))
print("空域噪声 lap_std : %.3f (越小越干净)" % avg_of("lap_std"))
print("色度均值 : %.2f (越低越灰, 说明颜色被抹掉)" % avg_of("chroma_mean"))
print("色度噪声 : %.2f" % avg_of("chroma_std"))
csv_path = os.path.basename(src.rstrip("/")) + ".csv"
with open(csv_path, "w", newline="", encoding="utf-8") as fp:
writer = csv.DictWriter(fp, fieldnames=list(rows[0].keys()))
writer.writeheader()
writer.writerows(rows)
print("明细已写入 : %s" % csv_path)
return 0
if __name__ == "__main__":
sys.exit(main())跑法:
# 宿主机(有桌面/摄像头网络可达板子)
python3 aq_metrics.py frames_on
python3 aq_metrics.py frames_off7.6 记录表与验收门槛
每档照度填一行,AINR 开/关各一份:
照度 | 组别 | Y 均值 | 时域噪声 | 空域噪声 | 色度均值 | 主观(1~5) | 备注 |
|---|---|---|---|---|---|---|---|
50 lux | ON | 基线,不应比 OFF 差 | |||||
50 lux | OFF | ||||||
0.1 lux | ON | 主战场 | |||||
0.1 lux | OFF | ||||||
0.01 lux | ON | 极限 | |||||
0.01 lux | OFF |
建议门槛(工程经验值,不是原厂标准,按项目自行收紧):
指标 | 门槛 | 不达标的含义 |
|---|---|---|
Y 均值 | 90~140 | 偏离说明 AE 没收敛或增益已打满 |
时域噪声 | AINR 开比关低 ≥ 30% | 低于此说明 AI 没真正生效或太弱 |
空域噪声 | 低 ≥ 20% | 只降时域不降空域 = 只剩 TNR 在干活 |
色度均值 | ≥ 关闭时的 80% | 低于此说明被降噪"抹成灰片",不算全彩 |
极暗可辨识 | 0.01 lux 能分辨肤色/衣物颜色 | 黑光全彩的对外承诺点 |
八、从"能出彩色"调到"像黑光全彩"
8.1 三层参数,改错层等于白改
层次 | 位置 | 生效时机 | 能改什么 |
|---|---|---|---|
代码写死 |
| 每次运行 | AINR 开关、 |
编译期默认 |
| 重新构建 sensor 驱动 | BNR/TNR 的 12 档 ISO 表、AWB 饱和度表、CCM |
运行时覆盖 |
| 导入成功即生效 | 上面全部 + AE/AWB/AF 算法参数(bin 里的 module 名可以是 |
现状:第三层文件缺失(sdk/sample/scene_auto/ 只有 README.txt),所以跑的是第二层的编译期默认值。这就是"AINR 通了但不够黑光"的根因。
8.2 曝光和帧率:收益最大、代价最直接
改 sample_ainr.c:665:
ai_param.isp_config.fps = 15; /* 1/15s 上限;想更暗改 10 或 5,拖影同步变大 */按 4.1 的表选:0.1 lux 档建议 15fps(66.5 ms),0.01 lux 档试 10fps(99.9 ms)。别一上手就 5fps,200 ms 曝光下 1 m/s 的行走目标拖影近 130 px,人脸会糊成糊。
8.3 AINR 强度:现在只能整体调,不能按 ISO 调
sample_comm_isp.c:1381-1387:
ainr_attr.enable = XMEDIA_TRUE;
ainr_attr.op_type = XMEDIA_VIDEO_OPERATION_MODE_MANUAL;
ainr_attr.manual_attr.pos_gain = 64;
ainr_attr.manual_attr.gau_gain = 64;pos_gain/gau_gain:xmedia_isp.h:2380-2383只声明了xmedia_u16,头文件没有单位注释,64是用例和 PQ ini(sc465sl_4M30_ainr.ini:190-191)里的一致默认值。按"Q6 定点、64=1.0x"理解是常见约定,但本文未在文档或代码里找到确认,改之前先用 PQ 工具看这两个值拖动后的画面变化,别盲调。想要"高 ISO 更狠、低 ISO 更保守",必须切
XMEDIA_VIDEO_OPERATION_MODE_AUTO并填auto_attr(iso_level[]/pos_gain[]/gau_gain[]三张表,长度iso_num,最大XMEDIA_ISP_ISO_MAX_COUNT)。switch_thd_low/high(:2394-2395)的判定口径没有注释,建议保留原值只动表,改阈值靠实测画面判断。
[!note] 没有 PQ bin 时,这就是唯一一档 6.4 已确认暗光下 AINR 满速在跑,所以当前真正生效的融合强度就是这组 64/64。拿不到 bin 时可以先在这里改值做对比实验(一次只改一个,同场景 A/B),但它只能调整体强度,补不回 sc465sl_ex.h 里那 12 档 ISO 的曲线,写报告时要区分"调了强度"和"调好了参数"。 另:能改强度不等于能改门控——enable=TRUE 之后是否真的把帧送进 NPU,仍由 6.4 那道亮度门控决定。
8.4 TNR/BNR:暗光画质的主要旋钮
sc465sl_ex.h 里 12 档 ISO 的表,几个直接影响黑光全彩观感的项:
字段 | 行 | 现状(ISO 100 → 204800) | 调它会发生什么 |
|---|---|---|---|
|
| 恒定 64 | 抬大 → 时域更狠,运动残影变明显 |
|
| 恒定 256 | 抬大 → 暗部被当噪声抹掉,细节丢失 |
|
| 256(≤3200)/ 320(6400)/ 384(≥12800) | 高 ISO 已在放大,说明原厂默认"暗处更保守防残影";黑光场景可再抬,代价是走动拖尾 |
|
| 1024 → 256(25600)→ 128(≥51200) | 阈值降低 = 更容易判成噪声,降噪更积极 |
|
| 2048 → 512(25600)→ 256(≥51200) | 同上,高阈值端 |
|
| 282 → 358(ISO≥1600 起拉满) | 空域去噪强度,抬大糊、压小噪 |
AWB |
| 120(1x)/ 100(2~16x)/ 60(≥32x) | 高增益主动降饱和抑制彩噪;黑光想保色可回调到 70~80,彩噪会回来 |
[!warning] 这张表不是"越大越好" tnr_luma_md_thd 已经随 ISO 反向调整,是原厂做过的平衡。手改优先只动 tnr_mot_noise_scl 和 tnr_bayer_str 两个,一次一档 ISO 地试,别整表重写。改完必须重编 sensor 驱动,且会被 PQ bin 覆盖。
8.5 AE:默认是"高光优先",黑光场景可以换
sc465sl.c:886:
ae_sns_dft->ae_exp_mode = XMEDIA_ISP_AE_STRATEGY_EXP_HIGHLIGHT_PRIOR;枚举里另一个是 XMEDIA_ISP_AE_STRATEGY_EXP_LOWLIGHT_PRIOR(xmedia_isp.h:1505-1507)。黑光全彩的诉求是"暗部提亮、彩色可辨",理论上该用低照优先,但它会更容易把路灯/车灯打爆,且这个档位在本文环境里没有实测数据。建议:先在 0.1 lux 场景切过去看直方图右端是否溢出,再决定是否保留。
8.6 补上 PQ bin(两种目的,别混淆)
路线 A:要真正的黑光全彩画质 → 原厂调好的 bin
仓库里没有,是因为 .gitignore 只放行 .ini,.bin/.xmm 都不入库。找原厂/代理要整个 scene_auto/,放成:
/mnt/sd/ainr/sample/scene_auto/sc465sl/sc465sl_linear_isp_vpss_ainr.bin路线 B:自己调 → PQ 工具导出
板端素材齐全:
板端服务:
sdk/tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi/PC 端 GUI:
sdk/tools/windows/pq_tools/PQTools_V1.3.12.zip参数工程:
configs/sc465sl/config_entry.ini里mode.1 = 4M30_AINR→sc465sl_4M30_ainr.ini
# 1) PQ 服务拷到 SD(rootfs 放不下)
cd /opt/sdk
scp -r $SSH_OPT tools/linux/pq_board/arm-gcc12.2.0-linux-uclibceabi root@$BOARD:/mnt/sd/pq
# 2) 起服务:sensortype + 模式序号(mode.1 = 4M30_AINR);PC 端连 4321 端口
ssh $SSH_OPT root@$BOARD 'cd /mnt/sd/pq/arm-gcc12.2.0-linux-uclibceabi && ./IspTool.sh -a sc465sl 1'[!warning] 参数个数有坑 IspTool.sh 开头判断是 [ $# -gt 4 ](IspTool.sh:47),参数多于 4 个会直接打帮助退出,size 根本传不进去。只用 -a sc465sl 1(3 个)或 -a sc465sl 1 0(4 个)。停服务用 ./IspTool.sh -as。
PC 端流程:解压 → 连板 IP:4321(config.cfg:3)→ 加载 sc465sl_4M30_ainr.ini → 在 0.1 lux 场景里边看 RTSP 边调 → 导出 bin → 命名 sc465sl_linear_isp_vpss_ainr.bin → 放 scene_auto/sc465sl/ → 重跑,日志里不再有 isp load pq bin failed!。
[!note] 起 PQ 服务和跑用例会抢 ISP ittb_stream 需要访问 ISP 设备。若 sample_ainr 正在跑导致起不来,改成"先起 PQ 服务、再用例连上去"或"离线调好导出 bin 再跑用例"。本文没有实测这条竞争关系,遇到失败按这个顺序换方案。
路线 C:只想消掉报错 + 固化基线
sample/pq_bin 工具可以导出当前运行参数(等于把默认值固化成 bin,不会提升画质,但能让每次运行的参数可复现、可 diff):
# 容器内:pq_bin 675940 字节,依赖只有 libc/libstdc++,拷一个文件就够
cd /opt/sdk
scp $SSH_OPT sample/pq_bin/pq_bin root@$BOARD:/mnt/sd/ainr/sample/pq_bin/
# 板上:sample_ainr 正在运行时执行(另开一路 SSH),导出到当前目录
ssh $SSH_OPT root@$BOARD 'cd /mnt/sd/ainr/sample/pq_bin && ./pq_bin 0'
ssh $SSH_OPT root@$BOARD 'cd /mnt/sd/ainr/sample/pq_bin && mv PQBin_isp_*.bin \
../scene_auto/sc465sl/sc465sl_linear_isp_vpss_ainr.bin'用法见 sample/scene_auto/README.txt 和 sample/pq_bin/sample_pq_bin.c:74-87(pq_bin [pipe] [file],带文件是导入,不带是导出)。
[!warning] 路线 C 的两个未验证点
pq_bin的sample_pq_bin_module_init()只做了xmedia_sys_init()+xmedia_vi_init()(sample_pq_bin.c:18-32),没有xmedia_isp_init(),而xmedia_isp_export()文档里明确可能返回NOT_INIT(xmedia_isp.h:3398)。如果导出失败,就改用 PQTools 界面上的导出按钮,或在sample_ainr进程内直接调xmedia_isp_export()。第二个进程调
xmedia_sys_init()时,VB 池已被sample_ainr占用,能否初始化成功取决于 MMZ 剩余空间(当前约 32 MB),失败时先停用例再试。
九、踩坑清单
# | 现象 | 根因 | 解决 |
|---|---|---|---|
1 |
|
| 挂 |
2 | rootfs 满后只剩 2 MB | 自带的 |
|
3 |
| cwd 不是 | 先 |
4 | 命令敲完终端空白 | stdout 管道下全缓冲 |
|
5 |
| 可能两次都没匹配到(空输出看起来一样) | 先确认有输出行再比较 |
6 |
|
| 见 8.6,路线 A/B/C 三选一 |
7 |
|
| 忽略,正常 |
8 | 拉流 30fps 但感觉卡顿 | ISP 15fps、VENC 按 30fps 打时间戳 | 用例本身的不一致,不是链路故障 |
9 | 暗光降噪"效果很好"但一验就崩 | 默认码率 1.5 Mbps,测到的是编码器降噪 | 按 7.3 改 |
10 | 退出后 MMZ 越来越小 |
| 回车、 |
11 | 改完 | 运行时被 PQ bin 覆盖,或 sensor 驱动没重编 | 先确认没有 bin 生效,再确认驱动重新构建 |
12 | AINR 开了但画面反而更"油"/细节丢失 |
| 按 8.3/8.4 一次只调一个旋钮 |
13 |
| 室内灯光下 AINR 被亮度门控旁路,增益远低于阈值 | 关灯 + 遮住镜头再采,见 6.4;亮光下 0 次是正常现象 |
14 | 已退回 shell, | 该块在 | 判断进程只看 |
坑 3 的证据链
日志里的 mmz_mmb_alloc(AIworkspace, 0, 0x0, 0, ) failed!,第二个参数就是申请大小,格式在 sdk/source/gmp/drv/osal/linux/kernel/mmz/mmz-userdev.c:163:
error_mmz("mmz_mmb_alloc(%s, %llu, 0x%llx, %lu, %s) failed!\n", name, size, align, gfp, mmz_name);size = 0 说明 xmedia_cl_graph_querysize_from_file() 没写回值,而这条路径用的正是 AINR_MODEL_DIR 相对路径。
为什么会"静默"带着 0 去申请:sample_comm_sys.c:631-633 的返回值判断写错了变量——
s32_ret = xmedia_cl_graph_querysize_from_file(npu_info->npu_model_path[i], &cl_work_size, &cl_weight_size);
if (cl_err_code != XMEDIA_SUCCESS) {
SAMPLE_ERR("xmedia_cl_graph_querysize_from_file fail\n");
goto CL_ERR3;
}
...
g_npu_workspace_cfg.work_space_phyaddr = xmedia_mmz_alloc(XMEDIA_NULL, "AIworkspace", max_cl_work_size);所以 querysize 失败也不会打印 querysize_from_file fail,而是直接拿 0 去 xmedia_mmz_alloc。调用顺序也印证:acquire_workspace 排在 isp_ainr_init 之前(sample_ainr.c:274-283),因此日志里只有 acquire workspace fail,不会出现 isp load model failed。
十、内存基线(判断还能不能加功能)
池 | 数量 | 单块(字节) | 小计 |
|---|---|---|---|
vi-npu(RAW12) | 2 | 5,529,600 | 10.55 MiB |
npu output | 1 | 6,912,000 | 6.59 MiB |
vpss-venc(tile compact) | 1 | 1,992,960 | 1.90 MiB |
AIworkspace | 1 | 3,520 KB | 3.44 MiB |
合计 | ≈ 22.5 MiB |
单块尺寸校验:2560 × 1440 × 1.5 = 5,529,600(12 bit 按 1.5 B/px 打包),与日志一致;NPU 输出块 6,912,000 / 3,686,400 = 1.875 B/px,是按 NPU 输出格式对齐后的尺寸。
80 MB 的 mmz 跑完剩 32 MB(实测 free=33032KB)。后续要加第二路编码或 VO,先按这张表重算,别直接改 spi_bootargs.txt 的 mmz——128 MB DDR 上 mem=48M + mmz=80M 已经没有余量。
十一、速查
# ---------- 容器内 ----------
export BOARD=192.168.31.55
SSH_OPT="-o ControlMaster=auto -o ControlPath=/tmp/b-%r@%h:%p -o ControlPersist=600"
cd /opt/sdk
# 编译(动过 common 才需要全编)
make -C sample/common -j"$(nproc)" && make -C sample/live_rtsp_server -j"$(nproc)" && make -C sample/ainr -j"$(nproc)"
# 部署
scp $SSH_OPT sample/ainr/sample_ainr root@$BOARD:/mnt/sd/ainr/sample/ainr/
# 运行(必须 -t,必须 cd)
ssh $SSH_OPT -t root@$BOARD 'killall factory_server 2>/dev/null; cd /mnt/sd/ainr/sample/ainr && ./sample_ainr 0'
# A/B:关 AINR 再跑一遍
ssh $SSH_OPT -t root@$BOARD 'killall factory_server 2>/dev/null; cd /mnt/sd/ainr/sample/ainr && AINR_OFF=1 ./sample_ainr 0'
# 验 AI 通路:6.2 看分配,6.4 看是否真在算
ssh $SSH_OPT root@$BOARD 'cat /proc/umap/media-mem | grep -E "AIworkspace|total size"'
ssh $SSH_OPT root@$BOARD 'ps | grep -c "[s]ample_ainr"'
ssh $SSH_OPT root@$BOARD 'grep -i npu /proc/interrupts; sleep 5; grep -i npu /proc/interrupts'
# 中断必须在关灯/遮镜头后采:亮光下恒 0 是正常,暗光满速约 +266/5s
# 抓流 + 抽帧 + 出指标
ffmpeg -rtsp_transport tcp -i rtsp://192.168.31.55:554/livestream/0 -t 30 -c copy -y out_on.mp4
ffmpeg -i out_on.mp4 -vsync 0 -y frames_on/%04d.png
python3 aq_metrics.py frames_on板端常用:
mount /dev/mmcblk1p1 /mnt/sd # 挂 SD
df -h /mnt/sd # 容量
cat /proc/umap/media-mem # MMZ 明细
killall sample_ainr # 停用例十二、还没闭环的部分
AINR 被旁路时链路是否照常出图:已观察到亮光下(AINR 不介入)用例完成建链、两路 RTSP 地址正常打印,但未抓流逐帧确认;要作为结论就按 7.4 录一段复核,或退回"两份二进制对照"。
亮度门控的阈值与判定口径未知:实测门控确实存在(6.4 三组数据),但
switch_thd_low/high在MANUAL下不参与,真正判定在预编译 ISP 内,用户态没有接口。想调阈值只能问 FAE。门控抖动只做了逻辑推断,没有实测画面:2.4 次/秒那组数据最可能是阈值穿越导致断续开关,需要在临界照度下连续采样 + 录流对照才能确认(方法见 6.4)。
ainr_state的语义与采样时机未闭环:头文件只写"已激活/未激活"(xmedia_isp.h:4725),没说它是"已使能"还是"当前帧用了 AI 结果"。实测 t≈0 读到 0;暗光下中断在涨时它是否翻 1,尚未做两点采样验证。单点读 0 不能作为否定证据。pos_gain/gau_gain的量纲未确认:头文件无注释,仅有 64 这个默认值。改之前用 PQ 工具观察实际效果。AUTO 模式的
switch_thd_low/high判定口径未知:没有注释也没有用例参考,建议只动 ISO 增益表。AE 实际 ISO/曝光/增益拿不到:公开接口只有
get_ae_statistics(统计值),没有 AE 结果查询。要看实时增益只能用 PQ 工具,或在sc465sl.c的sc465sl_update_inttime()(:981)/sc465sl_update_gains()(:1074)里加打印——后者需要重编 sensor 驱动。PQ 服务与用例抢 ISP 的实际情况未测:
ittb_stream在sample_ainr运行时能否正常起,取决于设备打开方式,本文按原厂流程记录,失败按 8.6 的备选顺序处理。80 MB mmz 偏紧:AINR 稳态 22.5 MiB,剩 32 MB;再加一路编码就要重新评估。
十三、实测样张
13.1 拍摄条件
项 | 夜眸(GK7206 + AINR) | 小米 15 对照 |
|---|---|---|
时间 | 2026-11-04 20:08 前后 | 2026-11-04 20:08 |
地点 | 29°30'52"N 106°27'1"E | 同左(两张同点位) |
出力分辨率 | 2560×1440(PNG 抓帧) | 4096×3528(内嵌 3000×2002 预览) |
曝光 | 待补(用例默认 15 fps → 上限 66.5 ms) | 0.3 s(300 ms) |
增益 | 待补( | ISO 12800 |
镜头 | 待补 | 23 mm 等效,f/1.62 |
补光 | 无(IR-CUT 保留) | 无 |
AINR 是否介入 | 待按 6.4 用 | — |
[!warning] 这张夜眸样张还没做 AINR 对照组 下面只是一个单帧抓图,没有 AINR_OFF=1 的同场景对照,也没有中断采样记录。按 7.1 的要求,它只能作为"当前出图长什么样"的记录,不能当作 AINR 降噪效果的证据。
13.2 夜眸黑光样张

逐像素客观指标(YCbCr 域直接统计,与 7.5 脚本口径一致):
指标 | 数值 | 判读 |
|---|---|---|
亮度均值 Y | 122.5 | 落在 7.6 的 90~140 目标区间内,AE 已把增益推满 |
亮度标准差 Y | 69.4 | 场景本身动态范围大,明暗跨度宽 |
细节/噪声标准差(Y 与 σ=2 高斯模糊之差) | 36.5 | 极噪。同一指标下相机样张是 4.87,相差约 7.5 倍 |
Cb 均值 / Cr 均值 | 130.5 / 135.6 | Cr 明显偏高 → 整体偏紫红,说明暗部噪声被后级 CCM/饱和度放大成彩色噪点 |
这张图的问题不是"不够亮",而是亮度和噪声同时被拉满:AE 把接近全黑的场景提亮到 Y≈122,全程增益已在 107x × 15.875x 的高位,RAW 散粒噪声原样进了 ISP 后级。按第二节的账,此处缺的不是 AINR 开关,而是进光量(光圈)和积分时间(曝光)——小米 15 用 300 ms + f/1.62,比 66.5 ms 多 4.5 倍光,这就是细节能留住的直接原因。
13.3 小米 15 黑光样张(同点位对照)

指标 | 数值 | 判读 |
|---|---|---|
亮度均值 Y | 39.3 | 走的是"保持现场真实明暗",不强行提亮 |
亮度标准差 Y | 81.2 | 对比度更高(含底部参数条,会拉低均值) |
细节/噪声标准差 | 4.87 | 干净,细节与噪声可分离 |
Cb 均值 / Cr 均值 | 127.4 / 128.1 | 中性,无明显色偏 |
13.4 差距该往哪调
按优先级(收益 / 代价比)排序:
曝光时间:小米 15 是 300 ms,本用例上限 66.5 ms(15 fps)。降到 5 fps 可得 199.9 ms,+15.6 dB,但按 2.3 的几何账,1 m/s 目标拖影 433 px —— 静止抓拍可以,有人的场景直接否掉。折中按 8.2 先试 10 fps(99.9 ms,+9.6 dB)。
光圈:f/1.62 → f/1.0 是 2.56 倍光通量(+8.2 dB)。这是硬件项,改不了镜头就只能认。
AE 策略:当前是
EXP_HIGHLIGHT_PRIOR(sc465sl.c:886),暗部被压、增益顶满。切EXP_LOWLIGHT_PRIOR提暗部,但要防路灯/窗光过曝,见 8.5。降噪参数:
sc465sl_ex.h的 12 档 ISO 表 + 缺失的sc465sl_linear_isp_vpss_ainr.bin(8.6)。当前跑的是编译期默认值,saturation在 ≥32x 档已被压到 60,先把这档回调到 70~80 再看颜色是否回来。色彩:Cr 偏紫 7.6 个码值,优先怀疑 AWB 在极低照度下未收敛(而非降噪),可用 PQ 工具在案发现场对着灰卡确认。
[!note] 两张图不可直接做 A/B 分辨率、光圈、曝光、增益、后处理全不同,只能做"目标效果参考"。真正的 A/B 必须按 7.2 在同一进程、同一场景、同一曝光下用 AINR_OFF=1 跑。
