昇思第18天打卡|ShuffleNet圖像分類

ShuffleNet網絡介紹

ShuffleNetV1是曠視科技提出的一種計算高效的CNN模型,和MobileNet, SqueezeNet等一樣主要應用在移動端,所以模型的設計目標就是利用有限的計算資源來達到最好的模型精度。ShuffleNetV1的設計核心是引入了兩種操作:Pointwise Group Convolution和Channel Shuffle,這在保持精度的同時大大降低了模型的計算量。因此,ShuffleNetV1和MobileNet類似,都是通過設計更高效的網絡結構來實現模型的壓縮和加速。

了解ShuffleNet更多詳細內容,詳見論文ShuffleNet。

如下圖所示,ShuffleNet在保持不低的準確率的前提下,將參數量幾乎降低到了最小,因此其運算速度較快,單位參數量對模型準確率的貢獻非常高。

shufflenet1

圖片來源:Bianco S, Cadene R, Celona L, et al. Benchmark analysis of representative deep neural network architectures[J]. IEEE access, 2018, 6: 64270-64277.

模型架構

ShuffleNet最顯著的特點在于對不同通道進行重排來解決Group Convolution帶來的弊端。通過對ResNet的Bottleneck單元進行改進,在較小的計算量的情況下達到了較高的準確率。

Pointwise Group Convolution

Group Convolution(分組卷積)原理如下圖所示,相比于普通的卷積操作,分組卷積的情況下,每一組的卷積核大小為in_channels/g*k*k,一共有g組,所有組共有(in_channels/g*k*k)*out_channels個參數,是正常卷積參數的1/g。分組卷積中,每個卷積核只處理輸入特征圖的一部分通道,其優點在于參數量會有所降低,但輸出通道數仍等于卷積核的數量

shufflenet2

圖片來源:Huang G, Liu S, Van der Maaten L, et al. Condensenet: An efficient densenet using learned group convolutions[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2018: 2752-2761.

Depthwise Convolution(深度可分離卷積)將組數g分為和輸入通道相等的in_channels,然后對每一個in_channels做卷積操作,每個卷積核只處理一個通道,記卷積核大小為1*k*k,則卷積核參數量為:in_channels*k*k,得到的feature maps通道數與輸入通道數相等

Pointwise Group Convolution(逐點分組卷積)在分組卷積的基礎上,令每一組的卷積核大小為?1×11×1,卷積核參數量為(in_channels/g*1*1)*out_channels。

%%capture captured_output
# 實驗環境已經預裝了mindspore==2.2.14,如需更換mindspore版本,可更改下面mindspore的版本號
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.2.14
# 查看當前 mindspore 版本
!pip show mindsporefrom mindspore import nn
import mindspore.ops as ops
from mindspore import Tensor
?
class GroupConv(nn.Cell):def __init__(self, in_channels, out_channels, kernel_size,stride, pad_mode="pad", pad=0, groups=1, has_bias=False):super(GroupConv, self).__init__()self.groups = groupsself.convs = nn.CellList()for _ in range(groups):self.convs.append(nn.Conv2d(in_channels // groups, out_channels // groups,kernel_size=kernel_size, stride=stride, has_bias=has_bias,padding=pad, pad_mode=pad_mode, group=1, weight_init='xavier_uniform'))
?def construct(self, x):features = ops.split(x, split_size_or_sections=int(len(x[0]) // self.groups), axis=1)outputs = ()for i in range(self.groups):outputs = outputs + (self.convs[i](features[i].astype("float32")),)out = ops.cat(outputs, axis=1)return out

?

Channel Shuffle

Group Convolution的弊端在于不同組別的通道無法進行信息交流,堆積GConv層后一個問題是不同組之間的特征圖是不通信的,這就好像分成了g個互不相干的道路,每一個人各走各的,這可能會降低網絡的特征提取能力。這也是Xception,MobileNet等網絡采用密集的1x1卷積(Dense Pointwise Convolution)的原因。

為了解決不同組別通道“近親繁殖”的問題,ShuffleNet優化了大量密集的1x1卷積(在使用的情況下計算量占用率達到了驚人的93.4%),引入Channel Shuffle機制(通道重排)。這項操作直觀上表現為將不同分組通道均勻分散重組,使網絡在下一層能處理不同組別通道的信息。

shufflenet3

如下圖所示,對于g組,每組有n個通道的特征圖,首先reshape成g行n列的矩陣,再將矩陣轉置成n行g列,最后進行flatten操作,得到新的排列。這些操作都是可微分可導的且計算簡單,在解決了信息交互的同時符合了ShuffleNet輕量級網絡設計的輕量特征。

shufflenet4

為了閱讀方便,將Channel Shuffle的代碼實現放在下方ShuffleNet模塊的代碼中。

ShuffleNet模塊

如下圖所示,ShuffleNet對ResNet中的Bottleneck結構進行由(a)到(b), (c)的更改:

  1. 將開始和最后的1×11×1卷積模塊(降維、升維)改成Point Wise Group Convolution;

  2. 為了進行不同通道的信息交流,再降維之后進行Channel Shuffle;

  3. 降采樣模塊中,3×33×3?Depth Wise Convolution的步長設置為2,長寬降為原來的一般,因此shortcut中采用步長為2的3×33×3平均池化,并把相加改成拼接。

shufflenet5

class ShuffleV1Block(nn.Cell):def __init__(self, inp, oup, group, first_group, mid_channels, ksize, stride):super(ShuffleV1Block, self).__init__()self.stride = stridepad = ksize // 2self.group = groupif stride == 2:outputs = oup - inpelse:outputs = oupself.relu = nn.ReLU()branch_main_1 = [GroupConv(in_channels=inp, out_channels=mid_channels,kernel_size=1, stride=1, pad_mode="pad", pad=0,groups=1 if first_group else group),nn.BatchNorm2d(mid_channels),nn.ReLU(),]branch_main_2 = [nn.Conv2d(mid_channels, mid_channels, kernel_size=ksize, stride=stride,pad_mode='pad', padding=pad, group=mid_channels,weight_init='xavier_uniform', has_bias=False),nn.BatchNorm2d(mid_channels),GroupConv(in_channels=mid_channels, out_channels=outputs,kernel_size=1, stride=1, pad_mode="pad", pad=0,groups=group),nn.BatchNorm2d(outputs),]self.branch_main_1 = nn.SequentialCell(branch_main_1)self.branch_main_2 = nn.SequentialCell(branch_main_2)if stride == 2:self.branch_proj = nn.AvgPool2d(kernel_size=3, stride=2, pad_mode='same')def construct(self, old_x):left = old_xright = old_xout = old_xright = self.branch_main_1(right)if self.group > 1:right = self.channel_shuffle(right)right = self.branch_main_2(right)if self.stride == 1:out = self.relu(left + right)elif self.stride == 2:left = self.branch_proj(left)out = ops.cat((left, right), 1)out = self.relu(out)return outdef channel_shuffle(self, x):batchsize, num_channels, height, width = ops.shape(x)group_channels = num_channels // self.groupx = ops.reshape(x, (batchsize, group_channels, self.group, height, width))x = ops.transpose(x, (0, 2, 1, 3, 4))x = ops.reshape(x, (batchsize, num_channels, height, width))return x

?

構建ShuffleNet網絡

ShuffleNet網絡結構如下圖所示,以輸入圖像224×224224×224,組數3(g = 3)為例,首先通過數量24,卷積核大小為3×33×3,stride為2的卷積層,輸出特征圖大小為112×112112×112,channel為24;然后通過stride為2的最大池化層,輸出特征圖大小為56×5656×56,channel數不變;再堆疊3個ShuffleNet模塊(Stage2, Stage3, Stage4),三個模塊分別重復4次、8次、4次,其中每個模塊開始先經過一次下采樣模塊(上圖(c)),使特征圖長寬減半,channel翻倍(Stage2的下采樣模塊除外,將channel數從24變為240);隨后經過全局平均池化,輸出大小為1×1×9601×1×960,再經過全連接層和softmax,得到分類概率。

shufflenet6

class ShuffleNetV1(nn.Cell):def __init__(self, n_class=1000, model_size='2.0x', group=3):super(ShuffleNetV1, self).__init__()print('model size is ', model_size)self.stage_repeats = [4, 8, 4]self.model_size = model_sizeif group == 3:if model_size == '0.5x':self.stage_out_channels = [-1, 12, 120, 240, 480]elif model_size == '1.0x':self.stage_out_channels = [-1, 24, 240, 480, 960]elif model_size == '1.5x':self.stage_out_channels = [-1, 24, 360, 720, 1440]elif model_size == '2.0x':self.stage_out_channels = [-1, 48, 480, 960, 1920]else:raise NotImplementedErrorelif group == 8:if model_size == '0.5x':self.stage_out_channels = [-1, 16, 192, 384, 768]elif model_size == '1.0x':self.stage_out_channels = [-1, 24, 384, 768, 1536]elif model_size == '1.5x':self.stage_out_channels = [-1, 24, 576, 1152, 2304]elif model_size == '2.0x':self.stage_out_channels = [-1, 48, 768, 1536, 3072]else:raise NotImplementedErrorinput_channel = self.stage_out_channels[1]self.first_conv = nn.SequentialCell(nn.Conv2d(3, input_channel, 3, 2, 'pad', 1, weight_init='xavier_uniform', has_bias=False),nn.BatchNorm2d(input_channel),nn.ReLU(),)self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')features = []for idxstage in range(len(self.stage_repeats)):numrepeat = self.stage_repeats[idxstage]output_channel = self.stage_out_channels[idxstage + 2]for i in range(numrepeat):stride = 2 if i == 0 else 1first_group = idxstage == 0 and i == 0features.append(ShuffleV1Block(input_channel, output_channel,group=group, first_group=first_group,mid_channels=output_channel // 4, ksize=3, stride=stride))input_channel = output_channelself.features = nn.SequentialCell(features)self.globalpool = nn.AvgPool2d(7)self.classifier = nn.Dense(self.stage_out_channels[-1], n_class)def construct(self, x):x = self.first_conv(x)x = self.maxpool(x)x = self.features(x)x = self.globalpool(x)x = ops.reshape(x, (-1, self.stage_out_channels[-1]))x = self.classifier(x)return x

?

模型訓練和評估

采用CIFAR-10數據集對ShuffleNet進行預訓練。

訓練集準備與加載

采用CIFAR-10數據集對ShuffleNet進行預訓練。CIFAR-10共有60000張32*32的彩色圖像,均勻地分為10個類別,其中50000張圖片作為訓練集,10000圖片作為測試集。如下示例使用mindspore.dataset.Cifar10Dataset接口下載并加載CIFAR-10的訓練集。目前僅支持二進制版本(CIFAR-10 binary version)。

from download import downloadurl = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz"download(url, "./dataset", kind="tar.gz", replace=True)

import mindspore as ms
from mindspore.dataset import Cifar10Dataset
from mindspore.dataset import vision, transformsdef get_dataset(train_dataset_path, batch_size, usage):image_trans = []if usage == "train":image_trans = [vision.RandomCrop((32, 32), (4, 4, 4, 4)),vision.RandomHorizontalFlip(prob=0.5),vision.Resize((224, 224)),vision.Rescale(1.0 / 255.0, 0.0),vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),vision.HWC2CHW()]elif usage == "test":image_trans = [vision.Resize((224, 224)),vision.Rescale(1.0 / 255.0, 0.0),vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),vision.HWC2CHW()]label_trans = transforms.TypeCast(ms.int32)dataset = Cifar10Dataset(train_dataset_path, usage=usage, shuffle=True)dataset = dataset.map(image_trans, 'image')dataset = dataset.map(label_trans, 'label')dataset = dataset.batch(batch_size, drop_remainder=True)return datasetdataset = get_dataset("./dataset/cifar-10-batches-bin", 128, "train")
batches_per_epoch = dataset.get_dataset_size()

?

模型訓練

本節用隨機初始化的參數做預訓練。首先調用ShuffleNetV1定義網絡,參數量選擇"2.0x",并定義損失函數為交叉熵損失,學習率經過4輪的warmup后采用余弦退火,優化器采用Momentum。最后用train.model中的Model接口將模型、損失函數、優化器封裝在model中,并用model.train()對網絡進行訓練。將ModelCheckpointCheckpointConfigTimeMonitorLossMonitor傳入回調函數中,將會打印訓練的輪數、損失和時間,并將ckpt文件保存在當前目錄下。

import time
import mindspore
import numpy as np
from mindspore import Tensor, nn
from mindspore.train import ModelCheckpoint, CheckpointConfig, TimeMonitor, LossMonitor, Model, Top1CategoricalAccuracy, Top5CategoricalAccuracydef train():mindspore.set_context(mode=mindspore.PYNATIVE_MODE, device_target="Ascend")net = ShuffleNetV1(model_size="2.0x", n_class=10)loss = nn.CrossEntropyLoss(weight=None, reduction='mean', label_smoothing=0.1)min_lr = 0.0005base_lr = 0.05lr_scheduler = mindspore.nn.cosine_decay_lr(min_lr,base_lr,batches_per_epoch*250,batches_per_epoch,decay_epoch=250)lr = Tensor(lr_scheduler[-1])optimizer = nn.Momentum(params=net.trainable_params(), learning_rate=lr, momentum=0.9, weight_decay=0.00004, loss_scale=1024)loss_scale_manager = ms.amp.FixedLossScaleManager(1024, drop_overflow_update=False)model = Model(net, loss_fn=loss, optimizer=optimizer, amp_level="O3", loss_scale_manager=loss_scale_manager)callback = [TimeMonitor(), LossMonitor()]save_ckpt_path = "./"config_ckpt = CheckpointConfig(save_checkpoint_steps=batches_per_epoch, keep_checkpoint_max=5)ckpt_callback = ModelCheckpoint("shufflenetv1", directory=save_ckpt_path, config=config_ckpt)callback += [ckpt_callback]print("============== Starting Training ==============")start_time = time.time()# 由于時間原因,epoch = 5,可根據需求進行調整model.train(5, dataset, callbacks=callback)use_time = time.time() - start_timehour = str(int(use_time // 60 // 60))minute = str(int(use_time // 60 % 60))second = str(int(use_time % 60))print("total time:" + hour + "h " + minute + "m " + second + "s")print("============== Train Success ==============")if __name__ == '__main__':train()

?

練好的模型保存在當前目錄的shufflenetv1-5_390.ckpt中,用作評估。

模型評估

在CIFAR-10的測試集上對模型進行評估。

設置好評估模型的路徑后加載數據集,并設置Top 1, Top 5的評估標準,最后用model.eval()接口對模型進行評估。

from mindspore import load_checkpoint, load_param_into_netdef test():mindspore.set_context(mode=mindspore.GRAPH_MODE, device_target="Ascend")dataset = get_dataset("./dataset/cifar-10-batches-bin", 128, "test")net = ShuffleNetV1(model_size="2.0x", n_class=10)param_dict = load_checkpoint("shufflenetv1-5_390.ckpt")load_param_into_net(net, param_dict)net.set_train(False)loss = nn.CrossEntropyLoss(weight=None, reduction='mean', label_smoothing=0.1)eval_metrics = {'Loss': nn.Loss(), 'Top_1_Acc': Top1CategoricalAccuracy(),'Top_5_Acc': Top5CategoricalAccuracy()}model = Model(net, loss_fn=loss, metrics=eval_metrics)start_time = time.time()res = model.eval(dataset, dataset_sink_mode=False)use_time = time.time() - start_timehour = str(int(use_time // 60 // 60))minute = str(int(use_time // 60 % 60))second = str(int(use_time % 60))log = "result:" + str(res) + ", ckpt:'" + "./shufflenetv1-5_390.ckpt" \+ "', time: " + hour + "h " + minute + "m " + second + "s"print(log)filename = './eval_log.txt'with open(filename, 'a') as file_object:file_object.write(log + '\n')if __name__ == '__main__':test()

模型預測

在CIFAR-10的測試集上對模型進行預測,并將預測結果可視化。

mport mindspore
import matplotlib.pyplot as plt
import mindspore.dataset as dsnet = ShuffleNetV1(model_size="2.0x", n_class=10)
show_lst = []
param_dict = load_checkpoint("shufflenetv1-5_390.ckpt")
load_param_into_net(net, param_dict)
model = Model(net)
dataset_predict = ds.Cifar10Dataset(dataset_dir="./dataset/cifar-10-batches-bin", shuffle=False, usage="train")
dataset_show = ds.Cifar10Dataset(dataset_dir="./dataset/cifar-10-batches-bin", shuffle=False, usage="train")
dataset_show = dataset_show.batch(16)
show_images_lst = next(dataset_show.create_dict_iterator())["image"].asnumpy()
image_trans = [vision.RandomCrop((32, 32), (4, 4, 4, 4)),vision.RandomHorizontalFlip(prob=0.5),vision.Resize((224, 224)),vision.Rescale(1.0 / 255.0, 0.0),vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),vision.HWC2CHW()]
dataset_predict = dataset_predict.map(image_trans, 'image')
dataset_predict = dataset_predict.batch(16)
class_dict = {0:"airplane", 1:"automobile", 2:"bird", 3:"cat", 4:"deer", 5:"dog", 6:"frog", 7:"horse", 8:"ship", 9:"truck"}
# 推理效果展示(上方為預測的結果,下方為推理效果圖片)
plt.figure(figsize=(16, 5))
predict_data = next(dataset_predict.create_dict_iterator())
output = model.predict(ms.Tensor(predict_data['image']))
pred = np.argmax(output.asnumpy(), axis=1)
index = 0
for image in show_images_lst:plt.subplot(2, 8, index+1)plt.title('{}'.format(class_dict[pred[index]]))index += 1plt.imshow(image)plt.axis("off")
plt.show()

心得:總的來說,通過這次學習,我對MindSpore和ShuffleNet有了更深入的了解,也積累了寶貴的實踐經驗。我相信,這將對我的職業發展產生積極的影響。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/43123.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/43123.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/43123.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

張大哥筆記:你一旦開竅,就會發現遍地都是錢

大家有沒有發現,窮人總是追逐眼前的利益,總是在追著錢跑,卻總是賺不到錢。而富人有著長遠的見識,追著問題跑,最后卻賺的盆滿缽滿。 我們聽過這樣一句話,錢不是賺來的,而是幫助別人解決問題后給你…

【計算機】同步/異步

同步/異步 在計算機科學和編程中,“同步”(Synchronization)是一種機制,用于協調不同進程或線程之間的操作,以避免競態條件(race conditions)、死鎖(deadlocks)和其他并…

Qt/C++編寫地圖應用/離線地圖下載/路徑規劃/軌跡回放/海量點/坐標轉換

一、前言說明 這個地圖組件寫了很多年了,最初設計的比較粗糙,最開始只是為了滿足項目需要,并沒有考慮太多拓展性,比如最初都是按照百度地圖寫死在代碼中,經過這幾年大量的現場實際應用,以及大量的用戶提出…

Django 新增數據 save()方法

1,添加模型 Test/app11/models.py from django.db import modelsclass Book(models.Model):title models.CharField(max_length100)author models.CharField(max_length100)publication_date models.DateField()price models.DecimalField(max_digits5, decim…

BFC 是什么?

BFC 是塊級格式化上下文(Block Formatting Context)的縮寫,是 CSS 中一個重要的概念,用于控制塊級盒子的布局及浮動元素的交互。BFC 是一個獨立的渲染區域,內部的塊級盒子會按照特定的規則進行布局,不會影響…

軟件工程(上)

目錄 軟件過程模型(軟件開發模型) 瀑布模型 原型模型 V模型 構件組裝模型 螺旋模型(原型瀑布) 基于構件的軟件工程(CBSE) 快速應用開發模型(RAD) 統一過程(UP&a…

Linux學習看這一篇就夠了,超超超牛的Linux基礎入門

引言 小伙伴們,不管是學習c還是學習其他語言在我們學的路上都繞不過操作系統,而且,老生常談的Linux更是每個計算機人的必修,那么我們對Linux的了解可能只是從別人那聽到的簡單的這個系統很牛,巴拉巴拉的,但…

大模型日報 2024-07-08

大模型日報 2024-07-08 大模型資訊 Anthropic CEO:大模型訓練成本暴漲,2027年將達1000億美元! Anthropic首席執行官表示,當前AI模型訓練成本是10億美元,未來三年,這個數字可能會上升到100億美元甚至1000億美…

GitLab管理員常用配置及設置匯總

? 之前在 虛擬機Ubuntu 22.04上搭建GitLab操作步驟 上介紹了在Ubuntu 22.04上如何搭建社區版的GitLab,這里整理下作為GitLab管理員時在搭建完GitLab CE后,如何對其進行配置或設置 更改倉庫存儲位置:切換到root用戶下操作 默認存放位置&…

SSL 證書

自動獲取 Lets Encrypt 免費證書 (適用于 Linux 系統) 安裝 Certbot sudo apt-get update sudo apt-get install certbot python3-certbot-nginx # Nginx 服務器 sudo apt-get install certbot python3-certbot-apache # Apache 服務器 獲取和安裝證…

小米rdemi紅米ax3000t刷機 20240707最新配套完整程序整理合集

小米rdemi紅米ax3000t刷機程序地址: https://www.123pan.com/s/LA1bVv-EOzVv.html 小米路由器SSH密碼計算器 https://www.1234f.com/fuwu/ax3000t/ 最新更新地址:https://www.1234f.com/fuwu/openwrt/ 依次輸入如下命令: curl -X POST h…

Leetcode 295.數據流的中位數

295.數據流的中位數 問題描述 中位數是有序整數列表中的中間值。如果列表的大小是偶數,則沒有中間值,中位數是兩個中間值的平均值。 例如 arr [2,3,4] 的中位數是 3 。例如 arr [2,3] 的中位數是 (2 3) / 2 2.5 。 實現 MedianFinder 類: Media…

算法013:水果成籃

水果成籃. - 備戰技術面試?力扣提供海量技術面試資源,幫助你高效提升編程技能,輕松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/fruit-into-baskets/ 這道題題目很長,仔細閱讀過后,我們其實可以簡化成&#xff…

MySQL 9.0新特性:向量存儲

MySQL 9.0 正式版已經發布,其中一個亮點就是向量(VECTOR)數據類型的支持,本文給大家詳細介紹一下這個新功能。 向量類型 MySQL 9.0 增加了一個新的向量數據類型:VECTOR。它是一種可以存儲 N 個數據項的數據結構&…

Redis Stream:實時數據流的處理與存儲

Redis Stream:實時數據流的處理與存儲 引言 在當今數據驅動的世界中,實時數據處理和存儲成為了許多應用的核心需求。Redis Stream作為一種新興的數據結構,為Redis帶來了強大的流處理能力。本文將深入探討Redis Stream的特點、使用場景以及如何高效地利用它來處理實時數據流…

聚焦數字創新,定義影像未來

國際數字影像產業園在明確產業定位與發展方向時,應聚焦于數字影像、文創、媒體等新興產業領域,以技術創新為核心動力、產業升級為保障、市場拓展為途徑、國際化發展為方向,推動園區的持續健康發展。 作為園區的核心產業,數字影像產…

python socks5代理的使用

需要安裝依賴 1、解決方法1 In order to make requests use socks proxy, you need to install it with it’s dependency. pip install requests[socks]2、解決方法2 pip install PySocks

第二證券股市知識:股票填權是怎么回事?利好還是利空?

1、股票填權的含義 股票填權是指在除權除息之后的一段時刻內,假設多數投資者看好該個股,股票的價格超過除權除息的基準價就叫做填權。上市公司假設能持續分紅,就會向市場傳遞積極信號,招引更多投資者買入,越來越多的投…

使用Livox-Mid360激光雷達,復現FAST_LIO(保姆級教程)

前面我已經完成了mid360激光雷達的驅動安裝,octomap的復現,昨天我去把這倆在正式環境中實測了一下,效果不好,走廊轉角沒建出來,我查了一下,應該是TF的原因,但這部分我還不太懂,看到有…