OpenCV人脸识别身份认证系统实战:从环境搭建到活体检测
简介这份资源是一套基于 Python 与 OpenCV 的人脸识别身份认证系统完整项目包面向计算机相关专业的毕业设计、课程设计学习者以及希望从零搭建应用级人脸识别引擎的开发者。项目覆盖人脸数据采集、数据预处理、数据分析、模型训练到最终识别认证的完整技术链路代码注释详尽可顺利编译运行。压缩包共 38 个文件约 50.69MB包含 3 个 Python 核心脚本、17 个 xml 配置与分类器文件、5 个 ui 界面文件、4 张 png 图标素材以及 whl 依赖包、db 数据库、yml 配置、wav 音频等目录按 modules、datasets、core、icons、ui 等模块划分结构清晰。目前已有 1291 人学习下载。读者可据此获得一套可直接运行的身份认证系统源码理解人脸检测与识别模块的协作方式并借助现成的界面与数据管理脚本快速完成二次开发或答辩演示。1. 从一张摄像头截图到你是你OpenCV人脸识别身份认证系统到底在做什么很多人第一次做身份认证系统脑子里想的是调个库传张图返回名字真跑起来才发现摄像头光照一变、人脸偏转十几度识别结果就开始飘。这个标题讲的事情本质是把人脸识别从演示级 Demo 做成一个能用于身份认证的闭环系统采集、检测、对齐、特征提取、比对、判定、记录。它解决的不是能不能认出人脸而是能不能稳定地认对特定的人并且拒绝冒用。适合谁正在做计算机毕业设计、课程设计或者想给门禁、考勤、实验室设备加一道人脸认证的 Python 开发者。OpenCV 在这里承担的是图像采集、预处理和人脸检测的主力识别环节常见做法是搭配 face_recognition、InsightFace 或自己训练的嵌入模型。整条链路里OpenCV 是地基不是全部把地基打稳上面的认证逻辑才不会塌。2. 环境搭建与 OpenCV 安装别在第一步就翻车2.1 Python 与 OpenCV 的版本匹配策略做这个系统第一步不是写代码是把环境弄干净。我一般会锁定 Python 3.8 到 3.10 这个区间原因是很多预编译的 OpenCV wheel 和深度学习推理库对 3.11 以上的支持还不齐。安装 OpenCV 最省事的方式是 pip但要注意包名分两种opencv-python是主包opencv-contrib-python带额外模块比如人脸相关的 contrib 算法。如果你只做基础检测和图像处理装前者就够如果后面想用 LBPH 人脸识别器就得装 contrib 版本。# 创建独立虚拟环境避免污染系统 Python python -m venv venv_face # Windows 激活 venv_face\Scripts\activate # Linux / macOS 激活 source venv_face/bin/activate # 安装核心依赖指定版本区间避免自动升级到不兼容版本 pip install opencv-python4.5,4.10 pip install opencv-contrib-python4.5,4.10 pip install numpy pillow逻辑说明虚拟环境是后悔药装崩了直接删目录重来。版本区间卡在 4.5 到 4.10 之间是因为这个区间的 API 稳定网上能查到的资料也最集中。参数上opencv-contrib-python和opencv-python不要同时装不同版本否则会出现cv2模块冲突报ModuleNotFoundError: No module named cv2或者函数找不到。提示如果你在 Ubuntu 上遇到libGL.so.1找不到装libgl1-mesa-glx和libglib2.0-0这两个系统包这是 OpenCV 图形界面依赖不是 Python 包的问题。2.2 验证安装与摄像头连通性装完别急着写业务代码先跑一个最小验证脚本确认 OpenCV 能读摄像头、能显示图像。这一步能帮你排除掉驱动、权限、索引错误三类问题。import cv2 # 打开默认摄像头索引 0 通常是内置摄像头 cap cv2.VideoCapture(0) # 检查摄像头是否成功打开 if not cap.isOpened(): print(摄像头打开失败检查索引或权限) exit() while True: ret, frame cap.read() if not ret: print(读取帧失败) break # 显示原始画面 cv2.imshow(Camera Test, frame) # 按 q 退出 if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明VideoCapture(0)里的 0 是摄像头索引笔记本外接摄像头可能是 1 或 2。cap.read()返回两个值第一个是布尔值表示是否读到帧第二个是帧数据。waitKey(1)里的 1 表示等待 1 毫秒这个值太大画面会卡太小 CPU 占用高。参数上如果画面是黑的但没报错多半是摄像头被其他程序占用或者 Linux 下当前用户不在video组。3. 人脸检测与对齐把找到脸和摆正脸分开做3.1 用 Haar 级联做快速人脸检测OpenCV 自带 Haar 级联分类器优点是快、不需要 GPU、模型文件小适合毕业设计这种算力有限的环境。缺点是侧脸和遮挡场景容易漏检。常见做法是先用 Haar 把脸框出来再做后续处理。import cv2 # 加载 OpenCV 自带的人脸检测模型 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) img cv2.imread(test.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 检测人脸参数需要根据实际场景调 faces face_cascade.detectMultiScale( gray, scaleFactor1.1, # 每次图像缩小的比例 minNeighbors5, # 每个候选框至少被检测到几次才保留 minSize(60, 60) # 最小人脸尺寸过滤噪点 ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imshow(Faces, img) cv2.waitKey(0) cv2.destroyAllWindows()逻辑说明scaleFactor越小检测越细但越慢1.1 是速度和召回率的平衡点。minNeighbors越大误检越少但漏检越多室内考勤场景我一般设 5 到 6。minSize必须设否则背景纹理会被当成脸。参数上如果检测框抖动厉害可以在检测前做一次高斯模糊cv2.GaussianBlur(gray, (5, 5), 0)代价是速度略降。3.2 人脸对齐用眼睛位置做仿射变换检测到脸只是第一步如果人脸是歪的后面提取的特征会不稳定。对齐的常见做法是检测双眼位置计算倾斜角然后旋转校正。OpenCV 自带眼睛检测器可以配合使用。import cv2 import numpy as np eye_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_eye.xml ) def align_face(img, face_rect): x, y, w, h face_rect roi_gray cv2.cvtColor(img[y:yh, x:xw], cv2.COLOR_BGR2GRAY) eyes eye_cascade.detectMultiScale(roi_gray, 1.1, 4) # 至少检测到两只眼睛才做对齐 if len(eyes) 2: # 取前两只眼睛的中心点 eye_centers [(ex ew//2, ey eh//2) for (ex, ey, ew, eh) in eyes[:2]] eye_centers.sort(keylambda p: p[0]) # 按 x 坐标排序区分左右眼 left_eye, right_eye eye_centers # 计算两眼连线角度 dy right_eye[1] - left_eye[1] dx right_eye[0] - left_eye[0] angle np.degrees(np.arctan2(dy, dx)) # 以人脸中心为旋转中心 center (x w//2, y h//2) M cv2.getRotationMatrix2D(center, angle, 1.0) aligned cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) return aligned return img逻辑说明getRotationMatrix2D生成旋转矩阵warpAffine执行仿射变换。角度计算用arctan2而不是arctan因为前者能正确处理象限。参数上旋转后图像边缘会出现黑边如果后续要裁剪人脸区域建议先旋转再裁剪顺序反了会引入无效像素。这一步不是必须的但如果你的识别准确率在侧脸场景下掉得厉害加上对齐通常能拉回几个百分点。4. 特征提取与比对从像素到身份向量4.1 用 LBPH 做轻量级人脸识别LBPH局部二值模式直方图是 OpenCV contrib 里自带的人脸识别器优点是训练快、不需要大量数据、对光照有一定鲁棒性非常适合课程设计。缺点是准确率上限不高人脸库大了容易混。import cv2 import numpy as np import os # 创建 LBPH 识别器 recognizer cv2.face.LBPHFaceRecognizer_create( radius1, # 邻域半径 neighbors8, # 邻域点数 grid_x8, # 水平方向网格数 grid_y8 # 垂直方向网格数 ) def load_faces(data_dir): faces [] labels [] label_map {} current_label 0 for person_name in os.listdir(data_dir): person_dir os.path.join(data_dir, person_name) if not os.path.isdir(person_dir): continue label_map[current_label] person_name for img_name in os.listdir(person_dir): img_path os.path.join(person_dir, img_name) img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: continue # 统一尺寸LBPH 要求输入灰度图 img cv2.resize(img, (100, 100)) faces.append(img) labels.append(current_label) current_label 1 return faces, np.array(labels), label_map faces, labels, label_map load_faces(dataset) recognizer.train(faces, labels) recognizer.save(trainer.yml)逻辑说明radius和neighbors控制 LBP 算子的感受野半径越大捕捉的纹理越粗通常 1 到 2 够用。grid_x和grid_y把图像分成网格分别统计直方图8x8 是常见起点。参数上训练数据每人至少 10 张不同角度和光照各来几张否则识别器会过拟合到某一种表情。label_map是必须的因为 LBPH 只认整数标签你得自己维护标签到姓名的映射。4.2 用深度嵌入模型提升准确率如果 LBPH 的准确率满足不了答辩要求可以换成深度嵌入模型。常见做法是用 face_recognition 库底层是 dlib或者 InsightFace提取 128 维或 512 维特征向量然后算欧氏距离或余弦相似度。import face_recognition import numpy as np # 加载已知人脸并编码 known_encodings [] known_names [] def register_face(image_path, name): img face_recognition.load_image_file(image_path) encodings face_recognition.face_encodings(img) if len(encodings) 0: print(f{name} 未检测到人脸) return known_encodings.append(encodings[0]) known_names.append(name) def verify_face(test_image_path, threshold0.45): img face_recognition.load_image_file(test_image_path) encodings face_recognition.face_encodings(img) if len(encodings) 0: return 未检测到人脸 # 计算与所有已知人脸的距离 distances face_recognition.face_distance(known_encodings, encodings[0]) best_idx np.argmin(distances) best_distance distances[best_idx] # 距离小于阈值才认为是同一个人 if best_distance threshold: return known_names[best_idx] return 陌生人逻辑说明face_encodings返回 128 维向量face_distance默认算欧氏距离。阈值 0.45 是经验值严格场景可以降到 0.4宽松场景可以放到 0.5。参数上注册照片要正脸、光照均匀否则编码质量差后面比对全歪。深度模型比 LBPH 准但依赖 dlib 编译Windows 上装 dlib 经常翻车建议用预编译 wheel 或者直接上 Linux。5. 避坑与排查身份认证系统最常见的 5 个翻车现场5.1 摄像头能打开但画面全黑现象cap.isOpened()返回 True但read()读到的帧全是零。原因摄像头被其他程序占用或者 Linux 下权限不足。解决关掉其他调用摄像头的程序Linux 下把用户加入video组命令是sudo usermod -aG video $USER然后重新登录。5.2 识别结果频繁跳变现象同一个人站在摄像头前识别结果在本人和陌生人之间反复横跳。原因单帧判定没有做平滑光照或姿态微小变化导致距离值在阈值附近波动。解决连续取 5 到 10 帧统计判定结果取众数作为最终输出。或者对距离值做滑动平均再和阈值比较。5.3 训练时标签错乱现象LBPH 训练完A 的脸被识别成 B。原因labels数组和faces数组顺序不一致或者label_map映射写反。解决加载数据时用同一个循环同时 append 图像和标签不要分开遍历。训练前打印labels和label_map核对一遍。5.4 阈值设死导致误拒或误认现象本人经常被拒或者陌生人偶尔被放行。原因固定阈值不适应不同光照和摄像头。解决注册时采集同一个人的多张照片计算类内距离分布取类内最大距离的 1.2 倍作为初始阈值再根据实际测试微调。不要直接抄网上的 0.6 或 0.4。5.5 程序跑久了内存暴涨现象长时间运行后程序变卡甚至崩溃。原因VideoCapture没有释放或者每帧都创建新的CascadeClassifier。解决分类器在循环外初始化一次循环内只做检测。程序退出前确保cap.release()和cv2.destroyAllWindows()被调用。6. 活体检测与防伪让认证系统不认照片6.1 用眨眼检测做基础活体判断身份认证系统如果只认特征向量拿一张照片就能骗过去。毕业设计里加一个基础活体检测答辩时是加分项。最常见也最容易实现的是眨眼检测用 dlib 的 68 点关键点模型计算眼睛纵横比EAR连续几帧 EAR 低于阈值再恢复判定为眨眼。import dlib import cv2 import numpy as np from scipy.spatial import distance detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) def eye_aspect_ratio(eye_points): # 计算垂直方向两个距离 A distance.euclidean(eye_points[1], eye_points[5]) B distance.euclidean(eye_points[2], eye_points[4]) # 计算水平方向距离 C distance.euclidean(eye_points[0], eye_points[3]) return (A B) / (2.0 * C) # 左眼和右眼在 68 点模型中的索引 LEFT_EYE list(range(42, 48)) RIGHT_EYE list(range(36, 42)) EAR_THRESHOLD 0.25 # 低于这个值认为眼睛闭合 CONSEC_FRAMES 3 # 连续几帧低于阈值才算眨眼 blink_counter 0 frame_counter 0 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) for face in faces: landmarks predictor(gray, face) left_eye [(landmarks.part(i).x, landmarks.part(i).y) for i in LEFT_EYE] right_eye [(landmarks.part(i).x, landmarks.part(i).y) for i in RIGHT_EYE] left_ear eye_aspect_ratio(left_eye) right_ear eye_aspect_ratio(right_eye) ear (left_ear right_ear) / 2.0 if ear EAR_THRESHOLD: frame_counter 1 else: if frame_counter CONSEC_FRAMES: blink_counter 1 frame_counter 0 cv2.putText(frame, fBlinks: {blink_counter}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(Liveness, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明EAR 的原理是眼睛睁开时垂直距离大闭合时垂直距离小。EAR_THRESHOLD设 0.25 是经验值戴眼镜的人可能需要调到 0.2。CONSEC_FRAMES设 3 是为了过滤噪声避免单帧抖动误判。参数上shape_predictor_68_face_landmarks.dat需要单独下载文件大约 100MB不要指望 pip 自动带。活体检测的判定逻辑是先做人脸识别识别通过后再要求用户在 3 秒内眨眼眨眼成功才最终放行。6.2 多帧投票与阈值自适应单帧识别加单次活体仍然可能被视频回放攻击。我一般会再加一层连续 15 帧做识别统计每个身份的命中次数命中率超过 70% 才进入活体环节。活体环节要求随机动作比如请眨眼或请左右转头动作由系统随机指定这样预录视频很难覆盖所有组合。阈值自适应方面不要用全局固定阈值。注册时对每个人采集 20 张照片两两计算距离取类内距离的 95 分位数作为该用户的个人阈值。比对时用个人阈值而不是全局阈值误拒率会明显下降。这个技巧在答辩时讲出来评委能看出你确实调过参。最后说一个我自己的习惯每次改完识别逻辑先拿自己的照片和同事的照片跑一遍混淆矩阵看误拒和误认各有多少再决定调阈值还是换模型。不要凭感觉调参那是玄学。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

