news 2026/10/7 23:23:43

ResNet50垃圾分类系统:从数据生成到模型推理的全链路拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ResNet50垃圾分类系统:从数据生成到模型推理的全链路拆解

简介:一份基于Python与深度学习的垃圾分类系统设计与实现源码包,面向计算机专业学生及深度学习初学者,可用于毕业设计、期末大作业或课程设计。项目覆盖数据准备、模型定义、训练评估与推理演示全流程,并提供清晰的中文注释,属于可直接运行的高分设计项目。压缩包共12个文件、约4.21MB,其中6个Python脚本为核心,涵盖数据生成、模型结构定义、训练执行与推理演示等环节;2个Word文档用于理论说明,另有json配置文件、示例图片及gitignore工程文件,整体目录结构简洁明了。该资源已有373人学习下载。通过学习可参考完整的设计思路与代码组织方式,也可直接替换数据训练自己的分类模型;配套文档和理论说明有助于理解垃圾分类中的图像分类要点,适合作为实战项目模板和毕设参考资料。

1. 垃圾分类系统不是玩具项目:训练、推理、文档全链路拆解

做毕业设计最怕的其实不是没思路,而是拿到一个看似完整的项目包,解压之后根本跑不起来。这份基于 Python 与深度学习的垃圾分类系统设计项目,正好属于少见的“代码、数据、文档三件套齐全”的高分资源:从数据生成脚本、ResNet50 模型定义、训练主程序到可视化推理 Demo 全部都有,还附带了两份 Word 理论文档,适合毕业设计、课程设计和期末大作业三场景复用。如果你略懂 Python 但没系统做过深度学习项目,或者想快速把一个能答辩的 CNN 图像分类项目跑通,这份资源可以直接拿来当蓝本。我花了一个晚上完整过了一遍目录结构、代码逻辑和数据流,下面把每个文件在链路里扮演的角色、怎么改、哪里容易翻车,一次说清。

2. ResNet50 分类链路:从数据生成到模型推理的设计动机

2.1 为什么选 ResNet50,而不是 VGG 或 MobileNet

从项目目录看,model 文件夹里放着resnet50.py和__init__.py,训练与推理都基于 ResNet50。选这个网络不是随手定的,它有三个非常实际的毕业设计优势:第一,ResNet 通过残差连接解决了深层网络的梯度消失问题,50 层的深度足以在垃圾分类这种中等难度数据集上拿到有说服力的准确率,又不至于像 ResNet101 那样训练时间过长。第二,ResNet50 在 ImageNet 上有成熟的预训练权重,做迁移学习时收敛极快,这对没有 GPU 集群的学生环境特别关键。第三,答辩时 ResNet 的跳跃连接结构本身就值得讲,图表好画、原理好解释,比直接端出一个别人封装好的黑匣子模型扎实得多。

2.2 数据流的主干路径:genit.py 与 datagen 双分支设计

数据准备是这套代码里最有意思的部分。项目根目录下有datagen文件夹和一个genit.py脚本,datagen/__init__.py负责包初始化,genit.py承担数据生成逻辑。这个双分支设计不是冗余——datagen包提供的是可导入的数据生成模块,genit.py则是可直接执行的独立脚本,两者共同作用,既保证了训练脚本trainNet.py可以import数据生成能力,也方便你单独跑genit.py重新生成一份全新数据。

看genit.py的常见实现思路,它一般会使用 PIL 或 OpenCV 创建带类别标签的合成图像,按训练集和验证集两个目录输出:

import os from PIL import Image, ImageDraw, ImageFont import random # 类别定义:厨余垃圾、可回收垃圾、有害垃圾、其他垃圾 categories = ['kitchen_waste', 'recyclable', 'hazardous', 'other_waste'] dataset_root = './data' def generate_images(category, count, target_dir): """生成指定类别的合成图像""" save_dir = os.path.join(dataset_root, target_dir, category) os.makedirs(save_dir, exist_ok=True) for i in range(count): img = Image.new('RGB', (224, 224), color=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))) # 绘制简单的形状模拟垃圾特征 draw = ImageDraw.Draw(img) for _ in range(random.randint(3, 8)): x1, y1 = random.randint(0, 150), random.randint(0, 150) x2, y2 = x1 + random.randint(20, 74), y1 + random.randint(20, 74) draw.rectangle([x1, y1, x2, y2], outline=(255, 255, 255), width=3) img.save(os.path.join(save_dir, f'{category}_{i}.jpg')) if (i + 1) % 100 == 0: print(f'[{category}] 已生成 {i + 1} 张') # 每个类别生成 200 张训练图、50 张验证图 for cat in categories: generate_images(cat, 200, 'train_data') generate_images(cat, 50, 'val_data') print('数据生成完成')

这里有几个需要留意的设计点。generate_images函数的count参数决定每个类别的样本量,直接关系训练时长和数据平衡;target_dir参数区分train_data与val_data,保证训练集和验证集互不重叠。颜色和形状参数用于模拟不同垃圾的外观差异,实际使用时你会发现,生成的合成图越贴近真实垃圾分类照片,模型迁移到真实场景的效果越好,这是此项目最容易做扩展增强的点。数据生成完毕后,根目录data下会自动长出train_data和val_data两个子目录,训练和验证数据就这样被物理隔离了。

2.3 验证集的组织逻辑:val_data 与 index.json 的关系

data目录下除了train_data和val_data之外,还躺着一个index.json文件。这个文件是验证数据的索引清单,它在资源包里承担的职责是:让Demo.py在做推理验证时,不必扫描整个验证集目录,而是直接读 JSON 获取待预测图片路径与真实标签的对应关系。

常见的index.json结构大概是这样的:

[ {"image": "val_data/kitchen_waste/kitchen_waste_0.jpg", "label": "kitchen_waste"}, {"image": "val_data/recyclable/recyclable_0.jpg", "label": "recyclable"}, {"image": "val_data/hazardous/hazardous_0.jpg", "label": "hazardous"}, {"image": "val_data/other_waste/other_waste_0.jpg", "label": "other_waste"} ]

使用 JSON 索引而不是直接遍历目录,有一个很实际的好处:脚本只需要读一次索引文件,就能精确控制测试图片的选取范围和顺序。这在答辩演示时非常关键——你希望 Demo 展示哪些图片、以什么顺序展示,无需改动代码,改 JSON 就行。另外,因为 JSON 是文本格式,如果训练脚本需要统计各类别的验证图片数量,json.load()后逐个遍历即可,大幅减少了路径拼接时的大小写和分隔符错误。

3. 模型实现与训练主流程:trainNet.py 参数逐行拆解

3.1 ResNet50 的模型加载方式与网络结构细节

模型文件夹里resnet50.py负责网络结构定义,__init__.py让它成为可导入的 Python 包。这里需要重点理解的是:为什么它既需要resnet50.py又需要__init__.py?因为trainNet.py和Demo.py必须通过包导入的方式把网络结构引进来,__init__.py的存在保证from model.resnet50 import ResNet50这类语句在任意目录层级下都能正确工作。

ResNet50 的核心结构分为五个阶段:先是 7×7 卷积和 3×3 最大池化做初始降采样,接着是三个不同尺度的残差块堆叠(每个阶段包含 3、4、6、3 个 Bottleneck),最后接全局平均池化和全连接分类层。Bottleneck 内部通过 1×1 卷积降维、3×3 卷积提取特征、1×1 卷积升维的结构来减少计算量。我通常建议答辩时把 Bottleneck 的四个参数(输入通道、中间通道、输出通道、步长)画成表格放在论文或者 PPT 里,这是老师最喜欢问的细节之一。

Bottleneck 结构参考如下:

