news 2026/9/26 12:46:47

YOLOv5智能垃圾分类系统:从环境配置到部署全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5智能垃圾分类系统:从环境配置到部署全攻略

简介:基于YOLOv5的智能生活垃圾分类系统源码,面向计算机视觉、深度学习方向的高校学生,适合作为毕业设计、期末大作业或课程设计的完整参考项目。资源围绕生活垃圾分类检测场景,运用YOLOv5目标检测框架,覆盖数据配置、模型训练与推理检测等关键环节。

资源包共76个文件,以Python脚本、YAML配置和Markdown说明文档为主。py文件约40个,包含训练、验证、检测等核心处理流程;yaml文件约22个,用于定义模型结构与数据参数;另有Markdown文档可辅助理解整体思路。压缩包约178KB,结构清晰。

目前已有165人学习下载。项目代码含注释、界面简洁,功能覆盖垃圾识别与目标定位,经过严格调试可直接部署运行。作为导师认可度较高的高分毕设项目,对需要快速搭建视觉识别系统、完成课程设计或毕设演示的同学,具备实在的参考与复用价值。

1. 为什么垃圾分类毕设选YOLOv5:从选题价值到可跑通性

每年毕业设计选“智能垃圾分类”这个题目的学生不少,但真正能交出一套可运行、可演示、可答辩系统的并不多。大多数人的项目卡在同一个位置:模型训练跑通了,检测结果却一塌糊涂;或者环境装了一星期,最后连 demo 都没跑起来。这套基于 YOLOv5 的智能生活垃圾分类系统,解决的核心问题就是把“目标检测”这件事落到真实的垃圾分类场景:输入一张垃圾照片,输出带有类别标签的检测框,对应到可回收物、有害垃圾、厨余垃圾和其他垃圾。适合两类人:一类是做毕设、需要完整源码和文档支撑的学生;另一类是刚接触深度学习目标检测、想找一个完整项目练手的新手。下面按我实际拆这个项目的顺序来写。

2. 环境与源码结构:两天跑通基线的配置路径

2.1 环境版本:Python、CUDA、PyTorch怎么匹配

YOLOv5 对环境的要求不算苛刻,但版本错配是最容易翻车的点。装环境之前先确认三件事:显卡型号和显存大小、显卡驱动版本、以及你打算用的 PyTorch 版本。很多人在这步栽跟头,是因为没搞清“驱动支持的 CUDA 版本”和“PyTorch 运行时实际调用的 CUDA 版本”是两回事。nvidia-smi 里显示的高版本 CUDA,只代表驱动上限,不代表你 pip 安装的 PyTorch 就能用它。

我一般建议的配置组合如下表所示,注意根据自己的显卡选择,而不是照抄教程。

组合PythonCUDAPyTorch适用场景
稳妥组合3.811.71.13.1老显卡、多次装环境失败的情况
新卡组合3.1012.12.1.0RTX 30/40 系显卡

创建虚拟环境时用 conda,因为 conda 创建的 Python 环境和系统 Python 隔离得干净,后面装错版本想回退也容易。激活环境后先装核心依赖,不要一开始就把 requirements.txt 整个装上,那样出了问题不好定位。

conda create -n yolov5 python=3.10 -y conda activate yolov5 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121

这里指定了 cu121 的 wheel 源,PyTorch 会捆绑对应的 CUDA 运行时。--index-url参数把安装源指向 PyTorch 官方 wheel 仓库,避免从 PyPI 默认源装到 CPU 版本。装完之后验证一下 GPU 是否可用:

python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"

打印结果为True和显卡名称,说明 CUDA 环境是通的。如果这里输出False,基本可以判定是 PyTorch 装成了 CPU 版,重新用指定 CUDA 版本的命令覆盖安装即可。

2.2 源码目录:每个文件是干什么的

源码包解压之后,你会发现目录结构和官方 YOLOv5 仓库基本一致,这是好事,因为查阅资料时可以照着官方文档和社区问答来排查问题。花半小时把目录过一遍比直接开跑要靠谱得多,至少你要知道 train.py 是训练入口、detect.py 是推理入口,别把两者混了。

