无界 15X Pro 在 Linux 下 Gaze 人脸识别重启后失效:Sunplus RGB/IR 摄像头冷启动预热方案
本文由 AI 根据我的实际排查过程、系统日志和实机测试整理生成,技术结论与命令均来自本次实际故障排查。
最近在 无界 15X Pro 上配置 Linux 人脸识别时,碰到了一个很奇怪的问题:Gaze、PAM、RGB 摄像头、IR 摄像头和人脸模板看起来都正常,刚配置好时也能刷脸,但只要整机重启,Gaze GUI、sudo、KDE 锁屏和登录界面就会一起无法识别人脸。
摄像头本身又没有直接报错:RGB 能打开,IR 能打开,日志里也有红外画面,但最后始终是 matched=false。
一路排查后发现,问题并不在 Gaze 模型、PAM、PipeWire、模板文件,也不在 USB autosuspend,而很可能是无界 15X Pro 上这颗 SunplusIT RGB + IR 复合摄像头的冷启动初始化问题:整机冷启动以后,需要先让 RGB 摄像头真正工作几秒,IR 才会进入正常状态。
至于鸡哥到底从什么犄角旮旯找来的这块摄像头模组,我不清楚;但至少在 Linux 下,它确实需要额外伺候一下。
这篇文章主要记录完整排查过程和最终 workaround,方便以后自己重装系统时直接抄作业,也给碰到同款机器、同款摄像头的人一个参考。
说明:目前能够确认这个问题在我的无界 15X Pro 上稳定复现。是否所有无界 15X Pro 都使用完全相同的摄像头模组、是否所有批次都会出现同样行为,我没有足够样本,因此本文把它当作特定硬件组合的兼容性问题,而不是直接断言所有同型号机器都有这个 bug。
环境
出问题时的主要环境:
- 机器:无界 15X Pro
- Linux:CachyOS / Arch 系
- KDE Plasma + Wayland
- Gaze:0.2.12
- RGB/IR 摄像头:SunplusIT Inc
- USB VID:PID:
2b7e:b663 - RGB:
/dev/video0 - IR:
/dev/video2
用下面的命令可以看到设备:
v4l2-ctl --list-devices
本机输出对应关系为:
HD Webcam: HD Webcam
/dev/video0
/dev/video1
HD Webcam: IR Camera
/dev/video2
/dev/video3
进一步检查:
v4l2-ctl -d /dev/video0 --all
v4l2-ctl -d /dev/video2 --all
RGB 节点支持 MJPG / YUYV,IR 节点则是:
Card type : HD Webcam: IR Camera
Width/Height : 640/360
Pixel Format : GREY
症状
Gaze 的配置和录入都看起来正常:
gaze doctor
可以得到类似:
21 passed, 0 warnings, 0 errors
人脸模板也同时包含 RGB 和 IR。
刚录入完成时:
gaze auth
可以正常:
Face Verified.
但是整机重启以后,登录、锁屏、sudo、Gaze GUI 全部开始失败。
日志里摄像头其实都能正常打开:
VerifyStart: sensing faces ... run_rgb=true run_ir=true
Attempting to open GStreamer camera: v4l2src device=/dev/video0 ...
Attempting to open GStreamer camera: v4l2src device=/dev/video2 ...
IR stream produced a lit frame: mean_luma=41
VerifyStart: frame budget spent matched=false
这个现象一开始很容易把排查方向带偏,可能的原因包括:
- 模板保存后重新加载坏了;
- Gaze daemon 开机启动太早;
- PipeWire / WirePlumber 会话没有准备好;
/dev/video*重启后换号;- IR emitter 没打开;
- USB autosuspend 把摄像头搞坏了。
这些方向后来基本都逐一排查过。
先排除 PipeWire
最早我的 Gaze 使用 PipeWire source:
[cameras]
rgb = "pipewiresrc target-object=...1.0"
ir = "pipewiresrc target-object=...1.2"
系统日志里又刚好能看到 gazed 比桌面用户的 PipeWire 更早启动,因此最开始很容易怀疑是启动时序问题。
为了完全绕开 PipeWire,我最后改成直接访问 V4L2:
[cameras]
rgb = "/dev/video0"
ir = "/dev/video2"
emitter_enabled = false
parallel_capture = "never"
Arch / CachyOS 上如果缺少 v4l2src,还需要安装:
sudo pacman -S gst-plugins-good
检查:
gst-inspect-1.0 v4l2src | head
切到 V4L2 后,RGB 和 IR 都能稳定打开,但整机重启后仍然无法识别。
结果切换以后问题依旧存在,因此 PipeWire 不是最终根因。
最关键的线索:IR 亮度完全不同
真正有价值的线索来自 gazed 日志里的 mean_luma。
重启后连续失败时:
IR mean_luma ≈ 37 ~ 46
而之前能够正常刷脸的时候:
IR mean_luma ≈ 113 ~ 170
例如一次明确成功的认证:
RGB face region: mean_luma=100
IR stream produced a lit frame: mean_luma=170
AUTH_EXIT=0
摄像头、模板和 Gaze 配置都没有变化,唯一明显不同的是 IR 画面亮度。
这时候基本可以确认,matched=false 只是最终表现:冷启动后的 IR 摄像头并没有进入录入模板时的正常工作状态。
emitter_enabled 没有解决
Gaze 支持:
emitter_enabled = true
理论上可以主动控制部分 Windows Hello 摄像头的 IR emitter。
但在这颗 2b7e:b663 Sunplus 摄像头上,打开它以后仍然只有:
IR stream produced a lit frame: mean_luma=37
所以至少在我这台机器上,这个开关没有解决问题。
最后还是恢复:
emitter_enabled = false
IR gain 也不是问题
IR 节点暴露了 gain:
v4l2-ctl -d /dev/video2 --list-ctrls-menus
范围大约是:
gain: min=100 max=1500
我依次测试了:
100
300
500
800
1200
结果 IR mean_luma 依旧只有 37~41,全部识别失败。
所以它也不是一个简单的 IR 增益过低问题。
USB autosuspend 也被排除了
摄像头默认允许 runtime autosuspend:
/sys/bus/usb/devices/1-1/power/control = auto
runtime_status = suspended
autosuspend_delay_ms = 2000
我尝试让 IR 摄像头保持连续工作 8 秒:
timeout 8s gst-launch-1.0 -q \
v4l2src device=/dev/video2 ! \
videoconvert ! fakesink
此时 USB 已经是:
runtime_status = active
但紧接着人脸认证仍然失败:
IR mean_luma=46
matched=false
因此仅仅让 USB 保持唤醒并不能解决问题。
真正的复现:先预热 RGB,马上恢复
真正找到突破口,是因为我回看了一次“什么都没改,但 Gaze GUI 突然又能验证”的日志。
时间线上刚好发生了一个重要动作:我打开 Gaze GUI 准备新增人脸。Gaze GUI 的 enrollment 流程会先打开摄像头做本地预览,然后再把摄像头释放给 daemon。
于是我单独做了一个实验:什么都不改,只让 RGB 摄像头先跑 5 秒。
timeout 5s gst-launch-1.0 -q \
v4l2src device=/dev/video0 ! \
videoconvert ! fakesink
然后立刻:
gaze auth
结果认证立即成功。到这里基本可以确认,问题确实和摄像头冷启动后的硬件状态有关。
日志从之前的:
IR mean_luma ≈ 40
AUTH_EXIT=1
变成:
RGB face region: mean_luma=88
IR stream produced a lit frame: mean_luma=117
AUTH_EXIT=0
也就是说:
冷启动
↓
直接开 IR
↓
IR 状态异常,亮度约 40
↓
Gaze 无法匹配
而:
冷启动
↓
先让 RGB 真正 stream 几秒
↓
Sunplus 复合摄像头进入正确状态
↓
再开 IR
↓
IR 亮度恢复 100+
↓
Gaze 正常认证
这也解释了之前那个看起来很奇怪的现象:打开 Gaze GUI 的录入页面后,问题会像“自己好了”一样消失。实际上不是玄学,而是 GUI 的录入流程顺手完成了 RGB 预热。
预热一次就够了
为了确认是不是每次认证前都要等 5 秒,我又做了一个测试。
RGB 预热成功以后,等待 USB 再次进入:
runtime_status = suspended
然后再次执行 gaze auth。
结果依然:
AUTH_EXIT=0
IR mean_luma=113
这说明这个初始化状态不会因为普通 runtime suspend 再次丢失。一次预热后,至少这一轮开机周期内都能保持正常。
这意味着不需要:
- 禁用 USB autosuspend;
- 每次刷脸前延迟 5 秒;
- 让摄像头一直常亮;
- 写一个后台守护进程不停占用摄像头。
只需要每次整机冷启动时预热 RGB 一次。
最终 workaround:在 gazed 启动前预热 RGB 5 秒
这里不建议直接修改发行版提供的:
/usr/lib/systemd/system/gazed.service
因为包更新会覆盖它。
使用 systemd drop-in:
sudo mkdir -p /etc/systemd/system/gazed.service.d
新建:
/etc/systemd/system/gazed.service.d/10-camera-warmup.conf
内容:
[Service]
ExecStartPre=-/usr/bin/timeout 5s /usr/bin/gst-launch-1.0 -q v4l2src device=/dev/video0 ! videoconvert ! fakesink
然后:
sudo systemctl daemon-reload
sudo systemctl restart gazed
检查:
systemctl cat gazed
应该能看到:
# /etc/systemd/system/gazed.service.d/10-camera-warmup.conf
[Service]
ExecStartPre=-/usr/bin/timeout 5s /usr/bin/gst-launch-1.0 -q v4l2src device=/dev/video0 ! videoconvert ! fakesink
这里 ExecStartPre= 前面的 - 是故意的。
timeout 5s 到时间后通常以非 0 状态结束;前面的 - 告诉 systemd 即使预热命令因为 timeout 返回非 0,也继续启动 gazed。
为什么把预热放在 gazed 前面
本机启动日志中,摄像头枚举顺序为:
19:03:11.785 uvcvideo 找到 RGB
19:03:11.917 uvcvideo 找到 IR
19:03:12.697 gazed 启动
所以当 ExecStartPre 开始时,/dev/video0 已经存在。
最终启动流程就变成:
uvcvideo 枚举 RGB / IR
↓
准备启动 gazed
↓
ExecStartPre 打开 /dev/video0 约 5 秒
↓
Sunplus 摄像头完成冷启动初始化
↓
gazed 正式启动
↓
SDDM / KDE / sudo 调用 Gaze
↓
RGB + IR 正常认证
相比单纯延迟整个 gazed 的启动,这个写法更明确:不是“等 5 秒看看会不会好”,而是主动让 RGB sensor 工作,把摄像头初始化到正确状态。
当前 Gaze 摄像头配置
最终保留的相关配置为:
[cameras]
rgb = "/dev/video0"
ir = "/dev/video2"
emitter_enabled = false
parallel_capture = "never"
如果你的 /dev/video* 对应关系不同,请不要直接照抄编号,先用:
v4l2-ctl --list-devices
确认 RGB 和 IR 节点。
验证修复是否生效
不重启机器时,可以先:
sudo systemctl restart gazed
sleep 1
gaze auth
在我的机器上,应用预热后重新启动 Gaze,日志为:
RGB face region: mean_luma=100
IR stream produced a lit frame: mean_luma=170
认证成功:
AUTH_EXIT=0
真正要验证冷启动问题,最终还是应该整机 reboot 后在 SDDM / KDE 锁屏或 gaze auth 中测试。
总结
这个问题最麻烦的地方在于,所有表象看起来都很像软件问题:
Gaze 能打开摄像头
IR 也有画面
模板也存在
PAM 正常
DBus 正常
doctor 全绿
但就是 matched=false
结果最后兜了一圈,问题还是回到了这颗摄像头模组本身:
无界 15X Pro 上这颗 SunplusIT
2b7e:b663RGB/IR 复合摄像头,在 Linux 冷启动后需要先让 RGB sensor stream 一段时间,IR 才进入和正常录入状态一致的工作模式。
最终 workaround 非常简单:
每次开机
→ gazed 启动前
→ RGB 预热 5 秒
→ 后续整次开机周期内正常使用
如果以后 Gaze、uvcvideo 或内核针对这颗摄像头补上正确的初始化 quirk,这个 drop-in 就可以删除。在那之前,开机时让 RGB 预热 5 秒,至少比关闭 autosuspend、长期占用摄像头或者放弃 IR 验证更干净。