夜眸4.屏摄流星素材采集与验证(推流 + 拍照)

原创2026-11-12 09:11:46浏览2
2
Star
title: 屏摄流星素材采集与验证(推流 + 拍照)
aliases:
  - 屏摄采集验证
  - 推流与拍照
tags:
  - 嵌入式
  - GK7206
  - 屏摄
  - 采集
  - 流星
created: 2026-11-05
updated: 2026-11-11

屏摄流星素材采集与验证(推流 + 拍照)

[!abstract] 一句话 相机对着显示器,显示器播放流星视频,板上推流取景、锁曝光连抓 JPEG 到 SD 卡。 本文只讲两件事:推流预览和拍照留档。 模型训练、XMM 转换、板端 NPU 部署见《[[AI 模型训练与板端部署验证]]》。 本文所有结论均已在 GK7206V11A + SC465SL 实板上跑通验证,非纸面推演。


一、这一步要回答的三个问题

问题

判据

不达标的后果

曝光对不对

图上肉眼能数出 ≥ 30 个星点

太暗 → 弱流星淹没在噪声里;太亮 → 屏幕过曝成白板

摩尔纹严不严重

行亮度波动 < 1.0 灰阶,无周期性网格

严重 → 素材带栅格纹理,不能用于标注

流星的像素尺寸

单帧内能看出长度 ≥ 8 px 的亮线

低于 8 px → 缩放后丢失,检测不了

这三个数字决定了这批素材值不值得进入标注流程。 花 1 小时量出来,比标两周数据然后发现输入信号不合格划算得多。


二、上电后操作清单

[!tip] 顺序 上电 → 确认 SD 已挂载 → 推流调角度 → 拍照 → 取回 PC

2.1 上电与 SD 卡挂载

开机脚本 S99y_mount_sd 会自动挂载 SD 卡,不启动任何业务程序。上电后确认:

df -h /mnt/sd
mountpoint /mnt/sd && echo mounted

三个判断点:

现象

含义

mountpoint 输出 is a mountpoint

挂载成功

df 显示 rootfs 那几 MB

没挂上,/mnt/sd 只是 rootfs 上的空目录

ls -l /mnt/sd 看不到 screen_capture

同上,程序还在卡里没挂出来

挂载失败时看启动日志:

dmesg | grep -i -E "mmc|exfat|vfat" | tail -20

脚本会依次尝试 mount 自动探测、exfat、vfat、ext4 四种方式。卡必须上电前插好,mmc1 的 cd-gpios 在 DTS 里被注释掉了,运行中插卡不保证被识别。

手动补挂(执行权限是必须的,exFAT 没有 Unix 权限位):

mkdir -p /mnt/sd
mount -o exec,umask=000 /dev/mmcblk1p1 /mnt/sd
df -h /mnt/sd

2.2 推流预览(调角度)

SD 卡里那套黑光夜视程序可以当取景器。必须先 cd 到程序所在目录,它按相对路径加载 NPU 模型:

cd /mnt/sd/ainr/sample/ainr
./sample_ainr 0

PC 上用 VLC 打开:

rtsp://192.168.137.53:554/livestream/0

调好角度后立刻停掉,它和拍照程序不能同时跑:

killall sample_ainr
sleep 3

三点注意:

  1. 末尾的 0 是场景模式(单路线性 ainr 15fps)。

  2. 不要 kill -9。强杀会留下未释放的 ISP pipe 和一片 MMB LEAK,下次启动直接报 0x80010010,只能重启。

  3. 在别的目录直接跑会报 mmz_mmb_alloc(AIworkspace, 0, ...) failed!——那个 0 是申请大小,模型没加载成功所以算成 0,不是内存不够。详见 7.5。

[!warning] 预览不是采集结果 ainr 开了 AI 降噪,噪点和细节都被抹过,曝光也是自动的。 构图照着调没问题,但亮度、锐度、噪点必须以 screen_capture 锁曝光后拍出来的图为准。

2.3 拍照

mkdir -p /mnt/sd/raw1
/mnt/sd/screen_capture /mnt/sd/raw1 0

程序会先等 AE 收敛稳定,把四个参数锁成手动,然后全速连抓。完整输出:

