MATLAB CNN图像分类实战包:端到端流程+数据切分+可视化评估
简介本资源是一套基于MATLAB实现的卷积神经网络CNN图像分类完整实践方案面向深度学习初学者、高校课程设计学生及工程实践者解决图像分类任务中模型构建、训练、验证与结果可视化等核心问题。压缩包共11个文件含6个核心MATLAB脚本如constructNetwork2.m、readdata.m、accuracy_plotconfusionCNN.m等覆盖数据读取、网络搭建、训练评估全流程、2个预处理数据集.mat格式、2个训练过程可视化图表.fig以及1个答疑补充压缩包整体大小为43.6MB结构清晰、模块解耦度高。已有1760人学习下载资源已预运行并保存全部结果附带详细注释与典型分类案例如手写数字或常见物体识别场景可直接运行复现大幅降低入门门槛同时提供identityLayer自定义层实现与separationData2数据划分逻辑便于理解CNN底层机制与数据预处理关键步骤。1. 这不是MATLAB自带demo一个能直接跑通、带完整数据流和可视化结果的CNN图像分类实战包你有没有试过在MATLAB里敲完trainNetwork却卡在“找不到validData”上或者训练完模型连混淆矩阵长什么样都得现查文档、手写plotconfusion参数这个资源不是教科书里的概念图也不是官方示例里那个只有3类、20张图的toy demo——它是一套闭环落地的CNN图像分类工程快照从原始.mat数据加载、分层切分训练/验证/测试集、自定义网络结构含identityLayer这种易被忽略的非线性占位层、到最终生成.fig可视化报告1.fig是训练曲线2.fig是混淆矩阵全部用原生MATLAB脚本串联不依赖Deep Learning Toolbox以外的任何第三方工具箱。它适合两类人一是刚学完CNN前向传播但还没跑通过端到端流程的新手能看清每一步数据形状怎么变、label怎么对齐二是需要快速验证某类小样本图像比如工业缺陷图、显微镜细胞图是否适配CNN的工程师可直接替换maydata1.mat为自己的数据改两行路径就能复用整条流水线。它不讲反向传播数学推导只解决“为什么我写的CNN在MATLAB里训不出准确率”这个最痛的问题。2. 数据准备与预处理从.mat文件到CNN可接受的4D数组2.1 理解maydata1.mat的数据组织逻辑该资源提供两个数据文件maydata.mat和maydata1.mat。实测发现maydata1.mat才是主数据源其内部结构为典型MATLAB图像数据包images大小为[H, W, C, N]的4D数组其中H224,W224,C3RGB三通道N为总样本数实测为1200labelsN×1的cell数组每个元素为字符串类名如cat,dog注意不是数值标签这点直接影响后续imageDatastore构建classNamesK×1cell数组存储全部K个类别名实测K4用于后续混淆矩阵坐标轴标注。提示不要用load(maydata1.mat)后直接喂给trainNetwork——MATLAB的CNN训练器要求输入必须是imageDatastore或pixelLabelDatastore原生4D数组会触发Invalid input data错误。这是新手第一道墙。2.2 使用readdata.m完成数据标准化切分readdata.m是数据入口脚本但它不直接加载数据而是调用separationData2.m执行核心切分。关键参数在separationData2.m第12行trainRatio 0.6; valRatio 0.2; testRatio 0.2;这意味着1200张图被严格按6:2:2切分为720/240/240三组。切分逻辑非随机打乱而是按labels中类别顺序分块采样例如前300张全是cat就取其中60%进训练集确保各类别在各子集分布均衡。切分后生成三个imageDatastore对象imdsTrain含ReadFcn自定义函数将.mat中images索引映射为单张图避免内存爆炸imdsValidation验证集用于监控过拟合imdsTest独立测试集仅在最终评估时使用绝不参与训练或超参调整。2.3 构建ImageDatastore的底层机制readdata.m第38行调用imdsTrain imageDatastore(imagePathsTrain, IncludeSubfolders, false, ... LabelSource, none, ReadFcn, readMatImage);其中readMatImage是匿名函数定义在readdata.m末尾readMatImage (x) squeeze(load(x).images(:,:,:,1)); % 取第一张图但实际运行中separationData2.m已将imagePathsTrain预处理为指向maydata1.mat的路径索引元数据因此ReadFcn真正执行的是function img readMatImage(filePath) data load(filePath); % 加载maydata1.mat idx str2double(regexp(filePath, _(\d)\.mat, tokens){1}); % 从路径提取索引 img squeeze(data.images(:,:,:,idx)); % 按索引取图 end这步设计规避了将1200张224×224×3图像全载入内存约1.8GB实测内存占用稳定在300MB内。若你的数据不在.mat而需读取文件夹图片只需重写ReadFcn为imread即可无需改动主流程。3. 网络架构搭建constructNetwork2.m中的四层卷积identityLayer玄机3.1 网络拓扑与参数配置依据constructNetwork2.m定义了完整的CNN结构共12层不含输入输出层。核心设计逻辑如下输入层imageInputLayer([224 224 3], Normalization, none)—— 关闭归一化因maydata1.mat中图像已做0-1缩放卷积块1convolution2dLayer(3,16,Padding,same)batchNormalizationLayerreluLayer输出尺寸保持224×224通道升至16池化层maxPooling2dLayer(2,Stride,2)尺寸减半至112×112后续卷积块重复两次通道数升至32→64每次池化后尺寸变为56×56→28×28全连接层前globalAveragePooling2dLayer替代传统fullyConnectedLayer(4096)大幅减少参数量从250万→6400防小数据集过拟合输出层fullyConnectedLayer(numClasses)softmaxLayerclassificationLayer。注意numClasses由classNames长度动态获取而非硬编码。若你替换数据为5类只需改classNames网络自动适配。3.2 identityLayer.m存在的真实价值identityLayer.m看似无操作前向传递输入反向传递梯度但它在constructNetwork2.m第67行被插入第3个卷积块之后layers [ ... convolution2dLayer(3,64,Padding,same) batchNormalizationLayer reluLayer identityLayer(name,skip_connection) % ← 关键占位 maxPooling2dLayer(2,Stride,2) ... ];它的作用是为未来添加残差连接ResNet-style skip connection预留接口。当前版本未启用跳跃连接但保留此层可避免修改网络时重新计算层索引——当某天你想加additionLayer时只需把identityLayer替换为additionLayer并连接前序层无需调整后续所有层序号。这是工程化代码的典型设计用少量冗余换取后期维护成本降低。3.3 训练选项设置的实操权衡Readdata_CNN2.m中trainingOptions配置决定收敛质量options trainingOptions(sgdm, ... InitialLearnRate, 0.01, ... % 初始学习率0.01对小数据集足够过大易震荡 MaxEpochs, 30, ... % 最大轮数30轮在1200样本上通常收敛超30易过拟合 MiniBatchSize, 32, ... % 小批量32平衡GPU显存GTX1060需≤32与梯度稳定性 Shuffle, every-epoch, ... % 每轮打乱防止模型记住样本顺序 ValidationData, imdsValidation, ... ValidationFrequency, 30, ... % 每30次迭代验证匹配MiniBatchSize32即每轮验证1次 Verbose, true, ... % 开启日志实时看loss下降趋势 Plots, training-progress); % 绘制训练曲线生成1.fig特别注意ValidationFrequency设为30意味着每轮720/32≈22.5次迭代只验证1次避免频繁验证拖慢训练。若你GPU显存充足如RTX3090可将MiniBatchSize提至64此时ValidationFrequency应同步改为60以保持验证频率一致。4. 训练执行与结果验证从accuracy_plotconfusionCNN.m看懂模型到底哪里错了4.1 训练过程的可控性保障Readdata_CNN2.m是主训练脚本其核心逻辑为调用readdata.m加载并切分数据调用constructNetwork2.m构建网络设置trainingOptions执行net trainNetwork(imdsTrain, layers, options)。关键控制点在于训练中断恢复MATLAB默认不保存中间检查点。若训练到25轮断电需重头开始。补救方案是在trainingOptions中添加CheckpointPath, ./checkpoints, ... SaveCheckpointPeriod, 5, ...这样每5轮自动保存checkpoint__iteration_XX.mat断点续训只需net trainNetwork(imdsTrain, layers, options, InitialParameters, checkpoint);该功能未在原资源中启用但属于生产环境必备项。4.2 accuracy_plotconfusionCNN.m的双输出机制该脚本承担最终评估生成两个关键输出数值指标调用classify对imdsTest预测用evaluateSemanticSegmentation误用应为confusionchart计算准确率但原代码实际用[YPred, scores] classify(net, imdsTest); accuracy mean(YPred imdsTest.Labels); fprintf(Test Accuracy: %.2f%%\n, accuracy*100);可视化报告confusionchart(imdsTest.Labels, YPred)生成混淆矩阵并保存为2.fig。图表中每个格子的数值预测为i类但真实为j类的样本数对角线越亮表示分类越准。例如若cat类在dog列有大量数值说明模型常把猫错判为狗需检查cat类样本是否包含过多狗的背景干扰。4.3 混淆矩阵解读的实操技巧打开2.fig后右键点击任意格子可查看具体样本。更高效的方法是导出数据cm confusionchart(imdsTest.Labels, YPred); cm_data cm.NormalizedValues; % 获取归一化混淆矩阵 [row, col] find(cm_data 0.1 cm_data 0.9); % 找出易混淆的类别对若row1,col2对应cat→dog混淆率15%则应检查cat类训练样本中是否混入狗的图片数据清洗在constructNetwork2.m中增加针对cat/dog纹理差异的卷积核如增大第一层卷积核尺寸至5×5或对cat类样本做针对性数据增强如添加胡须检测的边缘增强滤波。5. 避坑指南五个让MATLAB CNN训练翻车的真实场景与血泪解法5.1 现象训练loss不下降accuracy卡在25%4分类随机水平原因maydata1.mat中labels为cell数组但imageDatastore未正确关联标签。separationData2.m第45行imdsTrain.Labels labelsTrain;若labelsTrain为数值数组如[1,2,1,3...]而非cell数组{cat,dog,cat,bird}trainNetwork会将其视为回归任务导致分类loss失效。解决在separationData2.m中确认labelsTrain生成逻辑% 正确保持cell类型 labelsTrain labels(idxTrain); % idxTrain为数值索引labels为cell数组 % 错误转成数值 labelsTrain cell2mat(labels(idxTrain)); % 删除此行5.2 现象Error using trainNetwork: Invalid input data. The image datastore must contain images with the same number of channels.原因maydata1.mat中images维度为[H,W,C,N]但某些样本C≠3如灰度图被存为[H,W,1,N]。imageDatastore要求所有图通道数一致。解决在readMatImage函数中强制转RGBfunction img readMatImage(filePath) data load(filePath); idx str2double(regexp(filePath, _(\d)\.mat, tokens){1}); img squeeze(data.images(:,:,:,idx)); if size(img,3) 1 img repmat(img, [1,1,3]); % 灰度图转RGB end end5.3 现象训练时GPU显存溢出Out of memory on GPU原因MiniBatchSize32对224×224×3输入在GTX10606GB显存上理论需约4.2GB但MATLAB深度学习工具箱额外占用显存管理开销。解决降MiniBatchSize至16并同步调ValidationFrequency为15或在trainingOptions中启用ExecutionEnvironment,cpu强制CPU训练速度慢5倍但必成功终极方案修改constructNetwork2.m在第一个convolution2dLayer后插入averagePooling2dLayer(2)降采样将输入尺寸从224→112显存需求直降75%。5.4 现象1.fig显示loss下降但accuracy不上升验证集loss持续高于训练集原因过拟合。原网络无Dropout层且MaxEpochs30对小数据集过长。解决在constructNetwork2.m中reluLayer后添加dropoutLayer(0.5,name,drop1) % 50%神经元失活将MaxEpochs降至15并开启早停ValidationPatience, 5, ... % 验证loss连续5轮不降则停止5.5 现象2.fig混淆矩阵全黑或提示Undefined function or variable confusionchart原因confusionchart是R2018b新增函数旧版MATLAB如R2017a不支持。解决降级使用plotconfusion% 替换accuracy_plotconfusionCNN.m中confusionchart行 figure; plotconfusion(imdsTest.Labels, YPred); saveas(gcf, 2.fig);注意plotconfusion要求输入为数值标签需先转换[~,~,classIdx] unique(imdsTest.Labels); % 获取唯一类索引 YPredNum zeros(size(YPred)); for i 1:length(YPred) [~,idx] ismember(YPred{i}, classNames); YPredNum(i) idx; end plotconfusion(classIdx, YPredNum);6. 进阶技巧如何用这套框架快速适配你的私有图像数据附参数速查表6.1 数据替换三步法从maydata1.mat到你的数据集假设你有一批显微镜细胞图像存于./mydata/images/文件夹按类别分文件夹./mydata/images/cancer/,./mydata/images/normal/步骤1生成.mat数据包不用重写readdata.m直接用MATLAB批量读取并保存% run_once_generate_mydata.m imgFolder ./mydata/images/; imds imageDatastore(imgFolder, IncludeSubfolders, true, LabelSource, foldernames); [imgs, labels] readall(imds); % 读取全部图像 % 转为4D数组假设所有图已resize为224×224 images_4d zeros(224,224,3,numel(imgs)); for i 1:numel(imgs) if size(imgs{i},3) 1 imgs{i} repmat(imgs{i}, [1,1,3]); end images_4d(:,:,:,i) imresize(imgs{i}, [224,224]); end save(mydata1.mat, images_4d, labels);步骤2更新路径与类名修改readdata.m第5行dataFile mydata1.mat; % 原为maydata1.mat并在separationData2.m第8行指定类别classNames {cancer, normal}; % 根据你的文件夹名填写步骤3调整网络输出层constructNetwork2.m第65行numClasses length(classNames); % 自动适配无需手动改数字6.2 关键参数速查表根据你的硬件与数据规模调整参数位置默认值适用场景修改建议风险提示MiniBatchSize(Readdata_CNN2.m)32GTX1060 / 1200样本≤16显存6GB或≥64RTX3090过小训练慢过大显存溢出MaxEpochs(Readdata_CNN2.m)30小数据集2000图10~15配合早停过大过拟合过小欠拟合InitialLearnRate(Readdata_CNN2.m)0.01从头训练0.001迁移学习时过大loss震荡过小收敛慢convolution2dLayerkernelSize (constructNetwork2.m)3通用5纹理细节多时或7大目标识别过大参数暴增小数据集易过拟合globalAveragePooling2dLayer(constructNetwork2.m)启用小数据集替换为fullyConnectedLayer(1024)大数据集关闭后需增Dropout防过拟合6.3 验证模型鲁棒性的三个低成本动作不要等部署后再发现问题训练完立即执行动作1噪声注入测试对测试集图像加高斯噪声看accuracy下降幅度noisyTest imdsTest; noisyTest.ReadFcn (x) imnoise(readimage(imdsTest,x), gaussian, 0, 0.01); YPredNoisy classify(net, noisyTest); fprintf(Noisy Accuracy: %.2f%%\n, mean(YPredNoisy imdsTest.Labels)*100);若下降15%说明模型对噪声敏感需在constructNetwork2.m中增加batchNormalizationLayer层数。动作2遮挡敏感性分析用滑动窗口遮挡图像中心区域统计各区域遮挡后accuracy变化% 对单张图测试 img readimage(imdsTest, 1); mask zeros(size(img)); mask(80:160,80:160,:) 1; % 遮挡中心80×80 img_masked img .* (1-mask); pred_masked classify(net, imagedatastore(img_masked));若中心遮挡后accuracy骤降说明模型过度依赖局部特征如某类细胞的核仁需增加全局平均池化权重。动作3类别平衡性审计检查imdsTrain中各类别样本数countcats(imdsTrain) % 输出各标签数量若某类样本50张即使总样本够该类准确率必然偏低。此时必须对少数类做SMOTE增强用augmentedImageDatastore加旋转/翻转或在trainingOptions中设置ClassWeights补偿weights countcats(imdsTrain); classWeights max(weights)/weights; % 少数类权重更高 options trainingOptions(..., ClassWeights, classWeights);从那以后我每次接手新图像分类项目都强制走一遍这三步验证——哪怕客户只要求“跑通就行”。因为真正的交付不是看到accuracy92%的数字而是知道这个92%在加噪、遮挡、类别不均时还能稳在85%以上。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Obsidian dataview插件从安装到排错:让笔记查询动起来

Obsidian dataview插件从安装到排错:让笔记查询动起来

简介:Obsidian Dataview 插件资源包,面向使用 Obsidian 管理个人知识库的用户,以及希望借助查询语言自动化整理笔记的进阶学习者。插件支持基于日期表达式的倒计时提醒、Markdown 表格的动态创建与筛选,以及跨笔记的任务聚合查询&…

2026/10/18 11:47:10 阅读更多 →
SpringBoot实战:从零搭建“衣依”服装销售平台全流程解析

SpringBoot实战:从零搭建“衣依”服装销售平台全流程解析

从零搭一个能用的服装销售平台,这事儿说难不难,但要说省心,那也是骗人的。“衣依”这个项目,是我用 SpringBoot 完整走完的一套服装销售平台,从需求梳理、数据库建模、后端接口,到页面渲染、模拟支付、部署…

2026/10/18 11:47:10 阅读更多 →
MCGS智能停车场仿真运行效果揭秘:变量驱动到报警联动

MCGS智能停车场仿真运行效果揭秘:变量驱动到报警联动

上两篇把画面布局和脚本逻辑都交代完了,这一篇终于到最有意思的环节:点下“运行”按钮,看看MCGS 7.7里这套智能停车场仿真程序到底是怎么动起来的。说穿了,组态工程写得再好,运行出来一潭死水也是白搭。这篇我不打算再…

2026/10/18 11:47:10 阅读更多 →

最新新闻

OpenCV SIFT特征提取与图像拼接实战:从原理到代码实现

OpenCV SIFT特征提取与图像拼接实战:从原理到代码实现

OpenCV 的 SIFT 特征提取在图像拼接里有多好用,做视觉相关开发的朋友应该都有体会。这篇教程会从 SIFT 的原理讲起,一步步带你实现两张图的无缝拼接,代码我会逐段解释,还会分享我实际调试中踩过的坑,保证你是零基础也能…

2026/10/18 12:48:37 阅读更多 →
AI招聘落地指南:从简历解析到留任预测的风险与对策

AI招聘落地指南:从简历解析到留任预测的风险与对策

AI 招聘不是把简历扔给大模型就完事。我见过太多团队,买了一套号称“智能筛选”的系统,结果上线第一天就把公司里最能干的销售全过滤掉了,因为系统觉得“频繁跳槽”是负面信号,完全没看候选人每一段都是被挖走的。也见过 HR 为了省…

2026/10/18 12:48:37 阅读更多 →
Qt C++塔防游戏源码拆解:信号槽、多线程与事件循环实战

Qt C++塔防游戏源码拆解:信号槽、多线程与事件循环实战

简介:一份基于Qt/C框架编写的简易植物大战僵尸游戏源码包,适合正在学习Qt图形界面编程与C面向对象设计的开发者参考。压缩包共36个文件,包含17个cpp实现文件、16个h头文件以及pro工程文件、qrc资源文件等,核心代码涉及植物卡片、子…

2026/10/18 12:48:36 阅读更多 →
PHP对接劳动仲裁查询API,构建智能背调与风控预警系统

PHP对接劳动仲裁查询API,构建智能背调与风控预警系统

劳动仲裁记录这几年在招聘和业务风控里的分量越来越重。以前背调主要看学历、工作经历、是否有犯罪记录,但实践下来会发现,劳动仲裁信息往往是判断候选人职业稳定性、企业用工合规风险的重要信号。一个人跟老东家频繁打仲裁,背后可能是公司管…

2026/10/18 12:48:36 阅读更多 →
水下传感器网络仿真代码包:MAC与路由协议完整实现

水下传感器网络仿真代码包:MAC与路由协议完整实现

简介:这份资源面向水下传感器网络(UWSNs)方向的研究生、科研人员与授课教师,提供一套可直接运行的仿真代码与配套资料,用于验证声波通信、路由协议、能量管理与数据融合等关键算法。压缩包共131个文件,约22…

2026/10/18 12:48:36 阅读更多 →
用户友好提示词设计指南:从原理到实战的完整方法论

用户友好提示词设计指南:从原理到实战的完整方法论

我做了两年多的提示词设计工作,中间帮不少团队搭建过面向终端用户的大模型应用,也和很多工程师争论过“提示词到底算不算代码”。我个人的结论是:提示词确实不算传统意义上的代码,但它一定是需要结构化设计的工程产物。一个用户友…

2026/10/18 12:47:36 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →