夜眸3.AINR黑光全彩实测教程

原创2026-11-04 23:23:27浏览2
2
Star
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

sample_comm_isp.c:1367-1368 设 ainr_pos_switch=POST + tnr_enable=TRUE

AINR 是常开还是随光照切换

不是常开:亮度不够时被 ISP 旁路;强度则恒定,不随 ISO 变

强度写死在 sample_comm_isp.c:1381-1384(MANUAL、64/64);门控在预编译 ISP 内,实测亮光 0 次/秒、暗光 53 次/秒(6.4)

switch_thd_low/high(AUTO 模式阈值)管不管这个门控

管不到

用例全程 MANUAL;xmedia_isp.h:2394-2395 只给出字段、无注释;实测门控发生在 ISP 内部,用户态没有接口(6.4)

怎么确认 AINR 真在跑

暗光下看 NPU 中断,满速约 53 次/秒(15 fps、约 3.5 次/帧);亮光下恒 0 属正常

6.4 三组对照实测

那 ainr_state 算什么证据

最直接但时机敏感:单点读 0 不算证据,必须暗光下两点采样(启动瞬间 + 退出前)

xmedia_isp.h:4725;process_run 不阻塞(sample_ainr.c:464-529),实测启动瞬间读到 0

提高夜间亮度的最有效手段

降 fps 换曝光时间

sc465sl.c:1354 full_lines = vmax*30/fps,15fps 最大曝光 66.6 ms(见第四节算例)

为什么默认 15fps

用例写死

sample_ainr.c:665 isp_config.fps = 15

出图彩色但不"黑光"

缺 PQ bin,跑的是驱动里的编译期默认参数

sample_ainr.c:499 导入失败只打印,不中断;sdk/sample/scene_auto/ 只有 README

想按 ISO 分别调降噪强度

只能走 PQ bin / PQ 工具