sensor: 2560x1440 @ 30 fps
VB 池需求:RAW 5529600 B x 2 + YUV 5529600 B x 3 = 27000 KB
当前工作目录:/mnt/sd/ainr/sample/ainr
AE 已稳定(等待 1400 ms):exp_time=33220 again=1536 dgain=1024 isp_dgain=1029
准备写入 AE:exp_time=33220 again=1536 dgain=1024 isp_dgain=1029 sizeof_attr=324
AE 已切手动:exp_time=33220 again=1536 dgain=1024 isp_dgain=1029
锁定后 AE 状态:
---- 当前 AE 状态 ----
  exp_time        = 33208
  again           = 1536
  dgain           = 1024
  isp_dgain       = 1028
  fps             = 3000
  lines_per_500ms = 39990
  avg_luma        = 54
  iso             = 142
----------------------
开始采集,全速连抓,按 Ctrl-C 结束
[sample_comm_venc_capture_jpeg]-1397: save jpeg file:/mnt/sd/raw1/chn0_snap_0.jpg ok
[1136.564] 第 1 张已存盘
[1136.600] 第 2 张已存盘

按 Ctrl-C 停止,最后打印 采集结束,共 N 张。

每轮换一个目录(raw1、raw2…),否则编号从 0 重来会覆盖上一批。

[!note] 锁定成功的判据 「锁定后 AE 状态」里的 exp_time 和 again 必须与「准备写入 AE」时打印的数值一致(允许 exp_time 差几十微秒、isp_dgain 差 1,那是驱动内部的行数量化)。 如果回读值又跳回别的数,说明手动模式没生效,之后每一帧的曝光都在漂,这批数据不能当定量素材用。

2.4 采集节奏怎么选

程序支持三种节奏,按用途选:

用途

命令

实测表现

找流星帧

/mnt/sd/screen_capture /mnt/sd/raw1 0

全速连抓,27 fps,约 37 ms 一张

常规留档

/mnt/sd/screen_capture /mnt/sd/raw1 500

2 张/秒,文件量小

长时间铺量

/mnt/sd/screen_capture /mnt/sd/raw1 1000

1 张/秒

拍流星必须用 0。 30 fps 下流星只在单帧里出现(曝光 33 ms),间隔 500 ms 等于每 15 帧才看 1 帧,占空比 6.7%,约三分之一的流星抓不到。而且慢间隔下拿不到相邻帧,事后无法用"前后帧是否连续"来区分流星和噪声。

全速连抓实测 27 fps 而不是 30 fps,原因是 sample_comm_venc_capture_jpeg 同步阻塞(取帧 → 编码 → 写卡串行),每张耗时 37 ms 略超帧周期 33.3 ms,累积下来约 10% 丢帧。要彻底解决需要把取帧和编码拆线程,当前程度够用。

2.5 取回 PC

拔卡最快:

sync
umount /mnt/sd

sync 必须先执行,否则 exFAT 上可能丢文件。然后插读卡器。

走网络也行:

# 容器内(旧版 OpenSSH,不要加 -O)
IP=192.168.137.53
mkdir -p /opt/sdk/sample/screen_capture/pull
scp "root@$IP:/mnt/sd/raw1/*.jpg" /opt/sdk/sample/screen_capture/pull/

[!tip] 容器内路径就是宿主机路径 容器用 -v ~/NightEyes/sdk:/opt/sdk 启动,所以拉到 /opt/sdk/xxx 等于落到宿主机的 ~/NightEyes/sdk/xxx,可以直接在 VSCode 里打开看图。


三、交付物一:采集程序

3.1 文件位置

文件

路径

说明

源码

sdk/sample/screen_capture/screen_capture.c

UTF-8 with BOM

编译脚本

sdk/sample/screen_capture/Makefile

与 sample/ainr 同构

板上位置

/mnt/sd/screen_capture

整目录随 SD 卡走

程序做的事:建 VI → ISP → VPSS → VENC(JPEG) 链路,等 AE 稳定后锁曝光,然后按指定节奏抓图。

3.2 编译(容器内)

SDK 的 build/env.sh 会把交叉工具链加进 PATH,不要手写 export 路径:

cd /opt/sdk/sample/screen_capture
source ../../build/env.sh
make
ls -l screen_capture

