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三个判断点:
现象 | 含义 |
|---|---|
| 挂载成功 |
| 没挂上, |
| 同上,程序还在卡里没挂出来 |
挂载失败时看启动日志:
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/sd2.2 推流预览(调角度)
SD 卡里那套黑光夜视程序可以当取景器。必须先 cd 到程序所在目录,它按相对路径加载 NPU 模型:
cd /mnt/sd/ainr/sample/ainr
./sample_ainr 0PC 上用 VLC 打开:
rtsp://192.168.137.53:554/livestream/0调好角度后立刻停掉,它和拍照程序不能同时跑:
killall sample_ainr
sleep 3三点注意:
末尾的
0是场景模式(单路线性 ainr 15fps)。不要
kill -9。强杀会留下未释放的 ISP pipe 和一片MMB LEAK,下次启动直接报0x80010010,只能重启。在别的目录直接跑会报
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 采集节奏怎么选
程序支持三种节奏,按用途选:
用途 | 命令 | 实测表现 |
|---|---|---|
找流星帧 |
| 全速连抓,27 fps,约 37 ms 一张 |
常规留档 |
| 2 张/秒,文件量小 |
长时间铺量 |
| 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/sdsync 必须先执行,否则 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 文件位置
文件 | 路径 | 说明 |
|---|---|---|
源码 |
| UTF-8 with BOM |
编译脚本 |
| 与 |
板上位置 |
| 整目录随 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三个要点:
source只对当前 shell 生效,关掉终端要重来。报
command not found就是没 source,不是容器不对。4 MB 左右的体积是正常的。程序用
-Wl,-Bstatic静态链接了全部 xmedia 库,strip 只能去掉符号表,代码段去不掉。想再小要加-Wl,--gc-sections,但有误删构造函数的风险,跑通前别动。编译带
-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
----------------------读数规则:
字段 | 单位 | 判读 |
|---|---|---|
| 微秒 | 33220 ≈ 1/30 s,正好是 30 fps 的整帧曝光上限,说明场景偏暗、AE 拉满 |
| Q10 定点 | 1024 = 1.0 倍;屏摄显示器实测 1440(1.4 倍);夜间拍星空实测 12003(11.7 倍) |
| 0.01 fps | 3000 = 30.00 fps。若显示 2500 或 1200,说明 AE 动态降帧没关掉 |
| 灰阶 | 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 存到哪,文件名 | 用 |
2 | 间隔毫秒 | 两张之间等多久, | 找流星 |
3 | 曝光时间 | 单位微秒 | 省略即用 AE 自己收敛的值,推荐先省略 |
4 |
| 模拟增益,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 -l5.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判读表:
指标 | 合格 | 需要处理 | 处理办法 |
|---|---|---|---|
行条纹 |
|
| 曝光改成更短;显示器关 HDR/局部调光;相机帧率锁 30 |
星点 |
|
| 加曝光时间或提高 again,一次只动一个 |
均值 |
|
| 过曝降曝光,死黑加曝光 |
判据 3:流星的像素长度
找视频里流星出现的那几秒,在对应时间戳附近的 JPEG 里找那条亮线,量长度:≥ 8 px 合格,< 8 px 换素材。
六、参数怎么调
6.1 曝光时间(最重要的一个)
值(微秒制) | 等价 | 适用 |
|---|---|---|
| 1/30 s | 理论下限,会与刷新周期错拍,不建议 |
| ≈1/30 s | AE 在暗场自动拉到的值,就是满帧曝光 |
| 1/60 s | 屏摄下限,刚好一个刷新周期 |
| 1/100 s | 推荐起点,留余量避开刷新扫描的过渡行 |
| 1/200 s | 屏幕很亮时用,要相应提高增益 |
[!warning] 屏摄不能长曝光 显示器 60 Hz,曝光超过 1/60 s 就会叠加两帧以上的视频画面,流星变成等距重影。 这是屏摄与实拍最本质的区别。
曝光上限由帧周期决定,程序里按 1000000 / fps 现算,超过会被驱动截断:
30 fps → 33333 µs
15 fps → 66666 µs
6.2 间隔
目的 | 间隔 | 说明 |
|---|---|---|
找流星帧 |
| 全速 27 fps,覆盖最密 |
常规采集 |
| 2 张/秒,文件少 |
长时间铺量 |
| 1 张/秒 |
存储实测:2560x1440 JPEG 约 329 KB/张。
节奏 | 速率 | 占用 |
|---|---|---|
| 8.8 MB/s | 31.7 GB/小时 |
| 0.66 MB/s | 2.4 GB/小时 |
| 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 错误码对照表
错误码 | 宏 | 本次触发场景 | 处理 |
|---|---|---|---|
| INVALID_PARAM | 别的进程占着媒体系统时传显式 pipe_mode; | 先停占用进程;增益是 Q10 定点,不能小于 1024 |
| NOT_ENOUGH_MEMORY | VB 池分配时 MMZ 被别的程序占着 | 停掉占用者后重试,不是池子配大了 |
| NOT_INIT | 没调 | 补齐模块级 init |
| EXIST | ISP pipe 0 被占用 | 停掉占用者或重启 |
| NOT_READY | 只 reset 了 VI,没 reset VPSS |
|
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 init0x80010004
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 |
| SDK 源码树里没有这个文件,缺失属正常 |
FPN |
| 需自己生成,缺失只是残留固定图案噪声 |
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 其他
VI/ISP 独占:
screen_capture、sample_ainr、sample_venc、sample_vio同时只能跑一个。切之前先killall,用 SIGTERM 不要-9。别往
/mnt/sd之外写图:save_path是目录,路径写错会直接fopen失败。Ctrl-C 打断时最后一张可能是坏文件:
select被信号打断后库函数仍返回成功,会留下一个空或残缺的 JPEG。收工后检查最后一张的大小。降噪会吃细线:ISP 的 TNR/BNR 在暗场景强度很高,可能把 1~2 px 宽的亮线当噪声抹掉。要调就整份读回
xmedia_isp_bnr_manual_attr再改字段写回,不要凭空构造结构体。白平衡别让它飘:采集全程不要改显示器色温或相机 AWB。
相机必须上三脚架:摩尔纹对位移极敏感。
显示器侧四件事:刷新率切 60 Hz、关 HDR/局部调光/动态对比度、关播放器补帧插帧、全屏播放并隐藏光标。
摩尔纹怎么压:按效果排序——镜头前贴描图纸 > 轻微失焦 > 调整距离让屏幕明显欠采样 > 相机偏转 5° 斜拍。先试第一招,成本几块钱。
八、跑完之后
三项全过 → 输入信号合格,进入标注与训练流程
任意一项不过 → 先解决那一项。不要在画质未达标时开始标注,标完还要重标
下一步(标注、训练、模型转换、板端 NPU 部署)见《[[AI 模型训练与板端部署验证]]》。
[!warning] 边界 这套流程跑通,只证明「能在屏摄场景拍到视频里的流星」。不能外推到真实夜空——真实夜空噪底高 1~2 个数量级,流星细到 1 px,还有卫星、飞机、萤火虫干扰。 屏摄素材只能用于验证链路通不通,不能拿来训练模型(域差异见另一篇文档)。