OpenCV全景拼接实战:SIFT特征匹配与单应性矩阵详解

OpenCV全景拼接实战:SIFT特征匹配与单应性矩阵详解

简介:面向OpenCV与Python初学者的全景图片拼接实战资源,围绕多张连续照片的无缝合并这一经典计算机视觉任务,给出可直接运行的完整工程。压缩包共8个文件,包含1个Python代码、6张示例图片及1份使用说明,整体约15.48MB&…

2026/10/11 22:47:31 阅读更多 →
恶意网站检测系统实战:SVM、随机森林与DNN三模型融合

恶意网站检测系统实战:SVM、随机森林与DNN三模型融合

简介:这份资源面向计算机、人工智能与电子信息工程等专业的学生及机器学习初学者,提供一套完整的恶意网站检测实践方案,可用于网络安全、信息过滤等场景的教学与实验。压缩包共11个文件,约3.32MB,以Python脚本为主体&a…

2026/10/11 22:46:30 阅读更多 →
Rust异步性能监控与调优:用tokio-console透视Tokio运行时

Rust异步性能监控与调优:用tokio-console透视Tokio运行时

排了将近两周的异步性能问题,最后发现罪魁祸首是一个没有被监控到的同步阻塞调用。这事发生在一次基于 Rust 和 Tokio 的在线服务改造里,接口延迟从 3ms 抖到 800ms,CPU 占用却一直不高,所有日志都显示业务代码执行"正常&quo…

2026/10/11 22:46:30 阅读更多 →

最新新闻

同城家政服务平台搭建,多商户派单方案详解

同城家政服务平台搭建,多商户派单方案详解

同城家政服务平台搭建:多商户入驻与智能派单方案详解同城家政行业早已从单一门店自营模式,转向多商户平台化联营发展。平台整合全城多家家政公司、个体服务商、持证服务师傅,统一承接用户订单,通过智能调度完成订单分发与履约。相…

2026/10/11 23:39:46 阅读更多 →
PDF加密权限解除实战:用qpdf免费命令行一键解锁

PDF加密权限解除实战:用qpdf免费命令行一键解锁

上周同事甩过来一个PDF,说打印店打不了,让我帮忙看看。我一看,文件本身没坏,是加了权限限制——允许查看,但打印和复制都被锁了。这种问题我一年能遇到几十次:文档在手机上看一点毛病没有,真要用…

2026/10/11 23:39:46 阅读更多 →
大数据缓存实战:Redis与Alluxio定位配置与踩坑

大数据缓存实战:Redis与Alluxio定位配置与踩坑

干大数据这行的人,迟早会被一个词拦住:慢。任务跑得慢、查询出得慢、报表刷得慢,追根问底,大多不是因为计算引擎不给力,而是存储访问拖了后腿。我在几个大数据平台的项目里折腾过缓存方案,常用的两样是Redi…

2026/10/11 23:39:46 阅读更多 →
基于蝴蝶优化算法的IEEE30节点无功优化Matlab实现与参数调优

基于蝴蝶优化算法的IEEE30节点无功优化Matlab实现与参数调优

1. 从"网损"到算法:先搞懂无功优化到底在优化什么说到电力系统优化调度,"有功优化"大家都很熟——机组出多少钱、发多少有功,直接影响运行成本。但大部分人第一次接触"无功优化"时都会有一个疑问:无…

2026/10/11 23:39:46 阅读更多 →
四月修复版H5农场养殖鸡蛋理财鸡源码部署与支付对接避坑指南

四月修复版H5农场养殖鸡蛋理财鸡源码部署与支付对接避坑指南

简介:最新修复版H5农场牧场养殖理财鸡游戏运营源码,定位为可直接运营的网站游戏项目,适合有建站基础、希望搭建休闲理财类H5游戏的个人或团队二次开发。资源包共2271个文件,约88.4MB,主体由HTML页面、JavaScript逻辑、…

2026/10/11 23:39:46 阅读更多 →
改进版Q-learning实战:Double Q、n步回报与经验回放

改进版Q-learning实战:Double Q、n步回报与经验回放

简介:基于Q-learning的改进版强化学习算法项目,聚焦路径规划场景,面向MATLAB用户及强化学习入门者。项目针对经典Q-learning收敛慢的问题,融合学习率衰减、动态ε-greedy探索、经验回放、目标网络与双线性更新等改进策略&#xff…

2026/10/11 23:38:45 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →