FaceNet人脸特征提取与工业级部署实战
简介本资源是一份面向人工智能与深度学习初学者的FaceNet人脸识别实践项目聚焦计算机视觉中的核心任务——人脸验证与识别适用于高校学生、转行学习者及算法工程师快速掌握深度度量学习原理与工程实现。压缩包共10个文件含3个关键Python脚本main.py、lenet5.py、ResNet.py用于模型构建与训练流程4个XML配置文件支撑IDE环境与模块管理另有.gitignore、.name及.iml等开发元数据文件整体仅4KB轻量易部署。已有143人下载学习体现了小而精的典型教学型代码包价值。读者可直接复现FaceNet三元组损失训练逻辑、理解Inception/VGG主干网络微调方法并通过预置结构快速切入人脸嵌入向量生成与欧氏距离比对环节特别适合在有限算力环境下开展原理验证与代码调试。1. FaceNet 不是“又一个人脸识别模型”它是把人脸变成可比对向量的工业级范式你手上这个基于FaceNet的深度学习人脸识别.zip不是一份教学Demo而是一套可直接嵌入门禁系统、考勤终端或安防平台的特征提取流水线。它不依赖原始像素做分类而是用三元组损失Triplet Loss训练出一个紧凑的128维向量空间——同一人的不同照片在这个空间里距离极近不同人哪怕长得像向量距离也远超阈值。这正是为什么企业级人脸识别系统比如带活体检测的闸机、支持千人库的访客系统普遍绕不开FaceNet它解决了跨光照、小角度、低分辨率下特征稳定性差这个老大难问题。你不需要从头推导Inception ResNet-v1结构也不用纠结L2归一化是否必须——这个zip包里已经封装了预训练权重、标准化预处理管道、向量比对服务接口和最小可行验证脚本。适合两类人一是正在调试人脸识别门禁机硬件集成的嵌入式工程师需要快速验证特征提取模块是否兼容自己采集的图像流二是刚学完吴恩达深度学习课后题、想拿真实项目练手的开发者它比MNIST复杂但比ArcFace少30%的调参玄学。重点在于它不教你怎么训练模型而是教你如何让FaceNet在你的摄像头、你的服务器、你的数据库里真正跑起来、不翻车、不误判。2. 用预训练FaceNet模型提取人脸特征从图像到128维向量的最小闭环FaceNet的核心价值不在训练而在推理——它把人脸图像压缩成一个固定长度的向量embedding后续所有比对、聚类、检索都基于这个向量展开。本节带你用zip包里的代码在本地完成一次端到端特征提取不装CUDA、不改模型结构、不碰数据集只聚焦“输入一张图 → 输出一个向量”这个最基础但最关键的链路。2.1 环境准备避开PyTorch版本陷阱的轻量方案提示不要用conda install pytorch -c pytorch这是FaceNet复现中最常踩的坑FaceNet原始实现Google开源版严重依赖TensorFlow 1.x的图机制而当前主流环境多为PyTorch 2.x。但这个zip包采用的是PyTorch重写版FaceNet基于Inception ResNet-v1 backbone已适配torch1.9.0。实测发现torch1.12.1 torchvision0.13.1 组合最稳高版本如2.0会导致nn.AdaptiveAvgPool2d输出尺寸异常进而使embedding维度从128变成256。# 创建隔离环境推荐 python -m venv facenet_env source facenet_env/bin/activate # Linux/Mac # facenet_env\Scripts\activate # Windows # 安装精确匹配版本关键 pip install torch1.12.1cu113 torchvision0.13.1cu113 -f https://download.pytorch.org/whl/torch_stable.html pip install numpy opencv-python tqdm scikit-learn安装后验证import torch print(torch.__version__) # 必须输出 1.12.1cu113 print(torch.cuda.is_available()) # 若有GPU应为True无GPU时跳过CUDA相关代码即可2.2 加载模型与预处理为什么必须用facenet_preprocess而不是OpenCV resizeFaceNet对输入图像的归一化极其敏感。原始论文要求图像先中心裁剪为160×160再做均值归一化mean127.5, std128.0而非常见的/255.0。zip包中preprocess.py已封装此逻辑若你直接用cv2.resize(img, (160,160)) / 255.0会导致embedding向量整体偏移同一人脸两次提取的余弦相似度可能从0.92暴跌至0.65。# 示例从文件加载并预处理单张人脸 import cv2 import numpy as np from preprocess import facenet_preprocess # 来自zip包 img cv2.imread(test_face.jpg) # BGR格式 img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转RGB img_tensor facenet_preprocess(img_rgb) # 输出 shape: [1, 3, 160, 160], dtype: torch.float32 # 验证预处理效果 print(fPreprocessed tensor range: [{img_tensor.min():.3f}, {img_tensor.max():.3f}]) # 应接近 [-1.0, 1.0] print(fMean pixel value: {img_tensor.mean().item():.3f}) # 应接近 0.0facenet_preprocess内部逻辑先做中心裁剪非拉伸保留原始宽高比再转为float32执行(pixel - 127.5) / 128.0最后增加batch维度unsqueeze(0)。注意此函数不接受BGR输入必须先转RGB否则颜色通道错位导致特征失效。2.3 提取embedding用最少代码拿到128维向量zip包中的model.py已加载预训练权重通常为20180402-184057版本无需额外下载。核心是调用model.forward()并确保输出经L2归一化——这是FaceNet比对的前提。import torch from model import InceptionResnetV1 # 来自zip包 # 初始化模型自动加载权重 model InceptionResnetV1(pretrainedvggface2).eval() # 或 casia-webface if torch.cuda.is_available(): model model.cuda() img_tensor img_tensor.cuda() # 提取embedding with torch.no_grad(): embedding model(img_tensor) # shape: [1, 128] embedding torch.nn.functional.normalize(embedding, p2, dim1) # L2归一化 print(fEmbedding shape: {embedding.shape}) # [1, 128] print(fNorm of vector: {torch.norm(embedding, p2).item():.6f}) # 应严格等于1.0关键参数说明pretrainedvggface2使用在VGGFace2数据集上预训练的权重泛化性优于casia-webface尤其对亚洲人脸更鲁棒.eval()必须关闭dropout/batchnorm训练模式否则每次推理结果随机torch.nn.functional.normalize(..., p2, dim1)强制向量模长为1后续余弦相似度即点积值避免因尺度差异引入误差。3. 构建人脸比对服务从单张图到N人库的实时检索有了embedding下一步是构建可服务的比对逻辑。FaceNet本身不提供数据库或API但zip包中face_database.py和match_service.py已实现最小可行服务支持注册新用户、批量入库、实时查询Top-K相似人脸。本节聚焦如何把128维向量存进内存数据库并在毫秒级完成1:N比对。3.1 人脸注册为什么不用SQL存embedding而用NumPy二进制将embedding存入SQLite或MySQL看似合理但实际会带来两个致命问题数据库字段类型限制如SQLite BLOB最大1GB但10万人库的embedding总大小约500MB易触发锁表每次查询需反序列化二进制→numpy arrayCPU开销大。zip包采用内存映射NumPy memmap方案所有embedding以.npy格式存为连续二进制块用np.memmap直接映射到内存读取时零拷贝。# 注册新人脸示例 import numpy as np from face_database import FaceDatabase db FaceDatabase(db_path./face_db) # 自动创建目录 user_id zhangsan_001 embedding embedding.cpu().numpy().flatten() # [128] - [128] db.register(user_id, embedding) print(fRegistered {user_id} with embedding norm: {np.linalg.norm(embedding):.6f})FaceDatabase.register()内部执行将embedding追加到embeddings.npy末尾dtypefloat32同步更新index_map.pkl字典{user_id: index}embeddings.npy最终形状为[N, 128]其中N为总人数。3.2 实时比对用余弦相似度替代欧氏距离的物理意义FaceNet论文明确指出在L2归一化后的向量空间中余弦相似度 点积 欧氏距离的单调函数且计算更快无需开方。zip包中match_service.py默认使用np.dot计算相似度# 查询最相似的3个人 query_emb embedding.cpu().numpy().flatten() # [128] top_k_ids, top_k_scores db.search(query_emb, k3) print(Top matches:) for i, (user_id, score) in enumerate(zip(top_k_ids, top_k_scores)): print(f{i1}. {user_id}: {score:.4f}) # score ∈ [-1.0, 1.0]越接近1.0越相似阈值设定经验score 0.65大概率同一人光照/姿态变化下仍可靠0.4 score 0.65需人工复核或结合活体检测score 0.4几乎可判定为不同人。注意不要用0.5作为硬阈值实测在夜间低光场景下同一人多次采集的相似度会降至0.55~0.62硬切会导致漏报。3.3 批量入库优化避免逐条插入的I/O地狱当你要导入1000张员工照片时db.register()逐条调用会触发1000次磁盘写入。zip包提供bulk_register()方法一次性写入# 批量注册假设已有1000个embedding数组 embeddings_batch np.stack(all_embeddings, axis0) # shape: [1000, 128] user_ids [emp_0001, emp_0002, ..., emp_1000] db.bulk_register(user_ids, embeddings_batch)bulk_register内部先将embeddings_batch追加到embeddings.npy再批量更新index_map.pkl总耗时比逐条快8~12倍实测1000人从42s降至3.5s。4. 避坑指南FaceNet部署中90%的翻车都发生在这5个环节FaceNet看似简单但工程落地时极易因细节疏忽导致效果断崖式下跌。以下是我在线下门禁项目中踩过的5个真实坑按发生频率排序每条附带现象、根因和可立即执行的修复命令。4.1 现象同一张人脸两次提取的embedding余弦相似度仅0.72远低于0.9的理论值原因图像预处理未做L2归一化或模型输出未normalize。常见于直接调用model(img)后未执行F.normalize。解决# 错误写法 emb model(img_tensor) # 未归一化 # 正确写法 emb model(img_tensor) emb torch.nn.functional.normalize(emb, p2, dim1) # 必须加这一行4.2 现象GPU显存占用持续增长10分钟后OOM崩溃原因PyTorch默认启用梯度计算torch.is_grad_enabled()True即使在torch.no_grad()块内若之前有tensor未detach历史计算图仍被保留。解决在推理前强制清空缓存并确保所有输入tensor无gradtorch.cuda.empty_cache() # 加在推理循环开头 img_tensor img_tensor.requires_grad_(False) # 显式关闭梯度4.3 现象注册100人后搜索响应时间从5ms飙升至200ms原因search()方法默认遍历全部embedding计算点积时间复杂度O(N)。N1000时已明显卡顿。解决zip包内置faiss_index选项需pip install faiss-cpu启用近似最近邻db FaceDatabase(db_path./face_db, use_faissTrue) # 自动构建FAISS索引 # 搜索时间稳定在8~12msN10000时4.4 现象用手机拍摄的人脸照片比对失败率高达40%原因手机镜头畸变导致人脸形变而FaceNet预训练数据多为标准摄像头采集。未做畸变校正。解决在preprocess.py中加入OpenCV畸变校正需提前标定手机相机# 在facenet_preprocess函数内添加需calibration_matrix和dist_coeffs h, w img_rgb.shape[:2] undistorted cv2.undistort(img_rgb, calibration_matrix, dist_coeffs) # 后续对undistorted做中心裁剪4.5 现象Linux服务器上cv2.VideoCapture(0)无法读取USB摄像头原因Ubuntu 20.04默认使用v4l2驱动但部分USB摄像头需libv4l-0兼容层。解决sudo apt update sudo apt install libv4l-0 # 启动Python前加LD_PRELOAD LD_PRELOAD/usr/lib/x86_64-linux-gnu/libv4l/v4l1compat.so python your_script.py5. 进阶技巧用Triplet Loss微调FaceNet让模型适配你的特定场景预训练FaceNet在通用场景表现优秀但当你面对特殊需求时——比如工厂安全帽遮挡半张脸、医院ICU患者戴呼吸面罩、或者学校门口逆光抓拍——泛化能力会急剧下降。此时不重训整个网络而是用少量样本50~200张/人微调最后两层是最经济高效的方案。本节给出可直接运行的PyTorch微调脚本全程不超过20行核心代码。5.1 构造三元组数据集为什么不能用随机采样Triplet Loss要求每个batch包含锚点anchor、正样本positive同人不同图、负样本negative不同人。若随机采样90%的三元组是“简单样本”anchor与negative距离已很大Loss几乎不下降。zip包中triplet_sampler.py实现半硬负样本挖掘semi-hard negative mining只选满足dist(a,p) dist(a,n) dist(a,p)margin的负样本。# 构建数据加载器假设你有./data/train/下的子目录 from torch.utils.data import DataLoader from triplet_dataset import TripletFaceDataset from triplet_sampler import BalancedBatchSampler dataset TripletFaceDataset(root_dir./data/train) sampler BalancedBatchSampler(dataset, n_classes10, n_samples4) # 每batch 10人×4图 dataloader DataLoader(dataset, batch_samplersampler, num_workers4)5.2 微调策略冻结主干只训练最后两层FaceNet主干Inception ResNet-v1参数量超2000万全量微调需大量GPU显存。实践证明冻结除最后两个Inception模块外的所有层只训练Block8和AvgPool之后的线性层能在1个RTX 3090上2小时收敛且效果提升显著。# model.py中修改 def unfreeze_last_layers(model, unfreeze_blocks2): for name, param in model.named_parameters(): if block8 in name or avgpool in name or classifier in name: param.requires_grad True else: param.requires_grad False unfreeze_last_layers(model) optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr0.001)5.3 Triplet Loss实现margin设为0.2还是0.5原始FaceNet用margin0.2但在小样本微调时过小的margin会导致梯度消失。实测在50人库上margin0.4时收敛最快且验证集ROC曲线下面积AUC提升5.2%。from torch.nn import functional as F def triplet_loss(embeddings, targets, margin0.4): # embeddings: [N, 128], targets: [N] (类别标签) # 计算所有pairwise距离矩阵 dist_mat torch.cdist(embeddings, embeddings, p2) # [N, N] # 构造mask正样本对同类别和负样本对不同类别 labels_eq targets.unsqueeze(0) targets.unsqueeze(1) # [N, N] # 半硬负样本dist(a,n) dist(a,p) 且 dist(a,n) dist(a,p)margin loss 0 for i in range(len(embeddings)): pos_dist dist_mat[i][labels_eq[i]].max() # 最远正样本距离 neg_dist dist_mat[i][~labels_eq[i]].min() # 最近负样本距离 loss F.relu(pos_dist - neg_dist margin) return loss / len(embeddings)5.4 验证微调效果用ROC曲线代替准确率准确率在人脸比对中极具误导性——设阈值0.6时准确率98%但漏报率False Reject Rate可能高达15%。正确做法是画ROC曲线看在FAR1%时的识别率Genuine Accept Rate微调前vggface2微调后工厂场景FAR1% → GAR82.3%FAR1% → GAR94.7%FAR0.1% → GAR65.1%FAR0.1% → GAR88.9%生成ROC的代码已集成在eval_roc.py中只需运行python eval_roc.py --model_path ./models/facenet_finetuned.pth --test_dir ./data/test/我的习惯是每次微调后必跑ROC且只关注FAR0.1%和1%两个点。因为门禁系统中宁可多刷一次卡FAR升高也不能让陌生人通过FRR升高。这个取舍决定了你调参的方向——不是追求最高准确率而是把FAR压到业务能接受的底线之下。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

al-folio 404 页面定制指南:从 Jekyll 前端元数据到站内重定向与链接健康检查

al-folio 404 页面定制指南:从 Jekyll 前端元数据到站内重定向与链接健康检查

前端 【免费下载链接】al-folio A beautiful, simple, clean, and responsive Jekyll theme for academics 项目地址: https://gitcode.com/GitHub_Trending/al/al-folio 点击查看 免费下载 本篇技术指南围绕 al-folio 主题的 404 错误页(_pages/404.md…

2026/10/11 14:52:47 阅读更多 →
openjev 与闭源 Jev 差在哪:623 likes 的开源社区版 vs 原版全家桶

openjev 与闭源 Jev 差在哪:623 likes 的开源社区版 vs 原版全家桶

openjev 与闭源 Jev 差在哪:623 likes 的开源社区版 vs 原版全家桶 【免费下载链接】openjev 项目地址: https://ai.gitcode.com/hf_mirrors/AlexWortega/openjev Jev 大概是近一个月 AI 圈讨论密度最高的"非大模型":不写文章、不聊天…

2026/10/11 14:52:47 阅读更多 →
普通人也能看懂的MCP入门指南!从Stdio到JSON的6个实战案例(含TaoToken配置)

普通人也能看懂的MCP入门指南!从Stdio到JSON的6个实战案例(含TaoToken配置)

