Hi there 👋

Welcome to my blog, my name is Ray. I am a software developer and this is my personal blog where I share my thoughts, experiences, and projects. Feel free to explore and connect with me!
杂技浅尝

人脸识别为什么要眨眼还要闪各种颜色的光

今天办业务需要人脸识别,拿起手机把我的小胖脸放入框中眨眨眼,等待灯光闪烁,随后就是通过,这个已经见怪不怪了,偶尔还要张张嘴,左右摇头,我知道他这是在确认我是不是真人,但是哪怕我做过简单的人脸识别,我也没有深究过人脸活体检测具体有多少方法,以及闪光是在干嘛,本文对此做了调研和解释。 差不多在 23 年有一个小项目,要做离线人脸识别来替换百度人脸识别 API。没太多性能要求,我搜刮来一个 facenet-retinaface-pytorch 仓库,扒拉两下就集成进系统了。 这个仓库只需要把人脸向量存起来,再把新照片转换成向量做对比。底层不用懂也能跑通,后来需求搁浅,我也就没有继续深究。 首先,人脸识别是怎么实现的 不展开神经网络和数学公式,整个过程可以粗略分成三步。 摄像头拿到画面后,先找到人脸的位置,把脸裁出来并校正角度;接着模型把五官、轮廓和纹理压成一串数字,也就是人脸向量;最后,把这个向量和之前保存的向量计算相似度。 相似度超过阈值,系统就认为两张脸可能属于同一个人。阈值越严格,冒认更少,但真人也更容易被拒绝。 所以,人脸识别主要回答的是“这张脸像谁”,而像谁并不能代表眼前真的就是谁。一张照片、一段视频,照样能提取出很像的人脸向量。 为什么要做人脸活体检测以及怎么做 当然,不做活体检测的话,拿张照片或者播放一段视频就可能通过人脸比对。它要补上的问题是:镜头前的东西,究竟是现场真人,还是照片、屏幕、面具或被替换的视频流。 来点专业词汇,活体判断可能会组合随机挑战、RGB 纹理与时序、深度或红外、材质与光反射、可信采集链路和服务端风控。一下子就懵了,还是先从我最早的两个问题说起。 眨眨眼是干嘛的 最早、最直观的用途,是排除一张不会动的静态照片。系统连续观察眼睛,如果看到“睁开—闭合—再次睁开”的时序,就认为镜头前至少不是一张完全静止的普通照片。 系统通常会在每一帧里定位眼角和上下眼睑,计算眼睛高度与宽度的比例。这个数值短时间内从高变低、再变高,就很像一次眨眼。现代模型可能直接输出眼睛开合概率,原理差不多。1 如果系统临时提示“现在眨眼”,它还构成了一次简单的挑战—响应:系统刚发出要求,用户随后作出动作。这比静静等待一次自然眨眼,多了一点“正在发生”的证据。 但它的证明力有限。视频中的人也会眨眼,挖掉照片的眼睛区域也能模拟眨眼,实时换脸则可以直接跟随攻击者的动作。眨眼能对付普通照片,却不能单独对付视频重放。2 屏幕颜色变化是干嘛的 在典型的活体认证实现里,手机屏幕被临时当成了一个可编程光源。系统随机生成一串颜色、亮度或图案,让它们依次照到脸上,同时让前置摄像头记录脸部反射。颜色不是主要让人去识别、记住或回答;真正“回答问题”的是脸对光的即时反射。 它想核对三件事: 时间是否对得上。 本次认证临时生成的随机颜色序列,相当于一次性口令。脸上的颜色变化必须紧跟本次序列,不能是过去录好的一段固定画面。 空间形状是否像人脸。 鼻尖、眼窝、额头、面颊朝向不同,受到同一块屏幕照明时会出现不同的明暗分布。普通照片或显示屏是近似平面,反射分布不同。 材料是否像皮肤。 皮肤、纸张、玻璃屏和硅胶面具的反光方式不一样,模型会尝试找出这些差异。 2018 年的 Face Flashing 研究系统化提出了这种做法:屏幕随机闪现颜色或图案,再分析脸部反射的时间、纹理和三维形状。3 AWS 的商用活体服务也提供“面部移动 + 彩色光序列”的模式。4 当然,屏幕变色也可能兼有曝光、白平衡校准,甚至只是界面效果。只看页面闪了几下,无法确定某个 App 到底用了哪些模型。 如果我用一段眨眼视频会怎么样 答案是:有可能,但取决于系统检查到了哪一层。 只检查有没有眨眼: 预录视频本来就有“睁—闭—睁”,存在通过的可能。 还检查屏幕重放痕迹: 系统会寻找摩尔纹、屏幕刷新、玻璃反光、二次压缩和平面运动,视频就没那么容易过。 再加随机光、深度或红外: 固定视频不知道本次颜色序列,普通屏幕也缺少真实人脸深度,通过难度会进一步提高。 还有一种更危险的情况,是不拿屏幕对着摄像头,而是直接用虚拟摄像头或修改 App 的相机画面。这已经不只是活体算法的问题,还需要系统确认视频确实来自可信的真实摄像头。 最后 人脸识别回答“这张脸像谁”,眨眼检查“画面里的脸有没有做动作”,随机变色则检查“这张脸有没有立刻响应本次随机光”。可靠的系统不会只押宝其中一项,而是把多种线索放在一起判断。 Soukupová, T.; Čech, J., Real-Time Eye Blink Detection using Facial Landmarks, 2016. https://vision.fe.uni-lj.si/cvww2016/proceedings/papers/05.pdf ↩︎ A Survey on Anti-Spoofing Methods for Facial Recognition with RGB Cameras of Generic Consumer Devices, Journal of Imaging, 2020. https://doi.org/10.3390/jimaging6120139 ↩︎ ...

July 17, 2026 · 1 min · 117 words · Ray
杂技浅尝

vLLM + Qwen3.6 视频推理踩坑记录

vLLM + Qwen3.6 视频推理踩坑记录:AV1、OpenCV 与视频采样问题 Info 之前 qwen3.6-27b 模型发布后没多久我就部署他 并且测试过视频能力,一直是公司内部署的主力离线大模型。 所以我认为一直可用,但是今天同事提了个新的需求,要从视频中找出属于广告部分的内容裁剪掉。我首先给出的方案是 用 Qwen3-ASR+Qwen3-ForcedAligner 模型来获取 文本内容以及时间轴,再用 LLM 来判断哪些文本属于广告内容,随后根据文本所对应的时间轴裁剪视频,先这样子给同事搭建了环境让他去跑几个试试,同时我来试试直接让 qwen3.6 读视频是否可行。结果一来就给我个报错 环境 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Model: Qwen3.6-27B BF16 vLLM: 0.19.1 Transformers: 5.6.0 Python: 3.12 4*RTX 3090 24GB 显示已折叠代码(18 行) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 启动参数: vllm serve /data/huggingface_model/Qwen/Qwen3.6-27B \ --served-model-name qwen3.6-27b \ --port 13539 \ --tensor-parallel-size 4 \ --max-model-len 65536 \ --reasoning-parser qwen3 \ --enable-auto-tool-choice \ --tool-call-parser qwen3_coder \ --gpu-memory-utilization 0.90 \ --max-num-seqs 4 \ --enable-prefix-caching \ --enable-chunked-prefill \ --disable-custom-all-reduce \ --allowed-local-media-path /<指定目录> \ --media-io-kwargs '{"video":{"num_frames":64}}' \ --mm-processor-kwargs '{"fps":1,"do_sample_frames":false}' 这里的 --media-io-kwargs 是给 vLLM 多模态视频读取器传额外参数的。video.num_frames=64 表示整段视频送进模型前目标采样 64 帧,不是每秒采样 64 帧。比如一个 10 秒、25 FPS 的视频,原本大约有 250 帧,最后会在整段视频的时间范围内均匀取 64 帧,平均下来大约相当于 6.4 FPS。 ...

July 16, 2026 · 3 min · 606 words · Ray
什么是什么

什么是 CUDA:用 Java 世界的类比看懂 Driver / Toolkit / PyTorch / Conda

AI 开发配置 conda / cuda / pytorch / nvidia-driver 很容易混淆,让人头疼,实际上踩过几次坑就能摸清它们的概念和关系了。本文尝试用 Java 开发者熟悉的语言,对照解释几个常见概念。 GPU 世界 Java 世界 作用 GPU 硬件 CPU 真正执行计算 NVIDIA Driver JVM(HotSpot) 控制硬件、执行程序 CUDA Toolkit JDK 给开发者写程序用 nvcc javac 编译器 libcudart.so Java Runtime Library 运行时库 cuBLAS/cuDNN Netty、Apache Commons 等第三方库 提供高性能计算能力 PyTorch Spring Boot 应用框架 conda SDKMAN / Maven + 环境管理 管理版本 下面一点一点讲。 第一层:GPU Driver(最重要) 先理解一件事:GPU 本身不会执行 CUDA C++。 GPU 最终执行的是一种叫做 SASS 的机器码。 整个过程实际上是: ...

July 14, 2026 · 4 min · 697 words · Updated: July 15, 2026 · Ray
人间报错

chatgpt 你不打算 chat 了吗

自从更新 chatgpt 和 codex 之后,我手贱的把 chatgpt classic 给卸载了,现在找不回来了,我怀念我的 option+space openai 是不打算继续服务普通 chat 用户了吗,或者应该把那个功能恢复吧 2026 年 7 月 14 日 21:35:28 哦哦 找到解决方法了,mac 用户可以直接用 homebrew 安装回来 brew install --cask chatgpt-classic 2026 年 7 月 14 日 21:47:06

July 14, 2026 · 1 min · 36 words · Ray
什么是什么

什么是序列化和反序列化

理论上 这是基础中的基础,但是说实话越是基础越是不容易去细看,我一直只有模糊的概念,比如以Java为例,一个对象返回给前端是用的 json,对象->json 的过程就是序列化,json 到对象都过程就是反序列化,但是具体是对错与否,帮我进行这个操作的函数、框架下面是不是还有更多内容,却从未深究,这次水一篇博客来讲一讲这是什么意思 定义 互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是一个四层协议,而OSI模型却是七层协议模型。在OSI七层协议模型中展现层(Presentation Layer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象–这两个功能就是序列化和反序列化。一般而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,展示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。本文对序列化协议的讲解主要基于OSI七层协议模型。 序列化: 将数据结构或对象转换成二进制串的过程 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程 这里摘自 美团技术团队 15 年的文章,我也就从这里开始引入吧 通俗比喻 假设你家有很多家具:沙发、电视、冰箱、床…… 搬家的时候,你得把它们拆开、装进箱子,贴上标签 —— 这就是序列化。 到了新家,打开箱子、按照标签重新组装、恢复成原来的样子 —— 这就是反序列化。 由浅入深 现在从最常见的序列化协议入手,先是 json ,这个在现代系统里面最常见了,打开web 也好,网络请求很多都是用 json 传输内容的。那么 who is ‘Json’ json 1 2 3 4 { "name": "Ray", "age": 25 } 大概就长这样,如果我把它放到 Java 对象里面 它可以是这样的 java 1 2 3 User user = new User(); user.setName("Ray"); user.setAge(25); 这个 user 实际上存在于内存中。 而 Java 内存里是怎么存的? 栈(Stack)里只保存引用:user → 0x1008 真正的数据在堆(Heap)里面: 1 2 3 4 5 6 7 8 9 10 11 12 13 0x1008 (User 对象) ┌──────────────────────────────┐ │ Object Header │ │ klass pointer │ │ │ │ name ───→ 0x2010 │ │ age = 25 │ └──────────────────────────────┘ 0x2010 (String 对象) ┌─────────────┐ │ value="Ray" │ └─────────────┘ ...

July 9, 2026 · 2 min · 341 words · Updated: July 10, 2026 · Ray