【CVPR2021】DDRNets:高效实时车道分割算法

本文介绍基于PaddlePaddle复现DDRNet23-Slim的过程。该网络为高效道路场景实时语义分割模型,采用双分辨率架构和多尺度模块,融合高低级语义信息。文中展示关键结构设计及基于PaddleSeg的开发步骤,模型表现良好。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

【cvpr2021】ddrnets:高效实时车道分割算法 - 创想鸟

基于PaddlePaddle复现DDRNet23-Slim

简介

论文链接:https://arxiv.org/pdf/2101.06085.pdf

在开始之前先放一张作者实验的效果图镇楼:

【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟

该网络结构是截止到目前在cityspace数据集上表现最好的一个分割网络模型。该模型的DDRNet23-Slim的速度堪比BisenetV2但同时上限又远高于BisenetV2,个人认为这个网络其实就是模型者上位(bushi 由于其思路与Bisenetv2过于相似,但在相似的基础上又有很独到的见解,就很nice。下面让我们看一下他的模型结构:【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟

对比BiseNetV2来说是不是十分相似,都是采用了快速下降特征图尺寸,同时采用了并行细节分支和粗语义分支,而BisenetV2是在两个分支最后进行了粗语义(边缘,形状等浅语义信息)而DDRNet则在这个思路上进行了更高级的操作:每一步都进行融合,保证高级语义特征中始终保持有低级语义信息(因为随着特征层的深入低级语义信息会不断丢失,而这个操作就仿佛时时刻刻都让低级高级语义信息始终共存)。同时BisenetV2的booster都整过来了,在训练的时候启用中间的seghead,预测就不用(用也没问题,增加的计算量微乎其微),比较惊喜的就是这个DAPPM模块,这个模块看似和aspp一样无脑且大计算量,但是由于输入的层数以及featuremap size已经非常非常小了,这时候这个模块在整体的计算量就几乎可以免去。虽然在最后的sum部分我认为是有点违背轻量级网络设计的原则(ShuffleNetV2提出的四原则)进行了元素级别的运算,但是依然不能否认这个模型的优越性。(个人认为这里变成concat再进行conv提取信息会比较好)

关于DAPPM模块结构后续给出。

总结:本文提出了一种新的用于道路场景实时语义分割的深度双分辨率体系结构,并提出了一种新的多尺度上下文信息提取模块。据作者所知,作者是第一个将深度高分辨率表示引入实时语义分割的公司,作者的简单策略在两种流行基准上优于所有以前的模型,而不需要任何额外的附加功能。现有的实时网络大多是为ImageNet精心设计的或专门为ImageNet设计的高级骨干,这与广泛用于高精度方法的扩张骨干有很大不同。相比之下,DDRNet只利用了基本的残余模块和瓶颈模块,通过缩放模型的宽度和深度,可以提供更大范围的速度和精度权衡。由于作者的方法简单和高效,它可以被视为统一实时和高精度的语义分割的强大基线。

解压数据集&准备相应的环境

In [ ]

!unzip data/data125507/car_data_2022.zip

!!注意!!

为了方便起见我将ddrnet内置到paddleseg的安装包一并放入,但是有个问题是我之前写的ddrnet在生成静态图的时候会报错,与paddle的相关api冲突,因此我不得不在网络文件中定死尺寸并算出每个block的size,如果想使用生成静态图的版本请自行修改尺寸。可通过运行val.py文件根据报错的shape修改对应size的数值

In [ ]

#%cd work/PaddleSeg_withDDRnets/#!unzip /home/aistudio/data/data134261/PaddleSeg_with_DDRNets.zip

下面是自己添加ddrnet需要做的,拉取原生paddleseg环境,自行添加(此操作为不需要生成静态图的同学做,因为可换成自动获取shape的code)

In [ ]

# 首先从gitee上下载PaddleDetection和PaddleSeg程序# 此步骤已经完成,在此基线环境内,不需要再次进行# 当前为大家提供了PaddleSeg2.1版本做为基线,用户也可以选择PaddleSeg2.2、2.3或者develop版本。%cd work/!git clone https://gitee.com/paddlepaddle/PaddleSeg.git # 查看一下是否下载成功!ls /home/aistudio/work# 在AIStudio环境中安装相关依赖!pip install paddleseg -i https://mirror.baidu.com/pypi/simple

模型组网

引入相关库,当加入paddleseg之前需要额外添加两个库 from paddleseg.cvlibs import manager from paddleseg.utils import utils

In [2]

import  paddleimport  paddle.nn as  nnimport  paddle.nn.functional as  F

【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟

这里使用不同大小的卷积核进行扩大感受野,没有使用空洞卷积的原因可能就是因为空洞卷积的膨胀率不好把握(水太深,把握不住)这里可以看一下前面一篇regseg的复现,regseg反倒是支持使用大膨胀率卷积核讲解视频:(https://www.bilibili.com/video/BV1La41127Hc?spm_id_from=333.999.0.0) 不过采用这种级联式的流水线级的扩大感受野也未尝不可。几个关键部件都齐了:首先有shortcut,避免梯度消失和爆炸的同时保留进入模块前的语义信息,有kernel从1,5,9,17到全局池化,不断的增加感受野,但是不使用空洞卷积,以一个简单有效的结构去完成了功能,最后concat到一起逐点卷积去联系跨通道信息。(个人感觉这个跨通道信息里的全局平均池化与前面的感受野结合起来一定程度上解决了长程依赖问题。。吧ps:个人认为)

In [11]

class DAPPM(nn.Layer):    def __init__(self,in_dim,middle_dim,out_dim):        super(DAPPM,self).__init__()        '''        以1/64图像分辨率的特征图作为输入,采用指数步幅的大池核,生成1/128、1/256、1/512图像分辨率的特征图。        '''        kernel_pool=[5,9,17]        stride_pool=[2,4,8]        bn_mom = 0.1        self.scale_1=nn.Sequential(            nn.AvgPool2D(kernel_size=kernel_pool[0], stride=stride_pool[0], padding=stride_pool[0]),            nn.BatchNorm2D(in_dim,momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(in_dim, middle_dim, kernel_size=1, stride=1, padding=0,bias_attr=False)        )        self.scale_2 = nn.Sequential(            nn.AvgPool2D(kernel_size=kernel_pool[1], stride=stride_pool[1], padding=stride_pool[1]),            nn.BatchNorm2D(in_dim,momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(in_dim, middle_dim, kernel_size=1, stride=1, padding=0,bias_attr=False)        )        self.scale_3 = nn.Sequential(            nn.AvgPool2D(kernel_size=kernel_pool[2], stride=stride_pool[2], padding=stride_pool[2]),            nn.BatchNorm2D(in_dim,momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(in_dim, middle_dim, kernel_size=1, stride=1, padding=0,bias_attr=False)        )        self.scale_4 = nn.Sequential(            nn.AdaptiveAvgPool2D(output_size=(1,1)),            nn.BatchNorm2D(in_dim, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(in_dim, middle_dim, kernel_size=1, stride=1, padding=0,bias_attr=False)        )        #最小扩张感受野        self.scale_0=nn.Sequential(            nn.BatchNorm2D(in_dim,momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(in_dim,middle_dim,kernel_size=1,stride=1,padding=0,bias_attr=False)        )        '''        残差连接部分        '''        self.shortcut=nn.Sequential(            nn.BatchNorm2D(in_dim,momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(in_dim,out_dim,kernel_size=1,stride=1,padding=0,bias_attr=False)        )        '''        conv3x3集合        '''        self.process1 = nn.Sequential(            nn.BatchNorm2D(middle_dim, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(middle_dim, middle_dim, kernel_size=3, padding=1, bias_attr=False),        )        self.process2 = nn.Sequential(            nn.BatchNorm2D(middle_dim, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(middle_dim, middle_dim, kernel_size=3, padding=1, bias_attr=False),        )        self.process3 = nn.Sequential(            nn.BatchNorm2D(middle_dim, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(middle_dim, middle_dim, kernel_size=3, padding=1, bias_attr=False),        )        self.process4 = nn.Sequential(            nn.BatchNorm2D(middle_dim, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(middle_dim, middle_dim, kernel_size=3, padding=1, bias_attr=False),        )        self.concat_conv1x1=nn.Sequential(            nn.BatchNorm2D(middle_dim * 5, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(middle_dim * 5, out_dim, kernel_size=1, bias_attr=False),        )    def forward(self,inps):        layer_list=[]        H=inps.shape[2]        W=inps.shape[3]        '''            {   C1x1(x)                                     i=1        Y_i=|   C3x3( U(  C1x1(AvgPool) )  + Y_i-1  )       1<i<n            {   C3x3( U(  C1x1(GlobalPool) )  + Y_i-1  )      i=n        '''        layer_list.append(self.scale_0(inps))        layer_list.append(self.process1(F.interpolate( self.scale_1(inps),                                                    #   scale_factor=[2, 2],                                                        size=[H,W],                                                       mode='bilinear')                                +layer_list[0]))        layer_list.append(self.process2(F.interpolate(self.scale_2(inps),                                                    #  scale_factor=[4, 4],                                                       size=[H, W],                                                      mode='bilinear')                          + layer_list[1]))        layer_list.append(self.process3(F.interpolate(self.scale_3(inps),                                                    #  scale_factor=[8, 8],                                                       size=[H, W],                                                      mode='bilinear')                          + layer_list[2]))        layer_list.append(self.process4(F.interpolate(self.scale_4(inps),                                                    #  scale_factor=[16, 8],                                                       size=[H, W],                                                      mode='bilinear')                         + layer_list[3]))        #concat        oups=self.concat_conv1x1(paddle.concat(layer_list,axis=1))+self.shortcut(inps)        return  oups

【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟

这个结构的玄机之二就是残差卷积块的设计,不过分追求每个残差块中的激活函数步步到位,设计普通残差连接块和bottleneck残差链接块,分别对高级语义信息和低级语义信息进行融合处理。我感觉这个激活函数的位置和整体结构设计还是比较考究的,当然最关键的还是fushion模块,始终让低级高级语义信息共存。

In [8]

class BasicBlock(nn.Layer):    def __init__(self, in_dim, out_dim, stride=1, downsample=None, no_relu=False):        super(BasicBlock,self).__init__()        '''        组成低分辨率和高分辨率分支的卷积集合块,由两个3x3卷积组成        '''        bn_mom=0.1        self.expansion = 1.0        self.layer1 = nn.Sequential(            nn.Conv2D(in_dim,out_dim, kernel_size=3, stride=stride, padding=1, bias_attr=False),            nn.BatchNorm2D(out_dim, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(out_dim, out_dim, kernel_size=3, stride=1, padding=1, bias_attr=False),            nn.BatchNorm2D(out_dim, momentum=bn_mom)        )        self.layer2=nn.Sequential(            nn.Conv2D(out_dim, out_dim, kernel_size=3, stride=1, padding=1, bias_attr=False),            nn.BatchNorm2D(out_dim, momentum=bn_mom)        )        self.no_relu = no_relu        self.relu = nn.ReLU()        self.downsample = downsample    def forward(self, inps):        residual=inps        oups=self.layer1(inps)        oups=self.layer2(oups)        if self.downsample is not None:            residual=self.downsample(inps)        oups=residual+oups        if self.no_relu:            oups=oups        else:            oups = self.relu(oups)        return  oupsclass BottleNeckBlock(nn.Layer):    def __init__(self, in_dim, mid_dim, stride=1, downsample=None, no_relu=True):        super(BottleNeckBlock, self).__init__()        self.expansion = 2        self.layer1=nn.Sequential(            nn.Conv2D(in_dim, mid_dim, kernel_size=1, bias_attr=False),            nn.BatchNorm2D(mid_dim,momentum=0.1),            nn.ReLU()        )        self.layer2 = nn.Sequential(            nn.Conv2D(mid_dim, mid_dim, kernel_size=3, stride=stride, padding=1, bias_attr=False),            nn.BatchNorm2D(mid_dim, momentum=0.1),            nn.ReLU()        )               self.layer3 = nn.Sequential(            nn.Conv2D(mid_dim, mid_dim * self.expansion, kernel_size=1, bias_attr=False),            nn.BatchNorm2D(mid_dim * self.expansion, momentum=0.1),            nn.ReLU(),        )        self.downsample = downsample        self.stride = stride        self.no_relu = no_relu    def forward(self, x):        residual = x        out=self.layer1(x)        out = self.layer2(out)        out = self.layer3(out)        if self.downsample is not None:            residual = self.downsample(x)        out += residual        if self.no_relu:            return out        else:            return self.relu(out)

渐入层设计

In [5]

class Stem(nn.Layer):    def __init__(self,in_dim,out_dim):        super(Stem,self).__init__()        '''        进行两次下采样        '''        bn_mom=0.1        self.layer=nn.Sequential(            nn.Conv2D(in_dim, out_dim, kernel_size=3, stride=2, padding=1, bias_attr=False),            nn.BatchNorm2D(out_dim, momentum=bn_mom),            nn.ReLU(),            nn.Conv2D(out_dim, out_dim, kernel_size=3, stride=2, padding=1, bias_attr=False),            nn.BatchNorm2D(out_dim, momentum=bn_mom)        )    def forward(self,inps):        oups=self.layer(inps)        return oupsclass SegHead(nn.Layer):    def __init__(self, inplanes, interplanes, outplanes, scale_factor=None):        super(SegHead, self).__init__()        self.layer=nn.Sequential(            nn.BatchNorm2D(inplanes,momentum=0.1),            nn.Conv2D(inplanes,interplanes,kernel_size=3,padding=1,bias_attr=False),            nn.ReLU(),            nn.BatchNorm2D(interplanes,momentum=0.1),            nn.ReLU(),            nn.Conv2D(interplanes,outplanes,kernel_size=1,padding=0,bias_attr=True)        )        self.scale_factor = scale_factor    def forward(self,inps):        oups=self.layer(inps)        if self.scale_factor is not None:            height = inps.shape[-2] * self.scale_factor            width = inps.shape[-1] * self.scale_factor            oups = F.interpolate(oups,                                size=[height, width],                                mode='bilinear')        return  oupsdef _make_layer(block,in_dim,out_dim,block_num,stride=1,expansion=1):    layer=[]    downsample=None    if stride!=1 or  in_dim != out_dim * expansion:        downsample = nn.Sequential(            nn.Conv2D(in_dim, out_dim * expansion,kernel_size=1, stride=stride, bias_attr=False),            nn.BatchNorm2D(out_dim * expansion, momentum=0.1),        )    layer.append(block(in_dim, out_dim, stride, downsample))    inplanes = out_dim * expansion    for i in range(1, block_num):        if i == (block_num - 1):            layer.append(block(inplanes, out_dim, stride=1, no_relu=True))        else:            layer.append(block(inplanes, out_dim, stride=1, no_relu=False))    return nn.Sequential(*layer)

主体网络部分

In [9]

#在使用paddleseg二次开发时请取消下面注释#@manager.MODELS.add_componentclass DDRNet_23_slim(nn.Layer):    def __init__(self,num_classes=4):        super(DDRNet_23_slim,self).__init__()        spp_plane=128        head_plane=64        planes=32        highres_planes=planes*2        basicBlock_expansion=1        bottleNeck_expansion=2        self.relu=nn.ReLU()        self.stem=Stem(3,32)        self.layer1 = _make_layer(BasicBlock, planes, planes, block_num=2,stride=1 ,expansion=basicBlock_expansion)        self.layer2 = _make_layer(BasicBlock, planes, planes * 2, block_num=2, stride=2,expansion=basicBlock_expansion)        #1/8下采样        self.layer3 = _make_layer(BasicBlock, planes * 2, planes * 4, block_num=2, stride=2,expansion=basicBlock_expansion)        #1/16下采样 高分辨率        self.layer3_ =_make_layer(BasicBlock, planes * 2, highres_planes, 2,stride=1,expansion=basicBlock_expansion)        self.layer4 = _make_layer(BasicBlock, planes * 4, planes * 8, block_num=2, stride=2,expansion=basicBlock_expansion)        #high-resolution branch layer attch to layer3_        self.layer4_ = _make_layer(BasicBlock, highres_planes, highres_planes, 2,stride=1,expansion=basicBlock_expansion)        '''        上采样过程中的操作        '''        self.compression3 = nn.Sequential(            nn.Conv2D(planes * 4, highres_planes, kernel_size=1, bias_attr=False),            nn.BatchNorm2D(highres_planes, momentum=0.1),        )        self.compression4 = nn.Sequential(            nn.Conv2D(planes * 8, highres_planes, kernel_size=1, bias_attr=False),            nn.BatchNorm2D(highres_planes, momentum=0.1),        )        '''        low-resolution brach downsample        '''        self.down3 = nn.Sequential(            nn.Conv2D(highres_planes, planes * 4, kernel_size=3, stride=2, padding=1, bias_attr=False),            nn.BatchNorm2D(planes * 4, momentum=0.1),        )        self.down4 = nn.Sequential(            nn.Conv2D(highres_planes, planes * 4, kernel_size=3, stride=2, padding=1, bias_attr=False),            nn.BatchNorm2D(planes * 4, momentum=0.1),            nn.ReLU(),            nn.Conv2D(planes * 4, planes * 8, kernel_size=3, stride=2, padding=1, bias_attr=False),            nn.BatchNorm2D(planes * 8, momentum=0.1),        )        self.layer5_ = _make_layer(BottleNeckBlock, highres_planes, highres_planes, 1,stride=1,expansion=bottleNeck_expansion)        self.layer5 = _make_layer(BottleNeckBlock, planes * 8, planes * 8, 1, stride=2,expansion=bottleNeck_expansion)        self.spp = DAPPM(planes * 16, spp_plane, planes * 4)        self.final_layer = SegHead(planes * 4, head_plane, num_classes)    def forward(self, inps):        layers = [0, 0, 0, 0]        # layers = []        width_output = inps.shape[-1] // 8        height_output = inps.shape[-2] // 8        x = self.stem(inps)        x = self.layer1(x)        layers[0] = x # -- 0        # layers.append(x)        x = self.layer2(self.relu(x))        layers[1] = x #1/8 -- 1        # layers.append(x)        #Bilateral fusion        x = self.layer3(self.relu(x)) # get 1/16        layers[2] = x # -- 2        # layers.append(x)        x_low_resolution_branch = self.layer3_(self.relu(layers[1])) #get 1/8        x = x + self.down3(self.relu(x_low_resolution_branch))  # 低分辨度下采样成1/16+高分辨率1/16        x_low_resolution_branch = x_low_resolution_branch + F.interpolate(            self.compression3(self.relu(layers[2])),            #scale_factor=[2, 2], # from 1/16 to 1/8             size=[height_output, width_output],            mode='bilinear') #低分辨率1/16上采样成高分辨率的1/8        x = self.layer4(self.relu(x)) # 1/32        layers[3] = x # -- 3        # layers.append(x)        x_ = self.layer4_(self.relu(x_low_resolution_branch)) # 1/8        x = x + self.down4(self.relu(x_)) # 1/32        x_ = x_ + F.interpolate(            self.compression4(self.relu(layers[3])),            #scale_factor=[4, 4], # from 1/32 to 1/8             size=[height_output, width_output],            mode='bilinear') # 1/8        x_ = self.layer5_(self.relu(x_)) # 1/8        x = F.interpolate(            self.spp(self.layer5(self.relu(x))),            #scale_factor=[8, 8], # from 1/64 to 1/8            size=[height_output, width_output],            mode='bilinear')        x_ = self.final_layer(x + x_) # 1/8        oups=F.interpolate(x_,                        #   scale_factor=[8, 8],                            size=[inps.shape[-2],inps.shape[-1]],                           mode='bilinear')        return [oups]

测试一下网络结构

In [12]

data=paddle.randn([1,3,480,480])model=DDRNet_23_slim(4)print(model(data)[0].shape)
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:653: UserWarning: When training, we now always track global mean and variance.  "When training, we now always track global mean and variance.")
[1, 4, 480, 480]

ok,没问题,同时这篇文章附上不能生成静态图的pdparams文件(十七届智能车完全模型组数据集)假期搞了两个月结果不能生成静态图(写法上的冲突可能,有点玄乎),miou0.712,在相同搜索空间下的的测试数据为0.66,可惜不能生成静态图!提交不了,虽然后面改了一版能生成静态图的但是总是不对味,也很难复现到之前这版这么丝滑的涨点。只能换模型和方案去提交这个比赛了!因此开源(非常气!两三个月白给呜呜呜)我记得当时的成绩是在相同的训练环境下能比stdcseg少训练1w iters高出将近10个点的miou,炼丹,很玄学吧(摊手)

模型预测

In [1]

%cd work/PaddleSeg_withDDRnets/
/home/aistudio/work/PaddleSeg_withDDRnets

In [ ]

!python predict.py --config configs/ddrnet/train.yml --model_path /home/aistudio/model.pdparams --image_path /home/aistudio/car_data_2022/JPEGImages --save_dir output/result

如果是自己训练的话,model_path改成你的model.pdparams的路径哦

基于paddleseg的二次开发

【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟 先在这个路径下创建一个py文件,把上面的东西放进去,同时记住将init文件添加py文件中的类名,例如本文就是 from .xxx(创建的文件名) import DDRNet23-slim 【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟 然后在config文件下建立文件 【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟

然后在文件中加入你的配置,这里的配置可以是:

base: ‘../base/car2022.yml’

model: type: DDRNet_23_slim num_classes: 4

optimizer: type: sgd weight_decay: 0.0001

loss: types: – type: CrossEntropyLoss coef: [1]

batch_size: 2 iters: 160000

lr_scheduler: type: PolynomialDecay learning_rate: 0.01 end_lr: 0.0 power: 0.9

!!!注意!!!

下面的操作是内置固定尺寸的方法,而想使用上面动态尺寸的方法请自行按照上方拉取原生paddleSeg二次开发方式进行拉取,自主构建即可

模型训练

config后要跟自己的路径哦!

In [ ]

%cd work/PaddleSeg_withDDRnets/!python train.py        --config configs/ddrnet/train.yml        --do_eval        --use_vdl        --save_interval 500        --save_dir output

模型评估

In [ ]

!python val.py --config configs/ddrnet/train.yml --model_path output/best_model/model.pdparams

由于不能生成静态图,我就不放生成静态图的程序啦,但是动态图下还是很丝滑的!效果很nice

效果图:【CVPR2021】DDRNets:高效实时车道分割算法 - 创想鸟

以上就是【CVPR2021】DDRNets:高效实时车道分割算法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/70374.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月13日 05:33:49
下一篇 2025年11月13日 05:47:07

相关推荐

  • 怎样用免费工具美化PPT_免费美化PPT的实用方法分享

    利用KIMI智能助手可免费将PPT美化为科技感风格,但需核对文字准确性;2. 天工AI擅长优化内容结构,提升逻辑性,适合高质量内容需求;3. SlidesAI支持语音输入与自动排版,操作便捷,利于紧急场景;4. Prezo提供多种模板,自动生成图文并茂幻灯片,适合学生与初创团队。 如果您有一份内容完…

    2025年12月6日 软件教程
    000
  • Pages怎么协作编辑同一文档 Pages多人实时协作的流程

    首先启用Pages共享功能,点击右上角共享按钮并选择“添加协作者”,设置为可编辑并生成链接;接着复制链接通过邮件或社交软件发送给成员,确保其使用Apple ID登录iCloud后即可加入编辑;也可直接在共享菜单中输入邮箱地址定向邀请,设定编辑权限后发送;最后在共享面板中管理协作者权限,查看实时在线状…

    2025年12月6日 软件教程
    100
  • REDMI K90系列正式发布,售价2599元起!

    10月23日,redmi k90系列正式亮相,推出redmi k90与redmi k90 pro max两款新机。其中,redmi k90搭载骁龙8至尊版处理器、7100mah大电池及100w有线快充等多项旗舰配置,起售价为2599元,官方称其为k系列迄今为止最完整的标准版本。 图源:REDMI红米…

    2025年12月6日 行业动态
    200
  • Linux中如何安装Nginx服务_Linux安装Nginx服务的完整指南

    首先更新系统软件包,然后通过对应包管理器安装Nginx,启动并启用服务,开放防火墙端口,最后验证欢迎页显示以确认安装成功。 在Linux系统中安装Nginx服务是搭建Web服务器的第一步。Nginx以高性能、低资源消耗和良好的并发处理能力著称,广泛用于静态内容服务、反向代理和负载均衡。以下是在主流L…

    2025年12月6日 运维
    000
  • Linux journalctl与systemctl status结合分析

    先看 systemctl status 确认服务状态,再用 journalctl 查看详细日志。例如 nginx 启动失败时,systemctl status 显示 Active: failed,journalctl -u nginx 发现端口 80 被占用,结合两者可快速定位问题根源。 在 Lin…

    2025年12月6日 运维
    100
  • 华为新机发布计划曝光:Pura 90系列或明年4月登场

    近日,有数码博主透露了华为2025年至2026年的新品规划,其中pura 90系列预计在2026年4月发布,有望成为华为新一代影像旗舰。根据路线图,华为将在2025年底至2026年陆续推出mate 80系列、折叠屏新机mate x7系列以及nova 15系列,而pura 90系列则将成为2026年上…

    2025年12月6日 行业动态
    100
  • TikTok视频无法下载怎么办 TikTok视频下载异常修复方法

    先检查链接格式、网络设置及工具版本。复制以https://www.tiktok.com/@或vm.tiktok.com开头的链接,删除?后参数,尝试短链接;确保网络畅通,可切换地区节点或关闭防火墙;更新工具至最新版,优先选用yt-dlp等持续维护的工具。 遇到TikTok视频下载不了的情况,别急着换…

    2025年12月6日 软件教程
    100
  • Linux如何优化系统性能_Linux系统性能优化的实用方法

    优化Linux性能需先监控资源使用,通过top、vmstat等命令分析负载,再调整内核参数如TCP优化与内存交换,结合关闭无用服务、选用合适文件系统与I/O调度器,持续按需调优以提升系统效率。 Linux系统性能优化的核心在于合理配置资源、监控系统状态并及时调整瓶颈环节。通过一系列实用手段,可以显著…

    2025年12月6日 运维
    000
  • Linux命令行中wc命令的实用技巧

    wc命令可统计文件的行数、单词数、字符数和字节数,常用-l统计行数,如wc -l /etc/passwd查看用户数量;结合grep可分析日志,如grep “error” logfile.txt | wc -l统计错误行数;-w统计单词数,-m统计字符数(含空格换行),-c统计…

    2025年12月6日 运维
    000
  • 曝小米17 Air正在筹备 超薄机身+2亿像素+eSIM技术?

    近日,手机行业再度掀起超薄机型热潮,三星与苹果已相继推出s25 edge与iphone air等轻薄旗舰,引发市场高度关注。在此趋势下,多家国产厂商被曝正积极布局相关技术,加速抢占这一细分赛道。据业内人士消息,小米的超薄旗舰机型小米17 air已进入筹备阶段。 小米17 Pro 爆料显示,小米正在评…

    2025年12月6日 行业动态
    000
  • 「世纪传奇刀片新篇」飞利浦影音双11声宴开启

    百年声学基因碰撞前沿科技,一场有关声音美学与设计美学的影音狂欢已悄然引爆2025“双十一”! 当绝大多数影音数码品牌还在价格战中挣扎时,飞利浦影音已然开启了一场跨越百年的“声”活革命。作为拥有深厚技术底蕴的音频巨头,飞利浦影音及配件此次“双十一”精准聚焦“传承经典”与“设计美学”两大核心,为热爱生活…

    2025年12月6日 行业动态
    000
  • 荣耀手表5Pro 10月23日正式开启首销国补优惠价1359.2元起售

    荣耀手表5pro自9月25日开启全渠道预售以来,市场热度持续攀升,上市初期便迎来抢购热潮,一度出现全线售罄、供不应求的局面。10月23日,荣耀手表5pro正式迎来首销,提供蓝牙版与esim版两种选择。其中,蓝牙版本的攀登者(橙色)、开拓者(黑色)和远航者(灰色)首销期间享受国补优惠价,到手价为135…

    2025年12月6日 行业动态
    000
  • Vue.js应用中配置环境变量:灵活管理后端通信地址

    在%ignore_a_1%应用中,灵活配置后端api地址等参数是开发与部署的关键。本文将详细介绍两种主要的环境变量配置方法:推荐使用的`.env`文件,以及通过`cross-env`库在命令行中设置环境变量。通过这些方法,开发者可以轻松实现开发、测试、生产等不同环境下配置的动态切换,提高应用的可维护…

    2025年12月6日 web前端
    000
  • 环境搭建docker环境下如何快速部署mysql集群

    使用Docker Compose部署MySQL主从集群,通过配置文件设置server-id和binlog,编写docker-compose.yml定义主从服务并组网,启动后创建复制用户并配置主从连接,最后验证数据同步是否正常。 在Docker环境下快速部署MySQL集群,关键在于合理使用Docker…

    2025年12月6日 数据库
    000
  • Xbox删忍龙美女角色 斯宾塞致敬板垣伴信被喷太虚伪

    近日,海外游戏推主@HaileyEira公开发表言论,批评Xbox负责人菲尔·斯宾塞不配向已故的《死或生》与《忍者龙剑传》系列之父板垣伴信致敬。她指出,Xbox并未真正尊重这位传奇制作人的创作遗产,反而在宣传相关作品时对内容进行了审查和删减。 所涉游戏为年初推出的《忍者龙剑传2:黑之章》,该作采用虚…

    2025年12月6日 游戏教程
    000
  • 如何在mysql中分析索引未命中问题

    答案是通过EXPLAIN分析执行计划,检查索引使用情况,优化WHERE条件写法,避免索引失效,结合慢查询日志定位问题SQL,并根据查询模式合理设计索引。 当 MySQL 查询性能下降,很可能是索引未命中导致的。要分析这类问题,核心是理解查询执行计划、检查索引设计是否合理,并结合实际数据访问模式进行优…

    2025年12月6日 数据库
    000
  • VSCode入门:基础配置与插件推荐

    刚用VSCode,别急着装一堆东西。先把基础设好,再按需求加插件,效率高还不卡。核心就三步:界面顺手、主题舒服、功能够用。 设置中文和常用界面 打开软件,左边活动栏有五个图标,点最下面那个“扩展”。搜索“Chinese”,装上官方出的“Chinese (Simplified) Language Pa…

    2025年12月6日 开发工具
    000
  • VSCode性能分析与瓶颈诊断技术

    首先通过资源监控定位异常进程,再利用开发者工具分析性能瓶颈,结合禁用扩展、优化语言服务器配置及项目设置,可有效解决VSCode卡顿问题。 VSCode作为主流的代码编辑器,虽然轻量高效,但在处理大型项目或配置复杂扩展时可能出现卡顿、响应延迟等问题。要解决这些性能问题,需要系统性地进行性能分析与瓶颈诊…

    2025年12月6日 开发工具
    000
  • php查询代码怎么写_php数据库查询语句编写技巧与实例

    在PHP中进行数据库查询,最常用的方式是使用MySQLi或PDO扩展连接MySQL数据库。下面介绍基本的查询代码写法、编写技巧以及实用示例,帮助你高效安全地操作数据库。 1. 使用MySQLi进行查询(面向对象方式) 这是较为推荐的方式,适合大多数中小型项目。 // 创建连接$host = ‘loc…

    2025年12月6日 后端开发
    000
  • VSCode的悬浮提示信息可以自定义吗?

    可以通过JSDoc、docstring和扩展插件自定义VSCode悬浮提示内容,如1. 添加JSDoc或Python docstring增强信息;2. 调整hover延迟与粘性等显示行为;3. 使用支持自定义提示的扩展或开发hover provider实现深度定制,但无法直接修改HTML结构或手动编…

    2025年12月6日 开发工具
    000

发表回复

登录后才能评论
关注微信