/* 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:52:47 阅读更多 →

最新新闻

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

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

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

2026/10/11 15:34:08 阅读更多 →
CodeWiki生成什么?docs目录、Mermaid架构图与模块树全解读

CodeWiki生成什么?docs目录、Mermaid架构图与模块树全解读

【免费下载链接】CodeWiki [ACL 2026] Open-source framework for holistic, structured repository-level documentation across multilingual codebases 项目地址: https://gitcode.com/gh_mirrors/co/CodeWiki 点击查看 免费下载 CodeWiki 是一个开源的 AI 代码…

2026/10/11 15:34:08 阅读更多 →
耐酸膜品牌选型参考:工业工况耐受性与膜元件形态适配

耐酸膜品牌选型参考:工业工况耐受性与膜元件形态适配

耐酸膜品牌选型参考:工业工况耐受性与膜元件形态适配 在工业高浓度废水处理项目中,酸洗废水、金属蚀刻液、矿山酸性排水等场景往往涉及复杂的腐蚀性工况。如何在强酸环境下维持膜元件的稳定运行,同时满足分离效率与使用寿命的双重需求&#x…

2026/10/11 15:34:08 阅读更多 →
编译原理实验报告:词法分析、语法分析与冲突排查实践

编译原理实验报告:词法分析、语法分析与冲突排查实践

简介:一份编译原理词法分析与语法分析实验报告,面向计算机专业本科生及考研复习者,用于系统理解编译器前端词法分析、语法分析两大核心阶段。报告从词法分析状态图设计讲起,说明如何识别标识符、关键字、十进制整数以及 - * / >…

2026/10/11 15:34:08 阅读更多 →
12代酷睿+B660M平台重启黑屏?照着这篇排查思路搞定它

12代酷睿+B660M平台重启黑屏?照着这篇排查思路搞定它

兄弟们,如果你手上正好是12代酷睿(比如12400F/12600KF这类)搭配B660M主板,再塞一张3070Ti显卡,而且症状跟标题一模一样——开机一切正常,跑分打游戏都没事,但只要一“重启”,屏幕就彻…

2026/10/11 15:34:08 阅读更多 →
粮仓温湿度控制系统选型与PID策略:从传感器到避坑实践

粮仓温湿度控制系统选型与PID策略:从传感器到避坑实践

简介:一份基于单片机的粮仓温湿度检测与控制系统设计资料,面向自动化、电子及物联网相关专业学生与工程技术人员,针对大型粮库温湿度实时监测与超限报警需求,给出了可借鉴的完整设计方案。资源为华北电力大学本科毕业设计论文&…

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

日新闻

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