基于Python和OpenCV的疲劳驾驶检测系统:EAR/MAR算法与源码实战
简介这是一套面向高校计算机、人工智能及相关专业学生的疲劳驾驶检测系统毕业设计完整源码包基于Python与OpenCV实现适合正在准备毕业设计或课程项目的学习者参考与二次开发。资源包共包含22个文件整体约93.53MB涵盖py主程序与检测脚本、xml级联分类器数据、dat模型文件、png与jpg图像素材、html说明页面及txt使用说明等从算法实现到界面展示均有涉及目录结构清晰便于按模块查阅。目前已有1230人学习下载说明其在实际教学场景中具备一定参考价值。该资源已获导师认可并高分通过代码经过测试可正常运行读者可据此理解疲劳检测的核心流程包括人脸与眼部区域定位、特征提取及疲劳状态判定并可直接运行验证效果为毕业设计答辩与后续功能扩展提供扎实基础。1. 从一份 zip 说起疲劳驾驶检测系统到底在检测什么拿到「基于python和opencv的疲劳驾驶检测系统源码全部数据毕业设计.zip」这个标题多数人第一反应是去翻压缩包里有什么但真正决定这套系统能不能跑起来、能不能过答辩的是它背后那条检测链路。疲劳驾驶检测的本质不是「识别一张脸」而是从连续视频帧里提取眼睛和嘴巴的状态再用时间维度上的闭眼、打哈欠频率去判断一个人是不是困了。OpenCV 在这里承担的是图像预处理、人脸检测、关键点定位和可视化Python 负责把这些环节串成一条可调试的流水线。这套东西适合谁一是做计算机毕业设计、需要一套能演示、能改、能写论文的学生二是刚入门 OpenCV 图像处理项目、想找一个完整闭环练手的开发者。它解决的核心问题是把「疲劳」这个模糊概念量化成 EAR眼睛纵横比和 MAR嘴巴纵横比两个可计算的指标再用阈值和帧计数做判定。理解了这条链路源码就不再是黑匣子参数也不再是玄学。2. 疲劳判定的技术底座EAR、MAR 与帧计数怎么配合2.1 为什么不用「闭眼分类」而用纵横比很多人第一版会想训练一个 CNN 做睁眼/闭眼二分类但对毕业设计来说这是过度设计。原因有三第一训练数据要标注工作量翻倍第二分类模型对光照、眼镜、侧脸敏感泛化差第三答辩时讲不清网络结构反而扣分。EAR 的思路完全不同它不判断「这是不是眼睛」而是用眼睛周围 6 个关键点的几何关系算一个比值。EAR 的定义是眼睛垂直方向两组点距离之和除以水平方向点距离的两倍。眼睛睁开时垂直距离大EAR 在 0.25 到 0.35 之间闭眼时垂直距离趋近于零EAR 掉到 0.15 以下。这个方法的妙处在于它是尺度无关的——人脸离镜头远近分子分母同步变化比值稳定。MAR 同理用嘴巴上下唇关键点算张开程度打哈欠时 MAR 会明显高于正常说话。提示EAR 阈值不是固定值不同人眼型差异很大源码里如果写死 0.2换个人就可能误报。后面第 5 章会讲自适应阈值。2.2 关键点从哪来dlib 与 OpenCV 的分工OpenCV 自带的人脸检测器Haar 或 DNN只能给出人脸框给不出眼睛、嘴巴的精确坐标。所以这类项目通常引入 dlib 的 68 点人脸关键点模型其中第 36-41 点是左眼42-47 点是右眼48-67 点是嘴巴。OpenCV 负责读帧、缩放、画框、显示dlib 负责关键点回归两者配合是这套源码最常见的架构。如果你在环境里装 dlib 遇到编译报错一个常见替代是 MediaPipe它的人脸网格模型同样能给出眼睛和嘴巴坐标而且 pip 安装不需要编译。但要注意MediaPipe 的点位编号和 dlib 不同源码里的索引要跟着改否则算出来的 EAR 是错的。这是换库时最容易翻车的地方。2.3 单帧判定为什么不可靠引入帧计数只靠一帧 EAR 低就报警结果就是眨眼瞬间疯狂误报。人正常眨眼大约 100 到 400 毫秒如果视频是 30 帧每秒一次眨眼只占 3 到 12 帧。所以判定逻辑必须是连续 N 帧 EAR 低于阈值才认为是一次闭眼事件连续闭眼累计超过一定时长比如 2 秒才判定为疲劳。这就引出了两个计数器一个是连续闭眼帧数eye_counter一个是打哈欠次数yawn_count。源码里通常还会加一个总疲劳分数闭眼加分、打哈欠加分分数超过阈值就触发警报。这套逻辑不复杂但参数之间的配合决定了系统的可用性下一章直接上代码。3. 把源码跑起来环境、依赖与最小可运行脚本3.1 环境搭建Python 版本与依赖安装这套源码对 Python 版本不挑3.8 到 3.11 都能跑但要注意 dlib 在 3.12 上编译经常出问题稳妥起见用 3.9 或 3.10。安装顺序有讲究先装 numpy 再装 opencv最后装 dlib因为 dlib 编译时依赖 numpy 的头文件。# 建议在虚拟环境里操作避免污染全局 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 source venv/bin/activate # 按顺序安装numpy 必须先于 dlib pip install numpy1.24.3 pip install opencv-python4.8.1.78 pip install dlib19.24.2 pip install scipy imutils这里固定版本不是强迫症而是血泪经验opencv-python 4.9 之后某些 API 有变动dlib 19.24 是最后一个在 Windows 上预编译包比较全的版本。如果你遇到ModuleNotFoundError: No module named cv2八成是装到了另一个 Python 环境里用python -c import sys; print(sys.executable)确认解释器路径。3.2 关键点模型文件放哪、怎么读dlib 的 68 点模型文件shape_predictor_68_face_landmarks.dat大约 99MB压缩包里一般会带。代码里读取时路径写错是最常见的翻车点建议用相对路径加断言检查。import os import dlib import cv2 import numpy as np # 模型路径放在项目根目录的 models 文件夹下 PREDICTOR_PATH os.path.join(models, shape_predictor_68_face_landmarks.dat) # 启动时就检查别等到运行中途才报错 if not os.path.exists(PREDICTOR_PATH): raise FileNotFoundError(f关键点模型不存在: {PREDICTOR_PATH}) detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(PREDICTOR_PATH)get_frontal_face_detector是 dlib 的 HOG 人脸检测器速度比 CNN 版快CPU 上就能实时。shape_predictor加载模型后对每张人脸框返回 68 个坐标点。注意 detector 返回的是 dlib 的 rectangle 对象不是 OpenCV 的坐标画框时要转换。3.3 EAR 与 MAR 的计算函数把几何计算封装成函数是整个源码里最值得单独拎出来讲的部分。下面这段可以直接抄。from scipy.spatial import distance as dist def eye_aspect_ratio(eye_points): # eye_points: 6 个 (x, y) 坐标顺序为左角、上两点、右角、下两点 # 垂直距离上眼睑两点到下眼睑两点 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 def mouth_aspect_ratio(mouth_points): # 用嘴巴上下唇中间点算张开程度 A dist.euclidean(mouth_points[2], mouth_points[10]) B dist.euclidean(mouth_points[4], mouth_points[8]) C dist.euclidean(mouth_points[0], mouth_points[6]) mar (A B) / (2.0 * C) return mardist.euclidean算两点欧氏距离。EAR 公式里分子是两组垂直距离之和分母是水平距离乘 2这个 2 是为了归一化让睁眼时 EAR 落在 0.3 附近方便设阈值。MAR 用的是嘴巴 20 点模型里的索引如果你用的是 68 点模型嘴巴是 48-67索引要相应平移。参数说明eye_points必须是 6 个点且顺序正确顺序错了 EAR 会算出离谱的值这是新手最容易忽略的。3.4 主循环从读帧到报警的完整链路把上面拼起来就是一个最小可运行版本。下面这段代码去掉了界面美化只保留判定逻辑方便你理解主干。EYE_AR_THRESH 0.25 # 闭眼阈值 EYE_AR_CONSEC_FRAMES 3 # 连续多少帧算一次闭眼 YAWN_THRESH 0.6 # 打哈欠阈值 COUNTER 0 # 连续闭眼帧计数 TOTAL_BLINKS 0 # 闭眼事件总数 YAWN_COUNT 0 # 打哈欠次数 FATIGUE_SCORE 0 # 疲劳总分 cap cv2.VideoCapture(0) # 0 是默认摄像头也可换成视频文件路径 while True: ret, frame cap.read() if not ret: break frame cv2.resize(frame, (640, 480)) gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector(gray, 0) for face in faces: shape predictor(gray, face) # 把 dlib 关键点转成 numpy 坐标 coords np.array([(shape.part(i).x, shape.part(i).y) for i in range(68)]) left_eye coords[36:42] right_eye coords[42:48] mouth coords[48:68] ear (eye_aspect_ratio(left_eye) eye_aspect_ratio(right_eye)) / 2.0 mar mouth_aspect_ratio(mouth) if ear EYE_AR_THRESH: COUNTER 1 else: if COUNTER EYE_AR_CONSEC_FRAMES: TOTAL_BLINKS 1 FATIGUE_SCORE 1 COUNTER 0 if mar YAWN_THRESH: YAWN_COUNT 1 FATIGUE_SCORE 2 # 疲劳判定 if FATIGUE_SCORE 20: cv2.putText(frame, FATIGUE ALERT, (150, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3) cv2.imshow(Fatigue Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明每帧先检测人脸再取关键点算 EAR 和 MAR。闭眼时累加COUNTER睁眼时如果之前连续闭眼超过阈值帧数就记一次眨眼并加疲劳分。打哈欠直接加 2 分。FATIGUE_SCORE超过 20 触发警报。参数怎么调EYE_AR_THRESH从 0.25 开始试戴眼镜的人可能要降到 0.2EYE_AR_CONSEC_FRAMES设 3 是防止单帧噪声设太大漏检YAWN_THRESH0.6 是经验值张嘴说话可能误触发后面会讲怎么区分。4. 避坑与排查这套源码最容易翻车的五个地方4.1 摄像头读不到帧或画面全黑现象cap.read()返回 False或者窗口一片黑。原因通常是摄像头被其他程序占用或者VideoCapture(0)的索引不对。解决先换索引试 1 或 2再检查是否有其他软件会议、直播工具占着摄像头。Linux 下还要确认当前用户有/dev/video0的读权限用ls -l /dev/video*看。4.2 EAR 值始终异常闭眼睁眼没区别现象打印出来的 EAR 一直在 0.5 以上或者一直低于 0.1。原因几乎都是关键点索引取错了或者坐标顺序不对。解决把coords[36:42]画到图上用cv2.circle标出来肉眼确认这 6 个点是不是正好在眼睛上。如果用的是 MediaPipe索引完全不同必须查它的官方点位图重新映射。4.3 打哈欠误报说话也被算成哈欠现象正常说话时YAWN_COUNT疯涨。原因是 MAR 只看嘴巴张开程度说话时嘴也会张。解决加一个持续时间条件嘴巴张开必须连续超过 15 帧才算一次哈欠说话的张合通常更快。另外可以把 MAR 阈值提到 0.65 以上减少误触发。4.4 帧率不稳导致计数失真现象同一段视频有时报警有时不报。原因是EYE_AR_CONSEC_FRAMES是按帧数算的但摄像头帧率会波动30fps 和 15fps 下同样 3 帧代表的时间差一倍。解决改用时间戳计算闭眼持续时长用time.time()记录闭眼开始时刻超过 0.4 秒才算一次闭眼事件这样和帧率解耦。4.5 打包成 exe 后模型文件找不到现象源码里跑得好好的用 PyInstaller 打包后报模型文件不存在。原因是打包后工作目录变了相对路径失效。解决用sys._MEIPASS定位打包资源目录或者把模型文件路径改成基于os.path.dirname(os.path.abspath(__file__))的绝对路径。这是毕业设计答辩现场演示时最尴尬的翻车提前测一遍。5. 让检测更稳自适应阈值与多指标融合的进阶做法5.1 用前 3 秒校准个人基线固定阈值最大的问题是因人而异。一个简单有效的改进是程序启动后前 3 秒不判定只采集用户正常睁眼状态下的 EAR 均值然后把这个均值的 70% 作为闭眼阈值。这样戴眼镜、眼型小的人也能适配。calibration [] CALIB_FRAMES 90 # 约 3 秒 # 在主循环里前 90 帧只采集不判定 if len(calibration) CALIB_FRAMES: calibration.append(ear) cv2.putText(frame, Calibrating..., (200, 240), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2) continue else: if EYE_AR_THRESH is None: baseline np.mean(calibration) EYE_AR_THRESH baseline * 0.7 print(f校准完成基线 EAR{baseline:.3f}阈值{EYE_AR_THRESH:.3f})CALIB_FRAMES设 90 是按 30fps 算的 3 秒如果摄像头帧率低就相应减少。baseline * 0.7这个系数是经验值0.65 到 0.75 之间都可以试。校准期间要提示用户保持正常睁眼别眨眼否则基线偏低。5.2 把眨眼频率和 PERCLOS 一起看单看闭眼时长还不够真正有说服力的是 PERCLOS单位时间内闭眼时间占比。它的定义是统计窗口内闭眼帧数除以总帧数。正常驾驶 PERCLOS 低于 0.2疲劳时超过 0.4。把 PERCLOS 和打哈欠次数一起作为判定依据比单纯累加分数更符合学术规范答辩时也更好讲。指标正常范围疲劳预警计算方式EAR0.25-0.350.2 持续关键点几何比值MAR0.3-0.50.6 持续嘴巴张开程度PERCLOS0.20.4闭眼帧数/总帧数眨眼频率15-20 次/分10 或 30单位时间闭眼事件这张表可以直接放进论文的指标章节。注意 PERCLOS 的统计窗口一般取 30 秒或 60 秒窗口太短波动大太长反应迟钝。5.3 一个我踩过的坑别在循环里反复加载模型早期版本我把dlib.shape_predictor写在了 while 循环里结果帧率掉到 5fps还以为是电脑不行。模型加载是磁盘 IO 加内存分配必须放在循环外只做一次。同理cv2.CascadeClassifier也不要每帧新建。这个习惯养成后同样的硬件帧率能翻好几倍。5.4 验证方法用录制视频代替真人测试调试时别一直对着摄像头累且不可复现。用手机录一段自己正常、眨眼、打哈欠、低头四种状态的视频存成 mp4把VideoCapture(0)改成文件路径逐段跑打印 EAR 和 MAR 曲线。这样能快速定位阈值问题也方便截图放进论文。我一般会录 30 秒一段跑完看报警时间点对不对比对着镜头反复试高效得多。这套源码的价值不在于它多完美而在于它是一条完整的、可拆解、可替换的链路。你可以把 dlib 换成 MediaPipe把 EAR 换成 CNN把单摄像头换成红外但「提取指标、时间维度判定、阈值校准」这个骨架不会变。把校准和 PERCLOS 加上答辩时就有了讲不完的细节。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

macOS多版本JDK切换实战:JAVA_HOME与PATH配置全解析

macOS多版本JDK切换实战:JAVA_HOME与PATH配置全解析

我印象特别深,有一次把一个老项目拉下来,心里想着“反正我电脑上装了新 JDK,跑个 Web 项目还不是分分钟”。结果程序一启动,控制台直接抛编译错误,翻来覆去查代码却没问题。后来才发现:终端里那个 java 命令…

2026/10/11 15:35:08 阅读更多 →
终端安全最怕“慢半拍”:9项实时能力如何抢回主动权

终端安全最怕“慢半拍”:9项实时能力如何抢回主动权

一、前言:为什么"实时"是终端安全的胜负手 在终端安全领域,有一个残酷的现实:攻击是实时的,而传统的安全检查是滞后的。 传统企业终端安全管控多依赖T1静态巡检、日报周报复盘的被动模式,存在极大的防护时差…

2026/10/11 15:35:08 阅读更多 →
使用10年的日产油车,想换一辆30万左右性价比较高的纯电新能源汽车,有哪些推荐?

使用10年的日产油车,想换一辆30万左右性价比较高的纯电新能源汽车,有哪些推荐?

文章目录一、先定换车前提(决定买不买纯电)二、30万左右纯电推荐(分场景)1)家用SUV、求省心稳妥:首选Model Y、小鹏G6/G92)家用大空间、重舒适:理想i6、小米YU7、极氪7X3&#xff09…

2026/10/11 15:34:08 阅读更多 →

最新新闻

小区物业管理系统源码毕设实战:从架构到部署避坑全指南

小区物业管理系统源码毕设实战:从架构到部署避坑全指南

简介:这是一套小区物业管理系统的完整源码包,附带毕业论文,适合需要开发同类管理系统的程序员、计算机相关专业学生作为毕业设计或项目参考。资源包共含125个文件、大小约1.8MB,其中20个asp文件构成前台与后台核心功能&#xff0c…

2026/10/11 16:21:35 阅读更多 →
35个MCP工具大揭秘:Blitz如何让任意AI客户端完整驱动App Store Connect

35个MCP工具大揭秘:Blitz如何让任意AI客户端完整驱动App Store Connect

【免费下载链接】blitz-mac Native macOS App Store Connect tool with MCP. Submit iOS apps to App Store with AI agents 项目地址: https://gitcode.com/gh_mirrors/bl/blitz-mac 点击查看 免费下载 Blitz 是一款 macOS 原生应用,让 Claude Code、…

2026/10/11 16:21:35 阅读更多 →
鼠标宏科普:G502压枪宏的Lua脚本原理与DPI灵敏度调参指南

鼠标宏科普:G502压枪宏的Lua脚本原理与DPI灵敏度调参指南

简介:一份面向FPS玩家的罗技G502鼠标压枪宏配置包,适用于CS:GO、PUBG、Apex等全自动武器频繁交战的射击场景。资源共4个文件、约38KB,其中macro-G502.lua为核心宏脚本,提供可导入G-Hub的按键序列;G502-config.xml为预置…

2026/10/11 16:21:35 阅读更多 →
餐饮采购系统数据库建设:从Word清单到可计算结构化数据

餐饮采购系统数据库建设:从Word清单到可计算结构化数据

简介:本资源是一份面向餐饮信息化系统开发者、数据库设计人员及供应链管理从业者的专业文档,聚焦餐饮食品采购系统中核心的原材料清单数据库建设方案。文档系统梳理了蔬菜类食材的标准化编码体系(如VG/Asparagus, Green Large)、规…

2026/10/11 16:21:35 阅读更多 →
电信系下场开源,PaddleOCR 还坐得住吗:国产文档解析的版图要重排

电信系下场开源,PaddleOCR 还坐得住吗:国产文档解析的版图要重排

电信系下场开源,PaddleOCR 还坐得住吗:国产文档解析的版图要重排 【免费下载链接】TeleOCR 项目地址: https://ai.gitcode.com/XingChen-AGI/TeleOCR 2026 年 8 月,一个最初以 "NaviDC-OCR" 为名的模型静悄悄放出权重与技术…

2026/10/11 16:21:35 阅读更多 →
Oracle EBS R12月结关账顺序与子账对账SQL实战指南

Oracle EBS R12月结关账顺序与子账对账SQL实战指南

简介:本资源是一份面向Oracle EBS R12财务实施顾问、系统运维人员及财务信息化从业者的专业培训课件,聚焦财务月结核心流程与实操要点,系统解决多模块协同关账、数据一致性校验及常见异常排查等关键问题。课件为单个PPTX文件(2.9M…

2026/10/11 16:20:35 阅读更多 →

日新闻

流感时间序列预测实战: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 阅读更多 →