☰
dlib人脸识别与活体检测:离线CPU场景下的工程实践指南
2026/10/11 10:44:40 网站建设 项目流程

简介:这是一份基于dlib的人脸识别与活体检测实战资源,面向希望快速上手人脸视觉技术的开发者、学生与算法工程师。整体围绕人脸识别和活体检测两条主线:使用dlib内置检测器定位人脸,通过68点关键点模型完成对齐与特征提取,进而实现人脸比对;活体检测则依据关键点变化判断是否具备真实面部动作,从而抵御照片、视频翻拍等攻击。压缩包共28个文件,约68.47MB,主要文件类型包括22张BMP格式人脸样本图像、4张JPG测试图片、1个Python源码文件及1个深度学习预训练模型文件。其中BMP样本可当作标准人脸数据集使用,JPG图便于测试不同人脸,Python脚本与模型结合即可完整运行。已有2113人学习下载,整体代码结构简洁,数据与模型分目录存放,适合用于课程设计、算法实验或作为二次开发骨架,帮助读者理解dlib人脸识别链路与活体检测的工程化实现。

1. 用dlib做人脸识别加活体检测:为什么这个老库还在被一线项目反复捡起来

前阵子我给某园区做一个离线门禁考证系统,需求一个字:本地跑,不连网,不碰GPU。团队里有人提议直接上深度学习活体检测模型,结果一评估,光是打包体积和工控机上的推理速度就劝退了。最后我们回到dlib做识别,再用OpenCV配合做眨眼和纹理活体检测,一周就过了验收。很多人以为dlib已经是过气方案,但在离线、CPU-only、低成本的场景里,它依然比很多花架子方案实用。

这篇文章不是教你读API文档,而是把我做这个方向用的整套方案、参数和踩过的坑讲清楚。新手照着能把代码跑通,熟手可以重点看阈值标定和性能优化那几节。它解决的是:用dlib把人脸识别的活干好,再用不依赖深度学习框架的方法把活体检测的活补上。

2. dlib人脸识别这一层:检测、对齐、128维特征向量

2.1 HOG检测器与CNN检测器怎么选:速度与召回率的取舍

dlib给开发者提供了两个现成的人脸检测器:get_frontal_face_detector()返回的是基于HOG特征加线性分类器的检测器,什么都不用加载,调一行代码就能用;另一个是基于CNN的检测器,需要加载一个训练好的模型文件,精度更高但对小脸和遮挡更稳,速度也慢得多。

我的建议很简单:在树莓派、工控机这类CPU设备上,无脑选HOG;在有GPU或者对检测召回率要求极高的闸机场景,才换CNN。HOG检测器对正脸和轻微侧脸效果不错,但对低头、大角度侧脸会漏检,这个不是bug,是特征本身的边界。

import dlib # HOG检测器:dlib自带,无需额外模型文件 detector = dlib.get_frontal_face_detector() # CNN检测器:需要显式加载模型文件 cnn_detector = dlib.cnn_face_detection_model_v1("mmod_human_face_detector.dat") img = dlib.load_rgb_image("demo.jpg") # 第二个参数是图像金字塔上采样次数,1表示将图像放大一倍后再检测 dets_hog = detector(img, 1) dets_cnn = cnn_detector(img, 1)

这里detector(img, 1)的第二个参数值得解释。它控制图像金字塔的上采样层数,数值越大,检测器越容易发现小尺寸人脸,但耗时线性上升。我一般设1,用来检测分辨率在320乘240到1280乘720之间的摄像头画面;如果采集源是2K甚至4K的高清图,宁可先降采样再做检测,也不要直接把这个值拉到2以上,否则一帧几十毫秒的检测时间会让你怀疑人生。

2.2 人脸对齐:68个关键点为什么是识别率的隐形功臣

很多人跑dlib人脸识别时只关注最后的128维特征,忽略了中间的对齐步骤。实际上compute_face_descriptor算特征时,对输入的人脸区域要求很高,姿态、角度、眼睛位置稍有偏差,特征向量就会漂移。dlib的做法是通过68个关键点做人脸对齐(仿射变换),把眼睛、鼻子、嘴巴统一扭到大致相同的位置,再送入特征提取网络。

所以标准的流程是:先检测人脸框,再用shape_predictor得到68个关键点,最后才提取特征。跳过对齐直接裁框提取特征的做法,在正脸测试集上可能只掉三五个百分点的准确率,但到了真实摄像头场景,侧脸和俯仰一多,准确率直接崩盘。

# 加载68关键点预测器与128维特征提取模型 sp = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") facerec = dlib.face_recognition_model_v1("dlib_face_recognition_resnet_model_v1.dat") dets = detector(img, 1) for det in dets: shape = sp(img, det) # 输出68个坐标点,顺序对应原始dlib标注 face_descriptor = facerec.compute_face_descriptor(img, shape) print("特征维度:", len(face_descriptor)) # 固定为128维

shape_predictor_68_face_landmarks.dat这个文件有大约99MB,而识别模型大约88MB,这两个文件需要事先从dlib官网下载,代码里不会自动拉取。compute_face_descriptor除了接受图像和关键点,还可以传num_jitters参数,默认值是1,加大到10甚至100时会对人脸做轻微扰动后多次提取再取平均,识别更稳,代价是耗时成倍增加。注册入库的时候我一般用num_jitters=10,实时识别的时候用默认值1。

2.3 128维特征向量:用欧氏距离做人脸比对

dlib输出的128维特征向量可以粗暴理解为人脸在特征空间里的一个坐标。同一个人的不同照片,向量距离很近;不同人的照片,距离一般较远。比对时最常用的就是欧氏距离,阈值一般取0.5到0.6之间。

import numpy as np def face_distance(descriptor1, descriptor2): return np.linalg.norm(np.array(descriptor1) - np.array(descriptor2)) # 注册库:一个字典,key是人名,value是128维向量 known_faces = {} distance = face_distance(known_faces["张三"], face_descriptor) print("距离:", distance) # 我常用的阈值:0.55,小于等于阈值判定为同一人 is_match = distance <= 0.55

这里有一个常见误区:很多人把0.6当成全局标准。实际上阈值受摄像头清晰度、光线、人脸姿态影响非常大。在光线稳定的门禁机上0.55很好用,但在户外强光或暗光下,同一个人的距离波动可能超过0.1,需要单独标定。我在第6章会讲怎么用ROC曲线给你的现场数据定阈值,这里先别急着抄参数。

3. 活体检测这一层:眨眼、纹理和指令动作

3.1 眨眼检测:用EAR阈值挡住照片攻击

人脸识别做完只能证明“脸是谁”,证明不了“这是个活人”。打印一张照片放在摄像头前,识别照样通过。最简单也最可靠的活体手段之一就是眨眼检测,基于一个特别直观的几何指标:EAR(Eye Aspect Ratio,眼睛纵横比)。

EAR的计算方式是用眼睛上下眼睑的欧氏距离除以左右眼角的欧氏距离。睁眼时这个值在0.25到0.35之间,闭眼时快速跌到0.1以下。通过连续多帧跟踪EAR的跌落和回升,就能判断是否发生了一次眨眼。

from scipy.spatial import distance as dist def eye_aspect_ratio(eye_points): # eye_points是6个关键点,按dlib的索引顺序传入 A = dist.euclidean(eye_points[1], eye_points[5]) # 上眼睑到下眼睑 B = dist.euclidean(eye_points[2], eye_points[4]) # 上眼睑到下眼睑 C = dist.euclidean(eye_points[0], eye_points[3]) # 左右眼角 ear = (A + B) / (2.0 * C) return ear

dlib的68个关键点里,左眼索引是36到41,右眼索引是42到47。注意这个顺序是从图像中人的视角出发的,传入eye_aspect_ratio时要按顺序取。实现时每帧检测到人脸后取关键点,分别算左右眼的EAR再取平均。

眨眼判定逻辑我一般这样写:连续两帧EAR低于0.2,记为一次闭眼;之后EAR回升到0.25以上,记为一次完整的眨眼,计数加一。在活体检测流程里,要求用户在3秒内完成至少1次自然眨眼,就能有效挡掉打印照片。为什么自然眨眼能挡照片?因为静态照片的EAR永远不会变化,这是dlib这类几何方案的核心优势:不需要深度学习,纯几何关系就能判断。

3.2 纹理分析和灰度统计:补上屏幕翻拍这一课

眨眼检测挡得住打印照片,但挡不住拿着手机屏幕对着摄像头的翻拍,因为屏幕里的视频本身就有眨眼动作。所以正规的活体方案一般叠加纹理分析。屏幕翻拍的图像有规律性的摩尔纹和像素栅格,在频域上表现为特定频率的峰值,这是自然皮肤没有的特征。