预期:

-rwxr-xr-x 1 root root 4081948 Nov 11 19:26 screen_capture

三个要点:

  1. source 只对当前 shell 生效,关掉终端要重来。

  2. 报 command not found 就是没 source,不是容器不对。

  3. 4 MB 左右的体积是正常的。程序用 -Wl,-Bstatic 静态链接了全部 xmedia 库,strip 只能去掉符号表,代码段去不掉。想再小要加 -Wl,--gc-sections,但有误删构造函数的风险,跑通前别动。

  4. 编译带 -Wall -Werror,任何一个警告都会失败。

3.3 上板

# 容器内
IP=192.168.137.53
scp /opt/sdk/sample/screen_capture/screen_capture root@$IP:/mnt/sd/screen_capture
ssh root@$IP 'ls -l /mnt/sd/screen_capture'

权限应为 -rwxrwxrwx。若是 -rw-r--r--:

mount -o remount,exec,umask=000 /mnt/sd

四、拍照程序的三个模式

4.1 --dump:只看不采

/mnt/sd/screen_capture --dump

起 VI/ISP,等 AE 稳定,打印四个参数,然后退出。不建 VPSS/VENC、不写卡。想快速确认当前场景 AE 落在哪个档位时用。

实测输出:

AE 已稳定(等待 1400 ms):exp_time=33220 again=1440 dgain=1024 isp_dgain=1037
---- 当前 AE 状态 ----
  exp_time        = 33220
  again           = 1440
  dgain           = 1024
  isp_dgain       = 1037
  fps             = 3000
  lines_per_500ms = 39990
  avg_luma        = 54
  iso             = 142
----------------------

读数规则:

字段

单位

判读

exp_time

微秒

33220 ≈ 1/30 s,正好是 30 fps 的整帧曝光上限,说明场景偏暗、AE 拉满

again

Q10 定点

1024 = 1.0 倍;屏摄显示器实测 1440(1.4 倍);夜间拍星空实测 12003(11.7 倍)

fps

0.01 fps

3000 = 30.00 fps。若显示 2500 或 1200,说明 AE 动态降帧没关掉

avg_luma

灰阶

50 左右属正常;< 10 说明极暗,> 80 有过曝风险

[!important] 换 sensor 时仍要重新判单位 xmedia_ae_manual_attr.exp_time 的单位在头文件里没有写明。本文结论(微秒)只对 SC465SL 驱动成立。 换 sensor 先看 exp_time 与整帧周期是否对得上:30 fps 整帧 = 33333 us,25 fps = 40000 us。 若 exp_time 只有 1000 量级、和图像高度 1440 同数量级,那单位是行数,需按行时间换算。

4.2 全速连抓(拍流星)

mkdir -p /mnt/sd/raw2
/mnt/sd/screen_capture /mnt/sd/raw2 0

第 2 个参数填 0 = 不等待,抓完立即抓下一张。实测 27 fps。

4.3 慢速记录(记录环境变化)

/mnt/sd/screen_capture /mnt/sd/raw3 500

每张之间等 500 ms,适合记录云量、天光这类慢变化。

4.4 完整用法

screen_capture <保存目录> <间隔毫秒> [曝光时间] [again]

位置

参数

含义

怎么定

1

保存目录

JPEG 存到哪,文件名 chn0_snap_N.jpg 递增

用 /mnt/sd/ 下的目录,每轮换一个

2

间隔毫秒

两张之间等多久,0 为全速

找流星 0,常规 500,铺量 1000

3

曝光时间

单位微秒

省略即用 AE 自己收敛的值,推荐先省略

4

again

模拟增益,Q10 定点

省略即用 AE 值,需要严格跨批次可比时才手动指定

[!tip] 曝光参数可以完全省略 早期版本必须先 --dump 抄数再手动传参,现在改成"先等 AE 稳定,再把它自己算出的四个参数锁成手动"。 直接 /mnt/sd/screen_capture /mnt/sd/raw2 0 就行,省掉手抄这一步,也避免两次运行之间的环境光变化导致手抄值失配(实测遇到过 dump 读到 again=1536、隔一会儿启动时已经变了)。


五、判读方法

5.1 拷回 PC