文件/目录作用日常使用频率
train.py训练主入口,加载数据配置和模型结构高
detect.py推理入口,支持图片、视频、摄像头高
models/网络结构定义,yolov5s.yaml 等中
data/数据集配置和超参数配置高
utils/数据增强、损失计算、指标评估等工具低
runs/训练和推理输出目录,自动生成中

项目里垃圾分类相关的代码和数据配置一般集中在 data/ 和 utils/ 中,你要改的其实就是 data/garbage.yaml 和 models/yolov5s.yaml。前者定义数据集路径和类别数,后者定义网络结构大小。训练时输出的图纸、权重、日志都在 runs/train/exp 里,这个目录是你后期调参时盯得最多的地方。

2.3 首次跑通:先用官方权重验证环境

拿到源码先别急着训练自己的数据集。正确的顺序是用官方预训练权重跑一次推理,确认整条链路是通的,再做后续替换。

python detect.py --source data/images/bus.jpg --weights yolov5s.pt --conf-thres 0.25

--source指定输入图片,--weights指定预训练权重,--conf-thres是置信度阈值,低于这个值的检测框会被过滤掉。跑完在 runs/detect/exp 里能看到标注了类别和得分的输出图。

这一步的意义在于验证环境完整性。如果这一步都报错,问题一定出在环境配置或依赖版本上,而不是你的数据或代码逻辑。等到能稳定输出结果,再开始准备自己的数据集,这样能把“环境问题”和“数据问题”分开排查,避免到时候训练失败不知道锅该甩给谁。

3. 数据集构建与标注:从原始图片到YOLO格式的完整链路

3.1 类别设计:按物品分类还是按垃圾四分法

垃圾分类数据集的类别设计直接决定模型的可用性和训练难度。现在公开的垃圾分类数据集有按物品细分的,比如纸张、塑料、玻璃、金属、布料、电池、灯管、药品、果皮、剩饭,也有直接按“可回收物、有害垃圾、厨余垃圾、其他垃圾”四分类的。两种方案各有取舍。

按物品细分类别,模型学的是“这个物体是什么”,比如一个饮料瓶,无论它是否被压扁、是否带有标签,模型都要认得出来。这个方案的好处是后续可以按映射表把物品映射到四分类,比如塑料瓶映射到可回收物,坏处是标注工作量大、类别间外观差异有时很小。

按四分类直接做,标注工作量小,但模型要学“什么东西属于可回收物”这种抽象概念,同一类别的物体外观差异极大,对数据量和训练难度都不友好。作为毕设项目,我建议折中:选择 10 类左右的常见物品细分类别,像纸板、塑料瓶、玻璃瓶、易拉罐、果皮、电池、灯管、布料、香烟头、一次性餐盒,训练完成后在推理脚本里维护一张映射表,把物品类别映射到四分类桶,再输出投放建议。这样既控制了标注成本,又能讲清楚“为什么这么做”的设计逻辑。

3.2 标注规范:用labelImg时要注意的几件事

标注工具推荐 labelImg,因为它生成的 VOC 格式 XML 文件转换路径最清晰,社区资料也多。标注时有一条硬性要求:类别名称统一用英文字母或数字,不要用中文。YOLO 读取类别时按 classes.txt 的行号索引,中文字符编码问题会导致类别错位甚至读不出文件。

另外标注框要贴合目标轮廓。垃圾物品往往形状不规则,比如被压扁的纸箱、揉成团的塑料袋,别把整张桌子框进去,也别只框一半。标注框边缘只需留 1 到 2 个像素的余量,这样训练出来的框回归更精准。还有一条容易被忽略的:背景不标注。很多人把垃圾所在的桌面、地面、垃圾桶也标进去了,这会把大量背景像素教成物体,推理时满屏误检。

3.3 VOC转YOLO:转换脚本与坐标校验

labelImg 默认保存为 VOC 格式的 XML 文件,每个 XML 里包含图片尺寸和所有标注框的左上角、右下角坐标。YOLO 训练需要的是 txt 文件,每行一个框,格式为class x_center y_center width height,四个坐标都已归一化到 0 到 1 之间。转换脚本是必经之路,下面这个脚本是清理过的最核心逻辑。

import xml.etree.ElementTree as ET import os def convert(size, box): dw = 1.0 / size[0] dh = 1.0 / size[1] x_center = (box[0] + box[1]) / 2.0 y_center = (box[2] + box[3]) / 2.0 w = box[1] - box[0] h = box[3] - box[2] return x_center * dw, y_center * dh, w * dw, h * dh def xml_to_txt(xml_path, out_path, classes): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) with open(out_path, "w") as f: for obj in root.iter("object"): cls = obj.find("name").text if cls not in classes: continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) cx, cy, w, h = convert((img_w, img_h), (xmin, xmax, ymin, ymax)) f.write(f"{classes.index(cls)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n")

convert函数把左上角和右下角坐标换算成中心点加宽高的形式,再除以图片宽高做归一化。注意w和h在推导时用的是box[1] - box[0],也就是 x 方向差和 y 方向差,别把顺序搞混。classes是一个列表,顺序必须和后面data/garbage.yaml里的names完全一致,否则会错位。

转换完必须校验。打开生成的 txt 文件,看坐标是否都在 0 到 1 之间,有没有出现负数或大于 1 的异常值。常见问题是用 PIL 读图片尺寸时,size是(width, height),而用 OpenCV 读是(height, width),搞反了会导致宽高互换,标注框全部错位。

3.4 数据划分:训练集、验证集的目录得长这样

YOLOv5 读取数据时按目录自动匹配图片和标签。标准结构是images/train、images/val和labels/train、labels/val两套目录一一对应。图片叫001.jpg,标签就必须叫001.txt,后缀可以不同,文件名主体必须一致。

datasets/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── garbage.yaml

划分比例我按 8:1:1 来做,训练集占八成,验证集和测试集各占一成。划分时不要用 random 函数瞎洗,要按目录分层采样,比如按拍照场景分,同一场景的图片不要同时落进训练集和验证集,否则会引入数据泄漏,验证集指标虚高,实际部署后立刻现原形。

4. 训练与调参:从默认超参到稳定收敛

4.1 先写对数据配置文件:yaml里的每个字段都有用

训练前的第一步是配置data/garbage.yaml,这个文件描述数据集位置和类别信息。YOLOv5 新版把train和val的路径放在 yaml 顶层,注意路径是相对运行目录的,写绝对路径也没问题,但绝对路径在换机器后要全部改一遍,所以我会用相对路径加项目根目录的方式。

path: ./datasets train: images/train val: images/val test: images/test nc: 10 names: ["cardboard", "plastic_bottle", "glass_bottle", "can", "fruit_peel", "battery", "lamp", "cloth", "cigarette_butt", "lunch_box"]

path是数据集根目录,train和val是相对于path的子目录。nc是类别数,必须和names列表长度一致,这是最常见的低级错误。names的顺序和检测模型输出的类别索引一一对应,推理时系统靠这个列表把 0、1、2 之类的数字翻译成“塑料瓶”“玻璃瓶”。

一个容易被忽略的坑是:训练集、验证集图片和标签文件必须一一配对。如果某张图片没有对应的 txt 标签,YOLOv5 训练时会跳过它并输出警告;但如果某个 label 存在而图片缺失,会直接报错。我在准备数据后会用脚本检查两边文件数量是否一致,确保没有孤儿文件。

4.2 训练命令:核心参数怎么传

数据配置写好后就开始训练。第一次跑建议用官方预训练权重做迁移学习,也就是--weights yolov5s.pt,以 COCO 预训练模型作为初始化,再微调到垃圾分类数据集上。从零训练不是不行,但需要的数据量和训练轮次都要成倍增加,毕设项目没必要。

python train.py --data data/garbage.yaml --cfg models/yolov5s.yaml --weights yolov5s.pt --batch-size 16 --epochs 100 --imgsz 640 --workers 4

--batch-size受显存限制,8G 显存跑 16 已经到极限。--epochs先设 100,后期看曲线再决定是否提前终止。--imgsz是输入分辨率,640 是精度和速度的平衡点;如果数据里的目标比较小,可以试 960,显存不足降到 512 也行。--workers是数据加载线程数,Windows 下建议设 0 或 2,设太高容易报 DataLoader worker 相关错误。

训练过程中终端会滚动输出 GPU 利用率、当前 epoch、各类 loss、mAP 等指标。我一般盯两个关键指标:一个是box_loss和cls_loss是否稳定下降,另一个是验证集上 mAP@0.5 是否在持续上升。如果训练到一半 loss 不再下降甚至回升,就要考虑调学习率或者回退到上一个检查点。

4.3 超参数:决定收敛质量的几个关键项

YOLOv5 的超参数集中在data/hyps/hyp.scratch.yaml里。毕设场景不需要动太多,专注调整几个影响最明显的项目即可。

超参数默认值影响说明
lr00.01初始学习率,数据量小时调小到 0.001 更稳
lrf0.01最终学习率因子,控制学习率衰减终点
mosaic1.0是否用 mosaic 增强,显存不足时设 0
fl_gamma0.0焦点损失参数,类别不平衡时设为 0.5-1.0

垃圾分类数据集的常见问题是类别分布不均衡,比如“其他垃圾”里的物品形态杂、样本多,但某些类别样本极少。对于样本很少的类别,靠增加图片数量比调超参数更有效,因为过采样策略比损失函数调整更直观。如果实在收集不到图片,则可以裁剪已有图片做数据扩充——注意是裁剪后作为新样本加入训练集,而不是简单的翻转和旋转。

另一个常被忽视的参数是--cache,加在 train.py 参数里可以把图片预加载到内存,训练提速明显,但显存小的机器慎用。磁盘速度慢、图片数量多的情况下,这个参数带来的收益很可观。

4.4 训练产物:weights和曲线图怎么读

训练完成后,runs/train/exp目录下有几个必须搞清楚的文件。weights/best.pt是验证集上 mAP 最高的权重,部署时选它;weights/last.pt是最后一个 epoch 的权重,断点续训时用它。results.png汇总了所有训练曲线,包括损失函数曲线、精确率、召回率和 mAP 曲线。

看 results.png 时有个技巧是看验证集 loss 曲线是否出现拐点。训练集 loss 持续下降、验证集 loss 却掉头向上的那个位置,就是过拟合开始的信号,最佳权重往往就在拐点之前。best.pt 选择的是 mAP 最高的 epoch,这个权重不一定和损失拐点完全一致,但通常处于同一区间。

另一个常见困惑是训练结束后有多份 exp 目录。跑多次训练会生成 exp、exp2、exp3 这样的递增目录,注意确认 weights 属于哪一次实验,别在部署时拿错权重。我一般会在训练结束后直接复制一份 best.pt 到项目根目录并改名,避免后面被 runs 目录下的多个 exp 搞混。

5. 避坑:垃圾分类训练中六个真实翻车现场

5.1 CUDA和PyTorch版本不匹配:一跑就报CUDA error

现象:执行torch.cuda.is_available()输出 True,但一跑 train.py 就报CUDA error: no kernel image is available for execution on the device。

原因:这个报错说明 PyTorch 编译时的 CUDA 版本和当前显卡驱动支持的版本不一致,通常是显卡驱动版本太老,或者 PyTorch 的 CUDA 版本高于驱动能支持的版本。比如装了 cu121 的 PyTorch,驱动却是老版本。

解决:先查nvidia-smi确认驱动支持的 CUDA 版本,然后到 PyTorch 官网安装和驱动匹配的版本。驱动老就直接pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117降级。从这个坑之后我养成了习惯:每次装完环境先把训练跑起来再去做别的。

5.2 归一化坐标全为0或超出范围:VOC转YOLO时数据全废

现象:转换脚本跑完没有报错,但打开 txt 文件发现坐标要么都是 0,要么出现 1.5 这样的越界值。训练时 loss 直接飞起,模型根本学不进去。

原因:大部分情况是读 XML 时图片尺寸取错。YOLOv5 的图片读取和 labelImg 的尺寸记录方式不同,导致归一化时除以错误的宽高,坐标全部错位。

解决:转换后在脚本里加入校验逻辑,凡是坐标小于 0 或大于 1 的直接抛异常,不要默默写进 txt。同时抽样对比原图和标注框,写一个简单的可视化脚本,把检测框画到图上人工过一遍。这个过程虽然琐碎,但能提前拦下大部分数据问题。

5.3 mosaic增强导致显存溢出:连640分辨率也救不了

现象:batch-size 设 8 还是 OOM,报RuntimeError: CUDA out of memory。

原因:YOLOv5 默认开启了 mosaic 增强,这个增强会把 4 张图拼成 1 张参与训练,相当于单样本实际显存消耗是肉眼看到的 4 倍。小显存显卡不开 mosaic 反而跑得动。

解决:在 train.py 参数里加--mosaic 0关闭 mosaic,或者把 batch-size 降到 4。更折中的方案是保留 mosaic 但调小--imgsz到 512。垃圾分类的检测目标一般不大,imgsz 降到 512 对精度的影响在可接受范围内。

5.4 训练loss不降:学习率太大或类别不平衡

现象:跑了 20 个 epoch,box_loss 始终在 0.08 左右抖动,cls_loss 纹丝不动,mAP 曲线基本贴着 0。

原因:分类 loss 不下降的最常见原因是主线清楚、垃圾类别间相似度太高,比如瓶装可乐和易拉罐外形接近,模型始终分不清;另一个常见原因是正负样本比例失衡,某些类别样本太少,模型干脆把所有样本都预测为多数类。

解决:如果确认数据没问题,先把lr0从 0.01 改成 0.001 重新训练。如果依然不降,集中检查少数类别的标注框——样本少的类别逐一肉眼复核,别轻易依赖自动标注工具。

5.5 mAP很高但实测效果差:数据分布和真实场景脱节

现象:验证集 mAP@0.5 到 0.9 以上,结果用摄像头一拍,满屏漏检,连塑料瓶都找不到。

原因:这是最典型的过拟合变体。训练集中大量图片是网上下载的干净背景,目标大、光线好、角度正,而真实场景是桌面、地面、垃圾桶里的垃圾,光线变化大、目标互相遮挡,分布完全不在一个维度。

解决:去真实场景补拍一批图片并加入训练集,或者把一张大图裁切成多张小图来扩充场景多样性。这个坑提醒我明白了一点:验证集指标只是参考,模型最终是为了应对真实环境,而不是刷分。

5.6 训练时best.pt一直没有更新:mAP曲线异常

现象:训练跑完了,但 best.pt 一直停留在前几个 epoch,后续 mAP 不再刷新。

原因:学习率衰减过快,模型在后期基本停止更新;或者验证集数据太少,mAP 波动巨大,偶尔一个高值被锁定后再也没机会刷新。

解决:检查lr0和lrf的设置,数据量足够时把lrf调小让后半程还在微调;验证集图片太少时,增加验证集规模。从这之后我每次训练结束后都会看一眼 best.pt 的时间戳,确认保存顺序没有异常。

6. 部署与验证:把模型接到摄像头做实时检测

6.1 ONNX导出与OpenCV DNN推理

PyTorch 训练出的模型文件不能直接部署到嵌入式设备或移动端,导出为 ONNX 格式是为了让推理摆脱 PyTorch 环境依赖。ONNX 是一个通用的模型交换格式,之后用 OpenCV DNN 或 ONNXRuntime 都能加载。

python export.py --weights best.pt --include onnx --imgsz 640

在best.pt的同级目录会生成best.onnx。这一步的作用是把模型的结构和权重序列化到 ONNX 格式,导出时--imgsz必须和训练时的分辨率一致,否则会报维度错误或导致精度下降。

如果不想换推理后端,直接用 PyTorch 的torch.hub加载本地的 best.pt 也可以,但每次启动都要花时间加载权重,且依赖 torch 环境。ONNX 模型的优势是体积更小、在无 GPU 的环境也能跑 CPU 推理。垃圾分类场景不追求高帧率,CPU 推理一两百毫秒的延迟完全可接受。

6.2 摄像头实时检测脚本

把 ONNX 接到摄像头前,先跑一下单张图片确认导出模型行为正常,别直接上摄像头,要不问题定位困难。一个简单的摄像头推理循环长这样:

import cv2 import onnxruntime as ort sess = ort.InferenceSession("best.onnx") input_name = sess.get_inputs()[0].name cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() img = cv2.resize(frame, (640, 640)) img = img[:, :, ::-1].transpose(2, 0, 1) / 255.0 img = img[None].astype("float32") outputs = sess.run(None, {input_name: img}) # 解析outputs中的检测结果并绘制框 cv2.imshow("garbage detection", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break

代码里把 BGR 转 RGB、归一化到 0-1、加 batch 维度的三步预处理,每一步做错结果都会异常。ONNX 输出的是原始检测头数据,需要按 YOLOv5 的格式解码候选框,再叠加 NMS。建议直接用官方 detect.py 里对应的 utils 函数,别自己硬写,很多细节只有踩过坑才知道。

从那以后我每次做检测类项目,都会强制走一遍这个流程:先单张图片验证、再视频验证、最后部署。少一步,都可能让你在摄像头前对着满屏的误检框发呆。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 12:46:28

3ds Max安装循环重启原因排查与五步修复完整指南

1. 先说结论:循环重启到底是什么在“循环”装3ds Max装到一半,电脑毫无征兆地重启。屏幕一黑,和你一起回到了系统初始状态。你盯着桌面,安装器自己弹出来,进度条走几步,又黑屏重启。反复三五轮之后&#xf…

作者头像 李华
网站建设 2026/9/26 12:46:27

Jev大模型3500万围观:API接入、Token优化与十大玩法实操指南

1. 这个模型为什么突然被3500万人围观Jev模型这波热度来得挺猛,3500万围观量放在整个大模型圈子里都算现象级。我第一时间去翻了它的官网和社区讨论,发现大家真正兴奋的点不在于参数规模,而在于它把"大模型能力"和"轻量接入&q…

作者头像 李华
网站建设 2026/9/26 12:46:13

SDN园区网络自动化实战:Python+Ryu+Mininet流表配置

简介:这套资料包面向计算机网络、人工智能、通信工程、电子信息等专业的学生与研究者,聚焦SDN园区网络构建与配置实战。项目基于Ubuntu与Mininet仿真环境,涵盖中小规模网络拓扑设计、设备安装与参数配置、节点全互联通信,并实现SD…

作者头像 李华
网站建设 2026/9/26 12:46:10

区域综合能源系统双层优化调度与需求响应Matlab实现

这段时间又帮人复现了一篇关于“计及需求响应的区域综合能源系统双层优化调度策略”的核心期刊论文,顺手把整个思路和踩坑过程整理一遍。这种复现任务在研究生阶段特别常见,尤其是和 区域综合能源系统、需求响应、双层优化调度、Matlab 代码实现 相关的论…

作者头像 李华
网站建设 2026/9/26 12:45:10

SAP HANA SQLScript 条件断点深度解析,从循环精准调试到复杂业务问题定位

在 SAP HANA 数据库中排查 SQLScript 存储过程的逻辑问题时,我们经常会遇到一种棘手的情况。存储过程本身能够正常执行,没有 SQL 语法错误,也没有抛出数据库异常,但最终计算出来的业务数据却不符合预期。 以企业销售订单的批量计算为例,一个存储过程可能需要处理数万条销…

作者头像 李华
网站建设 2026/9/26 12:44:43

Cursor 汉化教程:Windows 下三步切换中文界面

1. 为什么值得花十分钟把 Cursor 换成中文界面Cursor 这个编辑器这两年在开发者圈子里火得很快,它把 AI 补全、对话式改代码、整仓理解这些能力直接塞进了一个类 VS Code 的壳子里,用起来确实顺手。但很多人第一次装完打开就懵了——菜单、设置项、右键菜…

作者头像 李华