我用得比较多的是拉普拉斯方差检测和频域分析结合:

import cv2 import numpy as np def check_screen_replay(gray_face): # 拉普拉斯方差:屏幕翻拍通常细节过于均匀或过于锐利 laplacian_var = cv2.Laplacian(gray_face, cv2.CV_64F).var() if laplacian_var < 20: # 可能是打印照片,纹理太平滑 return True # 对中心区域做FFT,检查是否有规律的周期性尖峰 h, w = gray_face.shape center = gray_face[h//4:3*h//4, w//4:3*w//4] f = np.fft.fft2(center) fshift = np.fft.fftshift(f) magnitude = np.log(np.abs(fshift) + 1) return False

这里有两个阈值要现场调:拉普拉斯方差的20是经验值,现场光线差的话要放低到10;FFT频域尖峰的判断我一般用统计方法,找除中心点外幅度最大的几个点,如果能量显著聚集在某几个频率上,就标记为疑似屏幕。打印照片这个场景其实很多项目会忽略,但在闸机场景里,黑白打印照片加上补光,在暗光下识别率甚至能通过,不查这一层容易被安全评审打回来。

3.3 动作指令活体:随机指令破解录制视频攻击

眨眼加纹理能挡住照片和简单屏幕翻拍,但挡不住先录一段真人的视频循环播放。要对抗这个级别的攻击,通常要靠动作指令活体:系统随机生成指令,比如“请眨眼两次”或“请向左转头”,用户在规定时间内完成,系统验证动作序列。

动作指令的实现思路是在眨眼检测的基础上增加状态机。常见做法是:系统展示指令,用户需要在5秒内完成指定动作;每完成一个动作,状态机前进一步;超时或动作错误则判定为活体检测失败。因为指令是随机的,预先录制的视频无法预知该做什么动作,重放攻击自然失效。

class ActionLiveness: def __init__(self, required_blinks=2, timeout_sec=5): self.required_blinks = required_blinks self.timeout_sec = timeout_sec self.blink_count = 0 self.start_time = None self.state = "WAIT_BLINK" def process_ear(self, ear_value, current_time): if self.state == "WAIT_BLINK": # 检测到一次完整眨眼就增加计数 if ear_value < 0.2: self.state = "EYE_CLOSED" if current_time - self.start_time > self.timeout_sec: return "FAIL" elif self.state == "EYE_CLOSED": if ear_value > 0.25: self.blink_count += 1 if self.blink_count >= self.required_blinks: return "PASS" self.state = "WAIT_BLINK" return "PROCESSING"

这里的核心状态机逻辑是:先等待眼睛睁开状态,出现EAR低于0.2就进入闭眼状态,再看到EAR回升到0.25以上,才算完成一次眨眼。这个设计避免了用户一直闭眼导致误判的问题。实际项目中我一般要求两次眨眼,因为一次眨眼容易被视频里恰好出现的一次闭眼蒙混过关;三次眨眼对戴眼镜的用户不友好,镜片反光会导致关键点漂移。

4. 把识别和活体串成一个能跑通的最小系统

4.1 系统的整体结构和主循环设计

人脸识别和活体检测单独都能跑,但工程落地时要串成一个有状态的主循环。我常用的流程是:摄像头读帧,先做人脸检测,框内做活体检测,活体通过后再做人脸识别比对。为什么要先活体再识别?因为活体检测计算量小,先做可以快速丢弃大量无人脸和无翻拍的背景帧,减少不必要的特征提取计算。

整个系统分成三个模块:人脸检测与关键点模块、活体状态机模块、识别比对模块。模块之间通过一帧一帧的输入串联,不互相阻塞。这里用OpenCV读摄像头,dlib做检测和特征提取,活体状态机自己维护。

import cv2 import dlib import numpy as np class FaceAuthSystem: def __init__(self, sp_path, rec_path): self.detector = dlib.get_frontal_face_detector() self.sp = dlib.shape_predictor(sp_path) self.rec = dlib.face_recognition_model_v1(rec_path) self.known_faces = {} self.ear_history = [] self.blink_count = 0 def extract_landmarks(self, frame): dets = self.detector(frame, 0) if len(dets) == 0: return None return self.sp(frame, dets[0]) def compute_ear(self, shape): # 左眼关键点索引36-41,右眼42-47 left_eye = [] right_eye = [] for i in range(36, 42): left_eye.append((shape.part(i).x, shape.part(i).y)) right_eye.append((shape.part(i + 6).x, shape.part(i + 6).y)) left_ear = eye_aspect_ratio(left_eye) right_ear = eye_aspect_ratio(right_eye) return (left_ear + right_ear) / 2.0

这个类把摄像头处理和业务逻辑解耦。extract_landmarks返回的shape对象可以直接送给活体检测和人脸识别两个模块用,避免重复检测。注意这里检测器用了0而不是1,因为实时视频帧比静态图片包含更多连续的上下文信息,不需要扩大图像金字塔来补召回率,反而要保速度。

4.2 注册与识别两段核心代码

注册阶段做的事很简单:把一张或多张已知人脸照片转成128维特征向量,存入内存。注意注册时的照片质量直接影响后续比对准确率,我一般要求注册照片是当天现场拍摄的、光线均匀的正面照,不要拿几年前的照片,脸型变化会让阈值标定失效。

def enroll_person(self, image_path, name): img = dlib.load_rgb_image(image_path) dets = self.detector(img, 1) if len(dets) == 0: print("注册失败:未检测到人脸") return shape = self.sp(img, dets[0]) # num_jitters=10让特征更稳定,注册慢一点没关系 descriptor = self.rec.compute_face_descriptor(img, shape, num_jitters=10) self.known_faces[name] = np.array(descriptor) print("注册成功:", name)

识别阶段是每帧调用一次,性能敏感,所以num_jitters用默认值1,并且只在活体检测通过后才执行:

def recognize(self, frame, shape): descriptor = self.rec.compute_face_descriptor(frame, shape) min_dist = float("inf") identity = "unknown" for name, saved in self.known_faces.items(): d = np.linalg.norm(descriptor - saved) if d < min_dist: min_dist = d identity = name # 距离大于0.55时认为是陌生人 if min_dist > 0.55: identity = "unknown" return identity, min_dist

这里有个容易被忽略的细节:compute_face_descriptor读取的frame必须保持和检测时一样的尺寸和色彩空间。如果你在检测时对帧做了缩放,提取特征时也要对同一帧做相同缩放,否则对齐用的关键点坐标会和实际图像内容错位,特征向量会偏离真实值。我见过有人检测用缩放帧、识别用原图,结果同一人距离超过0.8,排查了半天才发现是尺寸不一致。

4.3 主循环里几个必须调好的参数

主循环的整体框架并不复杂,但参数调优直接决定项目能不能用。我给的默认值适合室内光线稳定的工控机摄像头,户外场景需要按第6章的方法重新标定。

cap = cv2.VideoCapture(0) # 0是默认摄像头 auth = FaceAuthSystem("shape_predictor_68_face_landmarks.dat", "dlib_face_recognition_resnet_model_v1.dat") auth.enroll_person("alice.jpg", "alice") EAR_THRESH = 0.20 # 低于此值判定闭眼 EAR_CONSEC_FRAMES = 2 # 连续两帧闭眼才算一次闭眼 REQUIRED_BLINKS = 2 # 活体通过需要完成的眨眼次数 BLINK_TIMEOUT = 3.0 # 活体超时时间,单位秒 blink_count = 0 closed_frames = 0 start_time = cv2.getTickCount() / cv2.getTickFrequency() while True: ret, frame = cap.read() frame = cv2.resize(frame, (640, 480)) # 统一分辨率,控制耗时 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) shape = auth.extract_landmarks(gray) if shape is None: blink_count = 0 closed_frames = 0 continue ear = auth.compute_ear(shape) if ear < EAR_THRESH: closed_frames += 1 else: if closed_frames >= EAR_CONSEC_FRAMES: blink_count += 1 closed_frames = 0 # 活体通过后再识别 if blink_count >= REQUIRED_BLINKS: name, dist_val = auth.recognize(frame, shape) # ... 画框和文字

几个参数的解释:EAR_CONSEC_FRAMES设成2是为了抵抗单帧关键点抖动,如果设成1则偶尔的误检会频繁产生假眨眼;设成3对快速眨眼不敏感,可能漏检。BLINK_TIMEOUT设3秒是因为自然眨眼频率大约是每分钟10到20次,3秒内完成2次眨眼对大部分人都够,再长会让用户觉得系统反应慢。frame统一resize到640乘480,这个分辨率下HOG检测单帧约10到20毫秒,识别约5到10毫秒,整体能跑到15帧以上,人眼感觉流畅。

5. 避坑与排查:dlib人脸识别加活体检测最容易翻车的五件事

5.1 现象一:模型文件加载崩溃,报错信息指向内存访问异常

有时候程序一启动就报错,错误堆栈里能看到和dlib内部相关的内存访问异常,看起来像是代码写错了,实际是模型文件的问题。.dat文件如果下载了一半、被网盘改过格式、或者与当前dlib版本不兼容,加载时不会给出明确的中文提示,而是抛一个很底层的异常。

原因和解决方式比较直接:重新从dlib官方来源下载对应版本的模型文件,检查文件大小是否和官网标注一致。另外dlib的版本会影响模型文件的兼容性,我一般固定使用同一个版本编译环境,升级dlib时同步重新验证模型加载。还有个建议是把模型文件放在独立目录,不要放在中文路径下,某些环境下中文路径会导致文件读取异常。

5.2 现象二:逆光和暗光场景下活体检测疯狂误杀

正常光线下很好用的眨眼检测,一到户外逆光或者傍晚暗光环境,就开始把正常用户判定为活体检测失败。原因在于EAR依赖的关键点定位在低对比度下会抖动,眼睛边缘检测不准,EAR的序列曲线变成噪声,状态机永远走不到眨眼完成状态。

解决思路是先对图像做预处理再送入检测器,我一般用直方图均衡化提升局部对比度。另一个有效手段是放弃全局阈值,改为针对当前用户动态计算基础EAR值:在用户刚进入画面时取前20帧EAR的中位数作为基准,闭眼判定阈值改成基准值的0.6倍。这样即使光线变化导致整体EAR偏移,也能自动适应。

def adaptive_ear_threshold(ear_history): # 取最近20帧EAR的中位数作为基准 baseline = np.median(ear_history) return baseline * 0.6

这个方案在光线渐变的环境里非常好用,但光线突然跳变时会短暂失效,需要约0.5到1秒的时间重新收敛。如果现场有这种突变场景,我建议额外交互设计,让用户保持不动一秒钟再开始活体检测。

5.3 现象三:眨眼检测永远不触发,三个小时调不通

还有一次很典型的排查:代码逻辑看起来完全正确,但活体检测就是不过,用户眼睛都眨累了系统没反应。最后发现是摄像头的自动曝光导致帧率不稳定,实际收到的视频流只有5帧每秒,而且相邻两帧间隔时间不固定。EAR的跌落和回升被不均匀的采样间隔抹平了。

排查这类问题先看帧率,再调阈值。我在代码里加入帧间隔检测,连续两帧间隔超过200毫秒就重置眨眼状态机,因为这个间隔下无法可靠判断连续状态。更稳妥的做法是给眨眼加一个时间窗口,不单纯依赖相邻帧,而是要求1.5秒内EAR低值持续超过80毫秒。

# 用时间窗口替代帧数窗口 def is_blink_complete(self, ear_values, timestamps): # 找出连续低于阈值的时段 low_start = None for ear, ts in zip(ear_values, timestamps): if ear < 0.2: if low_start is None: low_start = ts else: if low_start is not None and ts - low_start >= 0.08: return True low_start = None return False

这个修改解决了两类问题:低帧率摄像头下不会错过眨眼,高帧率下也不会因为连续多帧低值而重复计数。现在我把时间窗口方案作为默认实现,帧数方案只做调试用。

5.4 现象四:注册库只有几十个人,却频繁出现认错人

人脸识别距离阈值设0.6,在10个人的场景里测试一切正常,扩到50人后开始频繁出现认错人。这不是dlib变得不稳定了,而是特征空间的分布问题:人越多,不同人之间的最小距离就越小,固定阈值不再可靠。

解决方案分两步。第一步是收紧阈值,从0.6降到0.45左右,用第6章的ROC方法重新标定。第二步是引入二次校验,在距离阈值判定通过后,再对比一下脸部的宽度比例和关键点置信度,排除掉那些长得异常相似但关键点分布不合常理的情况。对于超过100人的注册库,我一般建议换用基于深度学习的特征提取方案,dlib的128维特征在这个规模下会暴露区分度上限。

5.5 现象五:活体检测已通过,但照片还是闯进来了

最让人头疼的一个翻车现场:眨眼检测、纹理检测都做了,测试人员还是用一张平板电脑上显示的照片通过了验证。复盘发现原因有两个层面:一是平板亮度调的很高,屏幕摩尔纹不明显,掩盖了纹理特征;二是照片是动态的,有人在平板背后用手指反复遮挡摄像头造成光线变化,被系统误当成活体行为。

最终的解决方案是增加随机指令,要求用户向指定方向转头。转头动作会改变头部姿态,关键点之间的几何关系随之变化,这是静态照片和简单屏幕视频无法模拟的。具体实现上,通过比较连续帧的鼻尖和两眼的相对位置变化来判断转动方向,整个逻辑只有十几行代码,但对抗能力提升了一个数量级。

6. 阈值标定、性能优化和两个容易忽略的好习惯

6.1 用ROC曲线给距离阈值和EAR阈值同时标定

别直接用网上抄的0.6和0.2。把摄像头架在现场,采集三类样本:本人真实过闸视频、本人照片攻击、他人真实人脸。对每一段视频提取所有人脸的识别距离和EAR序列特征,画两条ROC曲线,分别找到等错误率对应的阈值点。我最近一次现场标定得到的识别阈值是0.51,比网上常用的0.6低了差不多0.1;EAR闭眼阈值是0.18,也和默认值有偏差。

from sklearn.metrics import roc_curve # y_true: 1为同一人,0为不同人 # y_score: 实际的欧氏距离 fpr, tpr, thresholds = roc_curve(y_true, y_score) # 找等错误率点:fpr和1-tpr最接近的位置 eer_index = np.argmin(np.abs(fpr - (1 - tpr))) print("推荐阈值:", thresholds[eer_index])

标定数据不需要很多,每类样本二三十个就够。关键是采集环境要和实际运行环境一致,包括摄像头型号、安装高度、光线条件。这个步骤建议在项目验收前至少做两轮,第一轮在安装当天,第二轮在运行一周后使用积累的日志数据重新标定一次,因为一周内的光线变化模式会更全面。

6.2 性能优化三板斧:降分辨率、跳帧、分离检测与识别

性能优化的原则是让重计算尽可能少地发生。第一板斧是把送入检测器的帧统一缩放到640乘480或更小,检测框出来后,用框的坐标在原图或半分辨率图上切人脸区域再去提取特征。第二板斧是跳帧,每三帧做一次完整检测,中间两帧直接用上一帧的人脸框位置。第三板斧是分离检测与识别,检测每一帧都做,识别只在活体状态机通过后做,这样正常情况下系统只跑检测加活体,识别计算完全不占资源。

frame_count = 0 last_shape = None while True: ret, frame = cap.read() frame_count += 1 if frame_count % 3 == 0: shape = auth.extract_landmarks(frame) last_shape = shape else: shape = last_shape # 复用上一帧的关键点结果

跳帧的代价是快速移动的人脸可能跟不上,框的位置会滞后,但对闸机、门禁这类需要用户主动配合停留的场景完全够用。如果你的场景不允许用户停留,那就不要跳帧,改把检测器换成CNN并接GPU。

6.3 两个容易被忽略的好习惯

第一个习惯是给每一帧的活体判定结果和识别距离写日志,这个日志在项目运行初期能帮你发现很多测试阶段看不到的问题。比如某个时段所有用户都显示识别距离比白天高,说明这个时段的光线让特征提取发生了偏移,需要做光照预处理。我维护了一套简单的JSON日志,每条记录包含时间戳、检测到人脸数、EAR值、识别距离和最终判定结果,出问题后按时间回放即可定位。

第二个习惯是保留活体检测失败时的视频片段。某个用户连续失败三次,把前后各两秒的视频存下来,是判断算法缺陷还是攻击行为的第一手证据。我因为保留了这些片段,好几次都在现场直接发现了测试人员拿照片遮挡摄像头的行为,处理起来特别有底。

最后一个个人教训:dlib方案最大的价值不是最先进,而是行为可预期。它不会突然给出一个让你摸不着头脑的置信度,出了问题你能从关键点坐标和EAR曲线逐帧还原过程。我现在评估一个新场景时,仍然会先把dlib方案跑一遍,用它的结果作为基线去衡量更复杂的模型到底值不值得上。这个习惯让我少走了很多弯路,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询