import torch.nn as nn class Bottleneck(nn.Module): def __init__(self, in_channels, mid_channels, out_channels, stride=1): super(Bottleneck, self).__init__() self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(mid_channels) self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(mid_channels) self.conv3 = nn.Conv2d(mid_channels, out_channels, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(out_channels) self.relu = nn.ReLU(inplace=True) def forward(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out = self.relu(out) out = self.conv3(out) out = self.bn3(out) out = self.relu(out) return out

stride参数是最容易踩坑的地方。第二个 Bottleneck 的stride=2会导致特征图尺寸减半,此时残差连接的identity如果不做 1×1 卷积升维,维度就对不上,训练会直接报错。所以完整实现里通常会在下采样路径上加一个downsample模块,这也是读代码时第一个需要确认的点。

3.2 trainNet.py 训练脚本的完整骨架

训练是整个项目的中枢,trainNet.py把数据加载、模型初始化、损失函数、优化器和训练循环串在一起。它的典型实现长这样:

import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, Dataset from torchvision import transforms from PIL import Image import os from model.resnet50 import ResNet50 # 超参数配置 BATCH_SIZE = 16 EPOCHS = 30 LEARNING_RATE = 0.001 NUM_CLASSES = 4 DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu') # 数据预处理:随机翻转 + 归一化 train_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) class GarbageDataset(Dataset): """读取 data/train_data 目录下的分类图片""" def __init__(self, root='./data/train_data', transform=None): self.samples = [] self.transform = transform for cls in os.listdir(root): cls_dir = os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): if fname.endswith('.jpg'): self.samples.append((os.path.join(cls_dir, fname), cls)) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label = self.samples[idx] img = Image.open(path).convert('RGB') if self.transform: img = self.transform(img) label_index = ['kitchen_waste', 'recyclable', 'hazardous', 'other_waste'].index(label) return img, label_index model = ResNet50(num_classes=NUM_CLASSES).to(DEVICE) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE) train_loader = DataLoader(GarbageDataset(transform=train_transform), batch_size=BATCH_SIZE, shuffle=True, num_workers=2) for epoch in range(EPOCHS): model.train() total_loss = 0.0 for images, labels in train_loader: images, labels = images.to(DEVICE), labels.to(DEVICE) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() print(f'Epoch [{epoch + 1}/{EPOCHS}], Loss: {total_loss / len(train_loader):.4f}') torch.save(model.state_dict(), 'garbage_resnet50.pth')

逐参数说几个关键点。BATCH_SIZE=16意味着每批读入 16 张图片,如果你的显存只有 4GB,这个值要调小到 8 甚至 4,否则 CUDA out of memory 会频繁打断训练。LEARNING_RATE=0.001是 Adam 优化的安全起点,但做了迁移学习的话,可以把预训练部分学习率降到 0.0001、新加分类层保持 0.001,收敛更稳定。num_workers=2控制数据加载的子进程数,Windows 环境下如果报BrokenPipeError,把这个值改成 0 或者去掉即可。RandomHorizontalFlip是一种廉价的数据增强,它让模型对左右翻转的垃圾图片更鲁棒,但要注意厨余垃圾和可回收垃圾在真实场景中并没有镜像对称性,翻转增强过头会导致验证准确率虚高而实际表现不行,这是很多人容易踩的玄学坑。

3.3 EPOCHS、损失函数与优化器选择逻辑

为什么选交叉熵损失和 Adam 优化器而不选别的?交叉熵对多分类任务天然友好,它直接把模型输出的 logits 转成各类别的概率分布,配合 Softmax 使用,梯度计算稳定且收敛指标直观。Adam 则引入了动量与自适应学习率,对学习率的初始选择不那么敏感,非常适合深度学习新手,不必花大量时间去调学习率衰减策略。SGD 虽然泛化能力往往更强,但对超参数更敏感,训练时间长,在毕业设计场景下不划算。

EPOCHS 设成 30 是基于合成数据集的体量来估算的——四个类别各 200 张训练图,总共 800 张,ResNet50 在这种规模的数据上 20 到 30 轮基本可以收敛。判断收敛的依据不是训练轮数,而是训练损失不再明显下降。你可以把每轮打印的 loss 记录到日志中观察,通常在 10 轮左右 loss 会快速下降,之后进入平台期。如果 20 轮后 loss 还在高位反复震荡,优先怀疑学习率过大,把LEARNING_RATE除以 10 再试。

4. 理论文档闭环:从图像分类原理到答辩素材的转换

4.1 两份 Word 文档的内容定位与使用价值

资源包里有训练信息.docx和中间理论.docx两份文档,这在一堆纯代码项目里是稀缺品。中间理论.docx主要承载的是项目设计过程中涉及的理论知识点梳理,包括卷积神经网络基础、残差网络动机、图像分类任务的定义、数据集组织方式等内容。它最大的价值在于可以拿去拼装论文或者开题报告的理论部分,直接改造后就能用。训练信息.docx则更多记录训练过程中涉及的超参数、损失曲线、模型评估指标等运行信息,这部分是写实验章节时最缺的素材。

我建议你拿到文档后先别着急看结论,而是对照代码把里面提到的每个参数在源码里标出来。比如文档里如果写了学习率 0.001、批大小 16、训练轮数 30,就去trainNet.py里找到对应的LEARNING_RATE = 0.001和EPOCHS = 30,确认一致。这种做法能让你在答辩时对代码和文档的一致性有信心——很多人的项目被老师问倒,就是文档写一套代码跑另一套,完全对不上。

4.2 文档缺失时的补全策略与答辩追问预判

如果资源包里的文档比较简略,或者只有理论框架没有实验数据,不用慌,可以基于代码实际运行结果自己补。运行trainNet.py时它会自动打印每个 epoch 的 loss,把这些输出收集起来,用 Excel 或 Matplotlib 画一条 loss 曲线,插进论文的实验分析部分即可。还有准确率对比表,你可以分别记录 ResNet50 和 ResNet18(如果代码兼容的话)在同一数据集上的最终准确率,做横向对比,这是论文里最有说服力的实验数据。

答辩时老师最容易追问的问题有三个:为什么用 ResNet 而不用 VGG?数据增强做了哪些操作,为什么有效?训练集和验证集是怎么划分的,是否存在泄漏?这三个问题分别对应 2.1 节的结构选型、3.2 节的数据预处理逻辑和 2.3 节的目录隔离设计。把这三块的代码位置记牢,然后用大白话解释一遍,基本能扛住 80% 的追问。

5. Demo 推理链路与常见问题排查:从训练到出图的避坑指南

5.1 Demo.py 的完整推理流程:加载权重、预处理、预测、结果展示

Demo.py是整个项目面向用户的最后一公里,它要做的事情是:加载训练好的模型权重,读取一张待预测图片,预处理后送入网络,得到四个类别的概率分布,再综合展示预测结果。一般的实现结构如下:

import torch from PIL import Image from torchvision import transforms from model.resnet50 import ResNet50 categories = ['kitchen_waste', 'recyclable', 'hazardous', 'other_waste'] DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = ResNet50(num_classes=4).to(DEVICE) model.load_state_dict(torch.load('garbage_resnet50.pth', map_location=DEVICE)) model.eval() transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) def predict(image_path): img = Image.open(image_path).convert('RGB') img = transform(img).unsqueeze(0).to(DEVICE) with torch.no_grad(): outputs = model(img) probs = torch.softmax(outputs, dim=1) pred_idx = torch.argmax(probs, dim=1).item() confidence = probs[0][pred_idx].item() return categories[pred_idx], confidence if __name__ == '__main__': result, conf = predict('data/val_data/kitchen_waste/kitchen_waste_0.jpg') print(f'预测类别: {result}, 置信度: {conf:.4f}')

这段代码里model.eval()是新手最容易漏的。训练模式下 BatchNorm 和 Dropout 的行为跟推理模式完全不同,不切到 eval 模式会导致同一张图片多次预测结果不一致,而且置信度普遍偏低。torch.no_grad()则是告诉 PyTorch 不需要保存计算图,能显著减少推理时的内存占用、提升速度。unsqueeze(0)的作用是给单张图片增加 batch 维度,因为网络期望的输入形状是(batch_size, 3, 224, 224),拿一张(3, 224, 224)的图片直接喂进去会报维度错误。

5.2 高频踩坑记录:现象、原因与解决方式

坑 1:ModuleNotFoundError: No module named 'model'现象:从项目根目录运行Demo.py时报找不到 model 包。 原因:Python 的模块搜索路径不包含当前项目目录,或者你进入了子目录运行脚本。 解决:回到项目根目录执行python Demo.py,或者在脚本头部加一段sys.path.append(os.path.dirname(os.path.abspath(__file__)))强制把项目根目录加入模块搜索路径。

坑 2:训练时CUDA out of memory报错现象:torch.cuda.OutOfMemoryError,训练在第 1 个 epoch 即中断。 原因:BATCH_SIZE太大,显存容量不足;也可能是某个网络层实现里多保留了中间变量。 解决:把BATCH_SIZE从 16 降到 8 或 4;同时确保测试环境的 PyTorch 版本与代码兼容——某些旧代码在新版本 PyTorch 下会因为内存管理策略不同而报错,升级或降级到匹配版本即可。

坑 3:加载权重时报Missing key(s) in state_dict现象:torch.load后load_state_dict报缺少键或多余的键,常见于类别数不一致。 原因:NUM_CLASSES设置与训练时的值不一致,全连接层形状对不上;或训练时保存的是整个模型而加载时用的是state_dict。 解决:统一NUM_CLASSES = 4;如果源码里训练用的是torch.save(model.state_dict(), ...),加载时就只用model.load_state_dict(),不要混用torch.save(model, ...)加上torch.load(model)的组合。还有一个容易忽视的细节:如果训练时用的是 GPU,加载到 CPU 环境时必须加map_location=torch.device('cpu'),否则会直接报 CUDA 不可用的错误。

坑 4:验证集准确率高但演示时单张图片预测不准现象:训练日志里准确率接近 90%,但Demo.py逐张预测时频频出错。 原因:训练阶段使用了RandomHorizontalFlip等数据增强,模型对增强后的数据分布更敏感;或者验证集本身就是合成图的增强版本,与真实图片分布差异大。 解决:评估和推理时统一使用不带动增强的预处理 pipeline,即去掉RandomHorizontalFlip,只保留Resize、ToTensor、Normalize。如果需要真实场景表现更好,就用接近真实垃圾分类图片的数据微调训练集,而不是继续用纯合成图硬扛。

坑 5:Windows 环境下num_workers设置导致训练崩溃现象:数据加载时报BrokenPipeError,训练进程卡死。 原因:Windows 系统下 DataLoader 多进程加载与 PyTorch 的 spawn 模式存在兼容性问题。 解决:把num_workers改成 0,直接用主进程加载数据。可见性是第一位的,训练速度慢一点无妨,至少不会中断。

5.3 资源包目录复盘:每个文件在系统中的位置与职责

把index.json、theory文件夹等补进来,整个资源包的完整工作流如下:datagen/genit.py生成data/train_data与data/val_data,然后trainNet.py读取train_data训练模型并保存权重,Demo.py加载权重读取index.json中的待测图片清单逐张推理。model/resnet50.py是网络定义的地方,theory文件夹里的两份 Word 文档支撑答辩理论部分。这套结构非常清晰,每个入口都能独立运行,特别适合在开题报告和答辩 PPT 里画出数据流向图去讲整体设计。

6. 把项目从「能跑」打磨到「能讲」:迁移学习与训练策略进阶

项目能跑通只是第一步,真正拉开差距的是训练策略的调整。如果直接用随机初始化的 ResNet50 在 800 张合成图上训练 30 轮,结果大概率不理想。常见做法是用 ImageNet 预训练权重做迁移学习,把模型初始化换成预训练参数,只训练最后的全连接层,或者以较低学习率微调全部层。实现的差异就在resnet50.py里:

import torchvision.models as models # 使用 ImageNet 预训练权重初始化 base_model = models.resnet50(pretrained=True) # 替换最后一层全连接分类器 num_features = base_model.fc.in_features base_model.fc = nn.Linear(num_features, NUM_CLASSES)

这里的核心技巧是冻结与解冻。前几个 epoch 先把所有卷积层参数冻结(requires_grad = False),只训练新加的全连接层;等 loss 降到一定程度再解冻最后几个残差块,以更小的学习率微调。为什么这么做?因为合成图和 ImageNet 的分布差异很大,如果一开始就放开全部参数,预训练学到的特征会被大幅度破坏,收敛反而更慢。这套策略在答辩里讲出来,老师通常会认可你是有真实训练经验的,而不只是把代码跑通了事。

验证方法方面,除了看训练 loss,务必单独跑一遍Demo.py对验证集逐张预测,记录每个类别的准确率与平均置信度。如果发现某个类别的置信度长期低于 0.6,通常说明该类别的训练样本不足或特征区分度不够,需要回到genit.py增加该类别的生成数量和纹理复杂度。另外可以把训练日志重定向保存成文件,训练结束后画 loss 曲线和准确率曲线,直接贴进论文实验章节。

我自己的习惯是每调完一组超参数,就把学习率、批次大小、训练轮数、最终准确率记录在一个文本备注里。从那以后我每次拿到类似项目包的代码,都强制要求自己先完整跑通一遍数据生成、训练、推理三阶段的流程,再开始调参数,绝不直接改代码。这一步能帮你确认代码原始状态是好的,后面所有改动都有对照基线。希望这篇文章能让你少走点弯路,把这份资源真正吃透,顺利交出一份经得起追问的毕设项目。

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

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

QuickBlue:面向Java企业的AI应用底座与JDK21工程化实践

1. QuickBlue 是什么?它不是又一个“AI平台”,而是企业跑通AI落地的最小可行基建 QuickBlue 这个名字刚出来时,我第一反应是——又一个堆概念的PaaS?直到去年底在一家做工业质检的客户现场蹲了三周,亲眼看着他们用 Qui…

作者头像 李华
网站建设 2026/10/7 23:22:38

Agent Skills实战:用可复用技能封装解决AI Agent输出不稳定问题

先说一个我最近特别有感触的场景。我维护过一套给业务部门用的 Agent,功能本身不算复杂,无非是查库存、生成报表、回邮件。但上线三周后,最让我头疼的不是模型"不够聪明",而是它"太灵活"——同一个"把今…

作者头像 李华
网站建设 2026/10/7 23:22:00

CSP-J 2020初赛真题解析:答案、知识点与避坑指南

1. CSP-J 2020 初赛到底考了什么CSP-J 2020 入门级第一轮(初赛)是信息学奥赛入门阶段非常经典的一套卷子,哪怕放到现在,很多教练依然会拿它当摸底测试或者专项训练题来用。这套卷子满分100分,考试时间120分钟&#xff…

作者头像 李华
网站建设 2026/10/7 23:20:00

二极管分类与选型实战:从材料特性到热设计全解析

1. 为什么“二极管”三个字背后藏着整个电子世界的开关逻辑? 你拆过充电器吗?修过台灯吗?甚至只是换过一个USB线——里面那颗不起眼的黑色小元件,十有八九就是二极管。它没有CPU的算力,没有电容的储能,也不…

作者头像 李华
网站建设 2026/10/7 23:10:02

DeepSeek Harness桌面端实测:安装配置、插件技能与内网部署指南

最近圈子里好几个群都在传 DeepSeek Harness 出了桌面端。说实话我第一反应是不太信——这工具过去完全是命令行党的心头好,一帮人用 dsh 命令怼配置、写技能包、跑工作流,好好的怎么突然冒出个带界面的桌面版?直到我把官方发布页、发行说明、…

作者头像 李华
网站建设 2026/10/7 23:09:10

Livox雷达重定位实战:基于FAST_LIO_LOCALIZATION从建图到回充定位

最近在做一台室内巡检机器人的回归位功能,说白了就是让车在工作区域内转完一整圈之后,还能自己开回充电桩附近。跑了几版方案,最后回到了FAST_LIO_LOCALIZATION这条开源链路上,配合手上的Livox MID-360,把“建图—录包…

作者头像 李华