# 容器内
IP=192.168.137.53
mkdir -p /opt/sdk/sample/screen_capture/pull
scp "root@$IP:/mnt/sd/raw1/*.jpg" /opt/sdk/sample/screen_capture/pull/
ls /opt/sdk/sample/screen_capture/pull | wc -l

5.2 三个判据,逐个数出来

判据 1:星点可辨

任选一张,在 PC 上放大到 100% 看。能数出 ≥ 30 个孤立亮点 = 曝光合格。

判据 2:横向条纹与摩尔纹(量化)

存成 judge_quality.py:

# judge_quality.py —— 量化屏摄画面的三个指标
import sys, glob
import numpy as np
import cv2

def row_band_score(gray):
    """横向条纹强度:每行亮度均值的一阶差分平均绝对值,越小越好。
       屏幕刷新拍频、滚动快门条纹都会把这个值顶上去。"""
    row_mean = gray.mean(axis=1)
    return float(np.abs(np.diff(row_mean)).mean())

def star_count(gray):
    """星点计数:均值 + 4σ 二值化后,面积 <= 40 px 的连通域个数。
       面积上限是为了排除流星、云块这类大目标。"""
    thr = gray.mean() + 4.0 * gray.std()
    _, mask = cv2.threshold(gray, thr, 255, cv2.THRESH_BINARY)
    n, _, stats, _ = cv2.connectedComponentsWithStats(mask, 8)
    return sum(1 for i in range(1, n) if stats[i, cv2.CC_STAT_AREA] <= 40)

files = sorted(glob.glob(sys.argv[1] + '/*.jpg'))
for f in files[:20]:
    img = cv2.imread(f, cv2.IMREAD_GRAYSCALE)
    if img is None:
        continue
    print('%-24s 行条纹=%5.2f  星点=%3d  均值=%5.1f'
          % (f.split('/')[-1], row_band_score(img), star_count(img), img.mean()))
python3 judge_quality.py /opt/sdk/sample/screen_capture/pull

判读表:

指标

合格

需要处理

处理办法

行条纹

< 1.0

≥ 1.0

曝光改成更短;显示器关 HDR/局部调光;相机帧率锁 30

星点

≥ 30

< 30

加曝光时间或提高 again,一次只动一个

均值

10 ~ 60

> 80 过曝 / < 5 死黑

过曝降曝光,死黑加曝光

判据 3:流星的像素长度

找视频里流星出现的那几秒,在对应时间戳附近的 JPEG 里找那条亮线,量长度:≥ 8 px 合格,< 8 px 换素材。


六、参数怎么调

6.1 曝光时间(最重要的一个)

值(微秒制)

等价

适用

33333

1/30 s

理论下限,会与刷新周期错拍,不建议

33220

≈1/30 s

AE 在暗场自动拉到的值,就是满帧曝光

16667

1/60 s

屏摄下限,刚好一个刷新周期

10000

1/100 s

推荐起点,留余量避开刷新扫描的过渡行

5000

1/200 s

屏幕很亮时用,要相应提高增益

[!warning] 屏摄不能长曝光 显示器 60 Hz,曝光超过 1/60 s 就会叠加两帧以上的视频画面,流星变成等距重影。 这是屏摄与实拍最本质的区别。

曝光上限由帧周期决定,程序里按 1000000 / fps 现算,超过会被驱动截断:

  • 30 fps → 33333 µs

  • 15 fps → 66666 µs

6.2 间隔

目的

间隔

说明

找流星帧

0

全速 27 fps,覆盖最密

常规采集

500

2 张/秒,文件少

长时间铺量

1000

1 张/秒

存储实测:2560x1440 JPEG 约 329 KB/张。

节奏

速率

占用

0(27 fps)

8.8 MB/s

31.7 GB/小时

500

0.66 MB/s

2.4 GB/小时

1000

0.33 MB/s

1.2 GB/小时

[!warning] 全速连抓一小时内就能写满 32 GB 卡 全速模式只适合短时抓拍(几分钟)。要长时间跑,要么降间隔,要么改成 H265 录像事后抽帧(码率 4 Mbps 时约 1.8 GB/小时,比 JPEG 小 18 倍)。

6.3 增益 again

不填就沿用 AE 稳定后的值。实测量级:

场景