运行时 xmedia_isp_import() 覆盖,sensor/*_ex.h 里的表只是默认值

FPN / BPC 能不能一起开

不能

xmedia_isp.h:3970、4164:fpn标定, bpc标定, ainr, dis, pdaf 功能互斥

拿不到当前 ISO / 增益

公开头文件没有 AE 结果查询接口

xmedia_isp.h 只有 get_ae_statistics(统计值),无 get_ae_result

mmz 够不够

够,AINR 稳态约 22.5 MiB,80 MB 池跑完剩 32 MB

见第十节 VB 尺寸表(数值来自代码 + 实测日志)

二、黑光全彩的边界:先算清物理账

2.1 它到底是什么

不靠红外灯、不靠白光补光,在肉眼接近全黑的场景(照度 0.1 lux 以下)输出可辨识颜色的图像。做到这一点只有三条路叠加:

  1. 进光量:大光圈镜头。f/1.0 相对 f/1.6 是 (1.6/1.0)^2 = 2.56 倍光通量,约 +8.2 dB。

  2. 积分时间:降帧率换长曝光。30fps → 15fps 是 2 倍,+6 dB;降到 5fps 是 6 倍,+15.6 dB。

  3. 降噪:长曝光和高增益必然带出散粒噪声 + 读出噪声,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

156-157、164

VB 池带 ainr_en=TRUE,区分 INPUT/OUTPUT 方向

VI 不会把 RAW 转给 NPU,AI 通路不存在

2

609-610

pipe_info.ainr_en=TRUE、ainr_model_bitwidth=bit_width

ISP 不知道要接 AI 结果

3

274-281

申请 NPU workspace(必须在 isp_init 之后)

acquire workspace fail

4

283

sample_comm_isp_ainr_init() 加载模型 + 开 AINR + 开 TNR

图像退回普通 ISP

5

499

导入 *_ainr.bin PQ 参数

画质是驱动默认值(黑光全彩效果就在这里丢的)

对黑光全彩最关键的是第 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

行时间

最大曝光 (full_lines-8)×行时间

相对 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(xm7206v11a),DDR 128 MB

cfg.mk

Sensor

SMART_SC465SL_MIPI_4M_4LANE_30FPS_12BIT

sample/sample_base.mk:43

模型

neuron_network.xmm,1706790 B

configs/sc465sl/

mmz

mmz=anonymous,0,0x43000000,80M

configs/xm7206v11a/prebuilts/spi_bootargs.txt:8

编译

容器内,SDK 在 /opt/sdk

见《SDK编译产物与运行指南》

SD 卡

/dev/mmcblk1p1,exFAT,挂 /mnt/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'

三条硬要求:

  1. 必须 cd 进去:模型和 bin 都是相对 cwd 的,路径错时不会报"文件不存在",而是 mmz_mmb_alloc(AIworkspace, 0, ...) failed(size=0 说明 querysize 没拿到,不是内存不够)。

  2. 先 killall factory_server:VI 独占。

  3. 必须 -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.npu

11340000 就是 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 没激活"。判据只有一条:关灯之后有没有涨。不关灯这条永远过不了。

判读表:

ps

暗光下中断

结论

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_off

7.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 三层参数,改错层等于白改

层次

位置

生效时机

能改什么

代码写死

sample_comm_isp.c:1381-1384

每次运行

AINR 开关、op_type、pos_gain/gau_gain

编译期默认

source/gmp/usr/isp/sensor/smartsens_sc465sl/sc465sl_ex.h

重新构建 sensor 驱动

BNR/TNR 的 12 档 ISO 表、AWB 饱和度表、CCM

运行时覆盖

*_ainr.bin → xmedia_isp_import()

导入成功即生效

上面全部 + AE/AWB/AF 算法参数(bin 里的 module 名可以是 ISP/AE/AWB/AF,见 xmedia_isp.h:101-109)

现状:第三层文件缺失(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)

调它会发生什么

tnr_bayer_str

:68

恒定 64

抬大 → 时域更狠,运动残影变明显

tnr_noise_min

:67

恒定 256

抬大 → 暗部被当噪声抹掉,细节丢失

tnr_mot_noise_scl

:126

256(≤3200)/ 320(6400)/ 384(≥12800)

高 ISO 已在放大,说明原厂默认"暗处更保守防残影";黑光场景可再抬,代价是走动拖尾

tnr_luma_md_thd_low

:80

1024 → 256(25600)→ 128(≥51200)

阈值降低 = 更容易判成噪声,降噪更积极

tnr_luma_md_thd_high

:81

2048 → 512(25600)→ 256(≥51200)

同上,高阈值端

snr_coarse_str

:28-43

282 → 358(ISO≥1600 起拉满)

空域去噪强度,抬大糊、压小噪

AWB saturation

:296

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 的两个未验证点

  1. 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()。

  2. 第二个进程调 xmedia_sys_init() 时,VB 池已被 sample_ainr 占用,能否初始化成功取决于 MMZ 剩余空间(当前约 32 MB),失败时先停用例再试。

九、踩坑清单

#

现象

根因

解决

1

scp: No space left on device,之后连 chmod 都报同样错

/mnt 没挂 SD,全写进 10.9 MB 的 jffs2 rootfs

挂 /dev/mmcblk1p1 到 /mnt/sd,二进制只放卡上

2

rootfs 满后只剩 2 MB

自带的 /usr/bin/sample_venc 一个文件 3.9 MB

rm -f /usr/bin/sample_venc && sync

3

check_model_header assert → mmz_mmb_alloc(AIworkspace, 0, 0x0, 0, ) failed → acquire workspace fail

cwd 不是 sample/ainr,相对路径解析失败,querysize 拿不到 size(不是内存不够)

先 cd 再执行,或做 wrapper:cd "$(dirname "$0")" && exec ./sample_ainr "$@"

4

命令敲完终端空白

stdout 管道下全缓冲

ssh -t 分配 pty,或重定向后 tail -f

5

cat /proc/interrupts \| grep npu 两次"一样"

可能两次都没匹配到(空输出看起来一样)

先确认有输出行再比较

6

isp load pq bin failed!

scene_auto/sc465sl/sc465sl_linear_isp_vpss_ainr.bin 缺失

见 8.6,路线 A/B/C 三选一

7

isp enable fpn failed!

xmedia_isp.h:3970/4164:FPN 与 AINR 互斥

忽略,正常

8

拉流 30fps 但感觉卡顿

ISP 15fps、VENC 按 30fps 打时间戳

用例本身的不一致,不是链路故障

9

暗光降噪"效果很好"但一验就崩

默认码率 1.5 Mbps,测到的是编码器降噪

按 7.3 改 VENC_RC_MODE_H265FIXQP 后重测

10

退出后 MMZ 越来越小

kill -9 或崩溃跳过清理,isp_* 与 AIworkspace MMB 泄漏(日志有 MMB LEAK)

回车、Ctrl+C、kill(SIGTERM)都会走 g_ainr_force_exit 清理(sample_ainr.c:79-84、:864-865、:535),只有 SIGKILL 会漏;已泄漏就重启再测

11

改完 sc465sl_ex.h 没变化

运行时被 PQ bin 覆盖,或 sensor 驱动没重编

先确认没有 bin 生效,再确认驱动重新构建

12

AINR 开了但画面反而更"油"/细节丢失

pos_gain/gau_gain 或 tnr_noise_min 过大

按 8.3/8.4 一次只调一个旋钮

13

grep -i npu /proc/interrupts 恒为 0,以为 AINR 没跑

室内灯光下 AINR 被亮度门控旁路,增益远低于阈值

关灯 + 遮住镜头再采,见 6.4;亮光下 0 次是正常现象

14

已退回 shell,/proc/umap/media-mem 里仍有 AIworkspace

该块在 isp_ainr_init 之前就已分配(sample_ainr.c:277),退出路径没释放干净时会残留

判断进程只看 ps \| grep -c "[s]ample_ainr",别拿内存块当存活证据

坑 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)

增益

待补(max_again 107x + max_dgain 15.875x 上限)

ISO 12800

镜头

待补

23 mm 等效,f/1.62

补光

无(IR-CUT 保留)

无

AINR 是否介入

待按 6.4 用 /proc/interrupts 复核

—

[!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 差距该往哪调

按优先级(收益 / 代价比)排序:

  1. 曝光时间:小米 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)。

  2. 光圈:f/1.62 → f/1.0 是 2.56 倍光通量(+8.2 dB)。这是硬件项,改不了镜头就只能认。

  3. AE 策略:当前是 EXP_HIGHLIGHT_PRIOR(sc465sl.c:886),暗部被压、增益顶满。切 EXP_LOWLIGHT_PRIOR 提暗部,但要防路灯/窗光过曝,见 8.5。

  4. 降噪参数:sc465sl_ex.h 的 12 档 ISO 表 + 缺失的 sc465sl_linear_isp_vpss_ainr.bin(8.6)。当前跑的是编译期默认值,saturation 在 ≥32x 档已被压到 60,先把这档回调到 70~80 再看颜色是否回来。

  5. 色彩:Cr 偏紫 7.6 个码值,优先怀疑 AWB 在极低照度下未收敛(而非降噪),可用 PQ 工具在案发现场对着灰卡确认。

[!note] 两张图不可直接做 A/B 分辨率、光圈、曝光、增益、后处理全不同,只能做"目标效果参考"。真正的 A/B 必须按 7.2 在同一进程、同一场景、同一曝光下用 AINR_OFF=1 跑。