again

倍数

对着显示器(画面较亮)

1440

1.41×

真实夜空

12003

11.72×

[!tip] 宁欠曝不要过噪 增益拉高会同时放大噪点,行条纹判据和星点计数都会被污染。先加曝光,加到满帧还暗,再动增益。


七、实测踩过的坑

7.1 错误码对照表

错误码

宏

本次触发场景

处理

0x80010001

INVALID_PARAM

别的进程占着媒体系统时传显式 pipe_mode;again < 1024

先停占用进程;增益是 Q10 定点,不能小于 1024

0x80010003

NOT_ENOUGH_MEMORY

VB 池分配时 MMZ 被别的程序占着

停掉占用者后重试,不是池子配大了

0x80010004

NOT_INIT

没调 venc_init 就建通道

补齐模块级 init

0x80010010

EXIST

ISP pipe 0 被占用

停掉占用者或重启

0x80010012

NOT_READY

只 reset 了 VI,没 reset VPSS

vi_init + vpss_init 一起做

7.2 MMZ 是完整的 80 MB,别被探测结果误导

xm_osal 卸载时会打印 zone 的真实范围:

MMZ force removed: PHYS(0x43000000, 0x47FFFFFF), GFP=0, nBYTES=81920KB, NAME="anonymous"

81920 KB = 80 MB,与 bootargs 完全一致。MMZ 从来不是 32 MB。

用「1 MB 块累加」探到 32 MB 就失败,是因为探测脚本先申请了一个 32 MB 大块再释放,而 MMZ 的释放是延迟的(引用计数归零才真正归还),后续累加只能拿到剩余部分。这个探测方法本身就不可靠。

正确的判断依据是卸载时的 nBYTES。

VB 池当前配置(SCREEN_CAPTURE_VB_BLK_CNT = 3):

池

块大小

块数

小计

pool[0] RAW12

5529600 B

2

11.1 MB

pool[1] YUV420

5529600 B

3

16.6 MB

合计

27.7 MB

既然实际有 80 MB,5 块(37.8 MB)应当也能跑,但没有复测。

7.3 初始化顺序缺一不可

sample_comm_sys_init(内含 sys_init + vb_init) → vi_init → vpss_init → venc_init
  • 缺 vi_init → ISP 建 pipe 报 0x80010010

  • 只有 vi_init 没有 vpss_init → xmedia_vi_init 报 0x80010012

  • 缺 venc_init → 建通道报 venc not init 0x80010004

7.4 必须关掉 AE 动态降帧

不设 disable_dynamic_fps 时,AE 会按曝光需求把帧率在 30/25/12 fps 之间反复切。实测开机第一次 --dump 就显示 fps = 2500(25 fps),曝光上限也随之改变。

g_isp_param.isp_info[SCREEN_CAPTURE_VI_PIPE].disable_dynamic_fps = XMEDIA_TRUE;

7.5 相对路径:PQ bin、FPN、模型都按 cwd 解析

程序用到的三个外部文件都是相对当前工作目录的字符串:

文件

相对路径

说明

PQ bin

../scene_auto/sc465sl/sc465sl_linear_isp_vpss_ainr.bin

SDK 源码树里没有这个文件,缺失属正常

FPN

./FPN.bin

需自己生成,缺失只是残留固定图案噪声

AINR 模型

../../tools/.../neuron_network.xmm

只在 sample_ainr 里用

所以从 / 下执行程序会把这些路径解析到根目录,日志里会出现:

当前工作目录:/
PQ bin 导入失败,相对路径:../scene_auto/sc465sl/sc465sl_linear_isp_vpss_ainr.bin
  从 / 解析,实际找的是该目录下的 scene_auto/sc465sl/...

这两个缺失不影响出图,只是 ISP 参数退回 sensor 默认值。要确认是不是文件真的缺,先看日志里打印的「当前工作目录」。

sample_ainr 在错误目录下运行会报:

mmz_mmb_alloc(AIworkspace, 0, 0x0, 0, ) failed!
[sample_ainr_isp_prepare]-279: acquire workspace fail

注意 mmz_mmb_alloc(AIworkspace, 0, ...) 里的 0 是申请大小。模型没加载成功,workspace 大小算出来是 0,于是分配失败。这不是内存不足,把目录切对就好。

7.6 图像方向:ainr 正、拍照倒,已在 VPSS 层修正

实测现象:两个程序在三层的 mirror/flip 默认值全是 FALSE,但 sample_ainr 推流画面是正的,screen_capture 存的 JPEG 上下颠倒。

唯一差异是链路模式:ainr 走 viproc_vpss_mode = ONLINE(ISP 直连 VPSS),screen_capture 走 OFFLINE(经 DDR 中转)。

修正方式是在 VPSS 输出通道补 180°,只变换 YUV 数据,不动 sensor 寄存器,因此不影响 bayer 相位和 AE/AWB 统计:

g_vpss_config.pipe_info[SCREEN_CAPTURE_VPSS_PIPE].chn_info[chn].chn_config.mirror_en = XMEDIA_TRUE;
g_vpss_config.pipe_info[SCREEN_CAPTURE_VPSS_PIPE].chn_info[chn].chn_config.flip_en   = XMEDIA_TRUE;

验证方法:对着播放字幕的显示器拍一张,字幕应该显示在画面底部且正读。修正前字幕会跑到顶部且倒置。

7.7 AE 锁定的两个坑

坑一:刚起流时查询返回全 0

准备写入 AE:exp_time=33000 again=0 dgain=0 isp_dgain=0 sizeof_attr=324
again=0 小于下限 1024(Q10 定点,1024 为 1.0 倍)

AE 是起流之后才逐帧收敛的,isp_start 紧接着查会拿到初始值。程序里改成轮询等待。

坑二:again=1024 也是初始值,会误判为已收敛

AE 已稳定(等待 50 ms):exp_time=29069 again=1024 dgain=1024 isp_dgain=1024

等 50 ms 只跑了 1~2 帧,1024 恰好等于驱动初值,判据 again >= 1024 立刻通过。真实收敛值可能要 12003,按 1024 锁下去画面会暗 11.7 倍。

现在的判据是「相邻两次采样的 exp_time / again / dgain 完全一致」,每 200 ms 采一次,超时 5 秒。isp_dgain 不参与判据——它在 1036/1037 之间来回抖 1 个 LSB,带上它永远等不到完全一致。

正常等待时间 400~1600 ms。

7.8 其他

  1. VI/ISP 独占:screen_capture、sample_ainr、sample_venc、sample_vio 同时只能跑一个。切之前先 killall,用 SIGTERM 不要 -9。

  2. 别往 /mnt/sd 之外写图:save_path 是目录,路径写错会直接 fopen 失败。

  3. Ctrl-C 打断时最后一张可能是坏文件:select 被信号打断后库函数仍返回成功,会留下一个空或残缺的 JPEG。收工后检查最后一张的大小。

  4. 降噪会吃细线:ISP 的 TNR/BNR 在暗场景强度很高,可能把 1~2 px 宽的亮线当噪声抹掉。要调就整份读回 xmedia_isp_bnr_manual_attr 再改字段写回,不要凭空构造结构体。

  5. 白平衡别让它飘:采集全程不要改显示器色温或相机 AWB。

  6. 相机必须上三脚架:摩尔纹对位移极敏感。

  7. 显示器侧四件事:刷新率切 60 Hz、关 HDR/局部调光/动态对比度、关播放器补帧插帧、全屏播放并隐藏光标。

  8. 摩尔纹怎么压:按效果排序——镜头前贴描图纸 > 轻微失焦 > 调整距离让屏幕明显欠采样 > 相机偏转 5° 斜拍。先试第一招,成本几块钱。


八、跑完之后

  • 三项全过 → 输入信号合格,进入标注与训练流程

  • 任意一项不过 → 先解决那一项。不要在画质未达标时开始标注,标完还要重标

下一步(标注、训练、模型转换、板端 NPU 部署)见《[[AI 模型训练与板端部署验证]]》。

[!warning] 边界 这套流程跑通,只证明「能在屏摄场景拍到视频里的流星」。不能外推到真实夜空——真实夜空噪底高 1~2 个数量级,流星细到 1 px,还有卫星、飞机、萤火虫干扰。 屏摄素材只能用于验证链路通不通,不能拿来训练模型(域差异见另一篇文档)。