python神經(jīng)網(wǎng)絡(luò)Keras?GhostNet模型的實(shí)現(xiàn)
什么是GhostNet模型
GhostNet是華為諾亞方舟實(shí)驗(yàn)室提出來(lái)的一個(gè)非常有趣的網(wǎng)絡(luò),我們一起來(lái)學(xué)習(xí)一下。
2020年,華為新出了一個(gè)輕量級(jí)網(wǎng)絡(luò),命名為GhostNet。
在優(yōu)秀CNN模型中,特征圖存在冗余是非常重要的。如圖所示,這個(gè)是對(duì)ResNet-50第一個(gè)殘差塊特征圖進(jìn)行可視化的結(jié)果,當(dāng)我們給一個(gè)神經(jīng)網(wǎng)絡(luò)輸入一張圖片時(shí),我們可以獲得特別多的特征圖。
利用小扳手連接起來(lái)的兩幅特征圖,它們的相似性就特別高,這個(gè)就是神經(jīng)網(wǎng)絡(luò)中存在的特征圖冗雜的情況。
作者將相似的特征圖認(rèn)為是彼此的Ghost,所以這個(gè)網(wǎng)絡(luò)就叫做GhostNet(誤)。
在GhostNet這篇論文里面,作者認(rèn)為可以使用一些計(jì)算量更低(Cheap Operations)的操作去生成這些冗余的特征圖,這樣就可以在保證良好檢測(cè)效果的情況下,減少模型的參數(shù)量與提高模型的執(zhí)行速度。
GhostNet模型的實(shí)現(xiàn)思路
1、Ghost Module
通過(guò)上述的介紹,我們了解到了,GhostNet的核心思想就是使用一些計(jì)算量更低(Cheap Operations)的操作去生成這些冗余的特征圖。
在論文中,作者設(shè)計(jì)了一個(gè)名為Ghost Module的模塊,他的功能是代替普通卷積。
Ghost Module將普通卷積分為兩部分,首先進(jìn)行一個(gè)普通的1x1卷積,這是一個(gè)少量卷積,比如正常使用32通道的卷積,這里就用16通道的卷積,這個(gè)1x1卷積的作用類(lèi)似于特征整合,生成輸入特征層的特征濃縮。
然后我們?cè)龠M(jìn)行深度可分離卷積,這個(gè)深度可分離卷積是逐層卷積,它也就是我們上面提到的Cheap Operations。它利用上一步獲得的特征濃縮生成Ghost特征圖。
因此,如果我們從整體上去看這個(gè)Ghost Module,它其實(shí)就是兩步簡(jiǎn)單思想的匯總:
1、利用1x1卷積獲得輸入特征的必要特征濃縮。
2、利用深度可分離卷積獲得特征濃縮的相似特征圖(Ghost)。
Ghost Module的實(shí)現(xiàn)代碼如下:
def _ghost_module(inputs, exp, kernel, dw_kernel, ratio, strides=1, padding='same',use_bias=False, relu=True): output_channels = math.ceil(exp * 1.0 / ratio) x = Conv2D(output_channels, kernel, strides=strides, padding=padding, use_bias=use_bias)(inputs) x = BatchNormalization()(x) if relu: x = Activation('relu')(x) dw = DepthwiseConv2D(dw_kernel, strides, padding=padding, depth_multiplier=ratio-1, use_bias=use_bias)(x) dw = BatchNormalization()(dw) if relu: dw = Activation('relu')(dw) x = Concatenate(axis=-1)([x,dw]) x = Lambda(slices, arguments={'n':exp})(x) return x
2、Ghost Bottlenecks
Ghost Bottlenecks是由Ghost Module組成的瓶頸結(jié)構(gòu),就像這樣。
其實(shí)本質(zhì)上就是用Ghost Module,來(lái)代替瓶頸結(jié)構(gòu)里面的普通卷積。
Ghost Bottlenecks可以分為兩個(gè)部分,分別是主干部分和殘差邊部分,包含Ghost Module的,我們稱(chēng)它為主干部分。
Ghost Bottlenecks有兩個(gè)種類(lèi),如下圖所示,當(dāng)我們需要對(duì)特征層的寬高進(jìn)行壓縮的時(shí)候,我們會(huì)設(shè)置這個(gè)Ghost Bottlenecks的Stride=2,即步長(zhǎng)為2。
此時(shí)我們會(huì)Bottlenecks里面多添加一些卷積層,在主干部分里,我們會(huì)在兩個(gè)Ghost Module中添加一個(gè)步長(zhǎng)為2x2的深度可分離卷積進(jìn)行特征層的寬高壓縮。在殘差邊部分,我們也會(huì)添加上一個(gè)步長(zhǎng)為2x2的深度可分離卷積和1x1的普通卷積。
Ghost Bottlenecks的實(shí)現(xiàn)代碼如下:
def _ghost_bottleneck(inputs, output_channel, hidden_channel, kernel, ghost_kernel, strides, ratio, squeeze): input_shape = K.int_shape(inputs) # 獲取輸入張量的尺寸 x = _ghost_module(inputs, hidden_channel, [1,1], ghost_kernel, ratio) if strides > 1: x = DepthwiseConv2D(kernel, strides, padding='same', depth_multiplier=1, use_bias=False)(x) x = BatchNormalization()(x) if squeeze: x = _squeeze(x, hidden_channel, 4) x = _ghost_module(x, output_channel, [1,1], ghost_kernel, ratio, relu=False) if strides == 1 and input_shape[-1] == output_channel: res = inputs else: res = DepthwiseConv2D(kernel, strides=strides, padding='same', depth_multiplier=1, use_bias=False)(inputs) res = BatchNormalization()(res) res = Conv2D(output_channel, (1, 1), padding='same', strides=(1, 1), use_bias=False)(res) res = BatchNormalization()(res) x = Add()([res, x]) return x
3、Ghostnet的構(gòu)建
整個(gè)Ghostnet的構(gòu)建方式如列表所示:
可以看到,整個(gè)Ghostnet都是由Ghost Bottlenecks進(jìn)行組成的。
當(dāng)一張圖片輸入到Ghostnet當(dāng)中時(shí),我們首先進(jìn)行一個(gè)16通道的普通1x1卷積塊(卷積+標(biāo)準(zhǔn)化+激活函數(shù))。
之后我們就開(kāi)始Ghost Bottlenecks的堆疊了,利用Ghost Bottlenecks,我們最終獲得了一個(gè)7x7x160的特征層(當(dāng)輸入是224x224x3的時(shí)候)。
然后我們會(huì)利用一個(gè)1x1的卷積塊進(jìn)行通道數(shù)的調(diào)整,此時(shí)我們可以獲得一個(gè)7x7x960的特征層。
之后我們進(jìn)行一次全局平均池化,然后再利用一個(gè)1x1的卷積塊進(jìn)行通道數(shù)的調(diào)整,獲得一個(gè)1x1x1280的特征層。
然后平鋪后進(jìn)行全連接就可以進(jìn)行分類(lèi)了。
GhostNet的代碼構(gòu)建
1、模型代碼的構(gòu)建
GhostNet的實(shí)現(xiàn)代碼如下,該代碼是Ghostnet在YoloV4上的應(yīng)用,可以參考一下:
import math import warnings import numpy as np import tensorflow as tf from keras import backend as K from keras.applications.imagenet_utils import decode_predictions from keras.initializers import random_normal from keras.layers import (Activation, Add, BatchNormalization, Concatenate, Conv2D, DepthwiseConv2D, GlobalAveragePooling2D, Lambda, Multiply, Reshape) def slices(dw, n): return dw[:,:,:,:n] def _make_divisible(v, divisor, min_value=None): if min_value is None: min_value = divisor new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) if new_v < 0.9 * v: new_v += divisor return new_v def _squeeze(inputs, hidden_channel, ratio, block_id, sub_block_id): x = GlobalAveragePooling2D()(inputs) x = Reshape((1,1,-1))(x) x = Conv2D(_make_divisible(hidden_channel/ratio, 4), (1,1), strides=(1,1), padding='same', kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".se.conv_reduce")(x) x = Activation('relu')(x) x = Conv2D(hidden_channel, (1,1),strides=(1,1), padding='same', kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".se.conv_expand")(x) x = Activation('hard_sigmoid')(x) x = Multiply()([inputs, x]) # inputs和x逐元素相乘 return x def _ghost_module(inputs, exp, ratio, block_id, sub_block_id, part, kernel_size=1, dw_size=3, stride=1, relu=True): output_channels = math.ceil(exp * 1.0 / ratio) x = Conv2D(output_channels, kernel_size, strides=stride, padding="same", use_bias=False, kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".primary_conv.0")(inputs) x = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".primary_conv.1")(x) if relu: x = Activation('relu')(x) dw = DepthwiseConv2D(dw_size, 1, padding="same", depth_multiplier=ratio-1, use_bias=False, depthwise_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".cheap_operation.0")(x) dw = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".cheap_operation.1")(dw) if relu: dw = Activation('relu')(dw) x = Concatenate(axis=-1)([x,dw]) x = Lambda(slices, arguments={'n':exp})(x) return x def _ghost_bottleneck(inputs, output_channel, hidden_channel, kernel, strides, ratio, squeeze, block_id, sub_block_id): input_shape = K.int_shape(inputs) x = _ghost_module(inputs, hidden_channel, ratio, block_id, sub_block_id, 1) if strides > 1: x = DepthwiseConv2D(kernel, strides, padding='same', depth_multiplier=1, use_bias=False, depthwise_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".conv_dw")(x) x = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".bn_dw")(x) if squeeze: x = _squeeze(x, hidden_channel, 4, block_id, sub_block_id) x = _ghost_module(x, output_channel, ratio, block_id, sub_block_id, 2, relu=False) if strides == 1 and input_shape[-1] == output_channel: res = inputs else: res = DepthwiseConv2D(kernel, strides=strides, padding='same', depth_multiplier=1, use_bias=False, depthwise_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.0")(inputs) res = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.1")(res) res = Conv2D(output_channel, (1, 1), padding='same', strides=(1, 1), use_bias=False, kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.2")(res) res = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.3")(res) x = Add()([res, x]) return x def Ghostnet(inputs): x = Conv2D(16, (3, 3), padding="same", strides=(2, 2), use_bias=False, kernel_initializer=random_normal(stddev=0.02), name="conv_stem")(inputs) x = BatchNormalization(name="bn1")(x) x = Activation('relu')(x) x = _ghost_bottleneck(x, 16, 16, (3, 3), strides=1, ratio=2, squeeze=False, block_id=0, sub_block_id=0) x = _ghost_bottleneck(x, 24, 48, (3, 3), strides=2, ratio=2, squeeze=False, block_id=1, sub_block_id=0) x = _ghost_bottleneck(x, 24, 72, (3, 3), strides=1, ratio=2, squeeze=False, block_id=2, sub_block_id=0) x = _ghost_bottleneck(x, 40, 72, (5, 5), strides=2, ratio=2, squeeze=True, block_id=3, sub_block_id=0) x = _ghost_bottleneck(x, 40, 120, (5, 5), strides=1, ratio=2, squeeze=True, block_id=4, sub_block_id=0) feat1 = x x = _ghost_bottleneck(x, 80, 240, (3, 3), strides=2, ratio=2, squeeze=False, block_id=5, sub_block_id=0) x = _ghost_bottleneck(x, 80, 200, (3, 3), strides=1, ratio=2, squeeze=False, block_id=6, sub_block_id=0) x = _ghost_bottleneck(x, 80, 184, (3, 3), strides=1, ratio=2, squeeze=False, block_id=6, sub_block_id=1) x = _ghost_bottleneck(x, 80, 184, (3, 3), strides=1, ratio=2, squeeze=False, block_id=6, sub_block_id=2) x = _ghost_bottleneck(x, 112, 480, (3, 3), strides=1, ratio=2, squeeze=True, block_id=6, sub_block_id=3) x = _ghost_bottleneck(x, 112, 672, (3, 3), strides=1, ratio=2, squeeze=True, block_id=6, sub_block_id=4) feat2 = x x = _ghost_bottleneck(x, 160, 672, (5, 5), strides=2, ratio=2, squeeze=True, block_id=7, sub_block_id=0) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=False, block_id=8, sub_block_id=0) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=True, block_id=8, sub_block_id=1) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=False, block_id=8, sub_block_id=2) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=True, block_id=8, sub_block_id=3) feat3 = x return feat1,feat2,feat3
2、Yolov4上的應(yīng)用
作為一個(gè)輕量級(jí)網(wǎng)絡(luò),我把Ghostnet和Mobilenet放在一起,作為Yolov4的主干網(wǎng)絡(luò)進(jìn)行特征提取。
對(duì)于yolov4來(lái)講,我們需要利用主干特征提取網(wǎng)絡(luò)獲得的三個(gè)有效特征進(jìn)行加強(qiáng)特征金字塔的構(gòu)建。
我們通過(guò)上述代碼可以取出三個(gè)有效特征層,我們可以利用這三個(gè)有效特征層替換原來(lái)yolov4主干網(wǎng)絡(luò)CSPdarknet53的有效特征層。
為了進(jìn)一步減少參數(shù)量,我們可以使用深度可分離卷積代替yoloV3中用到的普通卷積。
最終Ghostnet-Yolov4的構(gòu)建代碼如下:
from functools import wraps from keras import backend as K from keras.initializers import random_normal from keras.layers import (Activation, BatchNormalization, Concatenate, Conv2D, DepthwiseConv2D, Input, Lambda, MaxPooling2D, UpSampling2D) from keras.layers.normalization import BatchNormalization from keras.models import Model from keras.regularizers import l2 from utils.utils import compose from nets.ghostnet import Ghostnet from nets.mobilenet_v1 import MobileNetV1 from nets.mobilenet_v2 import MobileNetV2 from nets.mobilenet_v3 import MobileNetV3 from nets.yolo_training import yolo_loss def relu6(x): return K.relu(x, max_value=6) #------------------------------------------------------# # 單次卷積DarknetConv2D # 如果步長(zhǎng)為2則自己設(shè)定padding方式。 #------------------------------------------------------# @wraps(Conv2D) def DarknetConv2D(*args, **kwargs): darknet_conv_kwargs = {'kernel_initializer' : random_normal(stddev=0.02), 'kernel_regularizer': l2(5e-4)} darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same' darknet_conv_kwargs.update(kwargs) return Conv2D(*args, **darknet_conv_kwargs) #---------------------------------------------------# # 卷積塊 -> 卷積 + 標(biāo)準(zhǔn)化 + 激活函數(shù) # DarknetConv2D + BatchNormalization + Relu6 #---------------------------------------------------# def DarknetConv2D_BN_Leaky(*args, **kwargs): no_bias_kwargs = {'use_bias': False} no_bias_kwargs.update(kwargs) return compose( DarknetConv2D(*args, **no_bias_kwargs), BatchNormalization(), Activation(relu6)) #---------------------------------------------------# # 深度可分離卷積塊 # DepthwiseConv2D + BatchNormalization + Relu6 #---------------------------------------------------# def _depthwise_conv_block(inputs, pointwise_conv_filters, alpha = 1, depth_multiplier=1, strides=(1, 1)): pointwise_conv_filters = int(pointwise_conv_filters * alpha) x = DepthwiseConv2D((3, 3), depthwise_initializer=random_normal(stddev=0.02), padding='same', depth_multiplier=depth_multiplier, strides=strides, use_bias=False)(inputs) x = BatchNormalization()(x) x = Activation(relu6)(x) x = DarknetConv2D(pointwise_conv_filters, (1, 1), padding='same', use_bias=False, strides=(1, 1))(x) x = BatchNormalization()(x) return Activation(relu6)(x) #---------------------------------------------------# # 進(jìn)行五次卷積 #---------------------------------------------------# def make_five_convs(x, num_filters): # 五次卷積 x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x) x = _depthwise_conv_block(x, num_filters*2,alpha=1) x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x) x = _depthwise_conv_block(x, num_filters*2,alpha=1) x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x) return x #---------------------------------------------------# # Panet網(wǎng)絡(luò)的構(gòu)建,并且獲得預(yù)測(cè)結(jié)果 #---------------------------------------------------# def yolo_body(input_shape, anchors_mask, num_classes, backbone="mobilenetv1", alpha=1): inputs = Input(input_shape) #---------------------------------------------------# # 生成mobilnet的主干模型,獲得三個(gè)有效特征層。 #---------------------------------------------------# if backbone=="mobilenetv1": #---------------------------------------------------# # 52,52,256;26,26,512;13,13,1024 #---------------------------------------------------# feat1,feat2,feat3 = MobileNetV1(inputs, alpha=alpha) elif backbone=="mobilenetv2": #---------------------------------------------------# # 52,52,32;26,26,92;13,13,320 #---------------------------------------------------# feat1,feat2,feat3 = MobileNetV2(inputs, alpha=alpha) elif backbone=="mobilenetv3": #---------------------------------------------------# # 52,52,40;26,26,112;13,13,160 #---------------------------------------------------# feat1,feat2,feat3 = MobileNetV3(inputs, alpha=alpha) elif backbone=="ghostnet": #---------------------------------------------------# # 52,52,40;26,26,112;13,13,160 #---------------------------------------------------# feat1,feat2,feat3 = Ghostnet(inputs) else: raise ValueError('Unsupported backbone - `{}`, Use mobilenetv1, mobilenetv2, mobilenetv3, ghostnet.'.format(backbone)) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(feat3) P5 = _depthwise_conv_block(P5, int(1024* alpha)) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5) maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5) maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5) maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5) P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5]) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5) P5 = _depthwise_conv_block(P5, int(1024* alpha)) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5) P5_upsample = compose(DarknetConv2D_BN_Leaky(int(256* alpha), (1,1)), UpSampling2D(2))(P5) P4 = DarknetConv2D_BN_Leaky(int(256* alpha), (1,1))(feat2) P4 = Concatenate()([P4, P5_upsample]) P4 = make_five_convs(P4,int(256* alpha)) P4_upsample = compose(DarknetConv2D_BN_Leaky(int(128* alpha), (1,1)), UpSampling2D(2))(P4) P3 = DarknetConv2D_BN_Leaky(int(128* alpha), (1,1))(feat1) P3 = Concatenate()([P3, P4_upsample]) P3 = make_five_convs(P3,int(128* alpha)) #---------------------------------------------------# # 第三個(gè)特征層 # y3=(batch_size,52,52,3,85) #---------------------------------------------------# P3_output = _depthwise_conv_block(P3, int(256* alpha)) P3_output = DarknetConv2D(len(anchors_mask[0])*(num_classes+5), (1,1))(P3_output) P3_downsample = _depthwise_conv_block(P3, int(256* alpha), strides=(2,2)) P4 = Concatenate()([P3_downsample, P4]) P4 = make_five_convs(P4,int(256* alpha)) #---------------------------------------------------# # 第二個(gè)特征層 # y2=(batch_size,26,26,3,85) #---------------------------------------------------# P4_output = _depthwise_conv_block(P4, int(512* alpha)) P4_output = DarknetConv2D(len(anchors_mask[1])*(num_classes+5), (1,1))(P4_output) P4_downsample = _depthwise_conv_block(P4, int(512* alpha), strides=(2,2)) P5 = Concatenate()([P4_downsample, P5]) P5 = make_five_convs(P5,int(512* alpha)) #---------------------------------------------------# # 第一個(gè)特征層 # y1=(batch_size,13,13,3,85) #---------------------------------------------------# P5_output = _depthwise_conv_block(P5, int(1024* alpha)) P5_output = DarknetConv2D(len(anchors_mask[2])*(num_classes+5), (1,1))(P5_output) return Model(inputs, [P5_output, P4_output, P3_output])
以上就是python神經(jīng)網(wǎng)絡(luò)Keras GhostNet模型的復(fù)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Keras GhostNet模型復(fù)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Python機(jī)器學(xué)習(xí)利用鳶尾花數(shù)據(jù)繪制ROC和AUC曲線
- 利用Python畫(huà)ROC曲線和AUC值計(jì)算
- 一文詳解Python灰色預(yù)測(cè)模型實(shí)現(xiàn)示例
- python回歸分析邏輯斯蒂模型之多分類(lèi)任務(wù)詳解
- python深度學(xué)習(xí)tensorflow訓(xùn)練好的模型進(jìn)行圖像分類(lèi)
- Python實(shí)現(xiàn)自動(dòng)駕駛訓(xùn)練模型
- python神經(jīng)網(wǎng)絡(luò)ShuffleNetV2模型復(fù)現(xiàn)詳解
- python神經(jīng)網(wǎng)絡(luò)Densenet模型復(fù)現(xiàn)詳解
- python神經(jīng)網(wǎng)絡(luò)MobileNetV3?small模型的復(fù)現(xiàn)詳解
- python神經(jīng)網(wǎng)絡(luò)MobileNetV3?large模型的復(fù)現(xiàn)詳解
- python神經(jīng)網(wǎng)絡(luò)Inception?ResnetV2模型復(fù)現(xiàn)詳解
- python模型性能ROC和AUC分析詳解
相關(guān)文章
python 實(shí)現(xiàn)圖片旋轉(zhuǎn) 上下左右 180度旋轉(zhuǎn)的示例
今天小編就為大家分享一篇python 實(shí)現(xiàn)圖片旋轉(zhuǎn) 上下左右 180度旋轉(zhuǎn)的示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01Python 函數(shù)用法簡(jiǎn)單示例【定義、參數(shù)、返回值、函數(shù)嵌套】
這篇文章主要介紹了Python 函數(shù)用法,結(jié)合實(shí)例形式分析了Python函數(shù)定義、參數(shù)、返回值及函數(shù)嵌套相關(guān)使用技巧,需要的朋友可以參考下2019-09-09Django 創(chuàng)建新App及其常用命令的實(shí)現(xiàn)方法
這篇文章主要介紹了Django 創(chuàng)建新App及其常用命令的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Python?OpenCV實(shí)現(xiàn)圖像增強(qiáng)操作詳解
由于很多不確定因素,導(dǎo)致圖像采集的光環(huán)境極其復(fù)雜;為了提高目標(biāo)檢測(cè)模型的泛化能力,本文將使用python中的opencv模塊實(shí)現(xiàn)常見(jiàn)的圖像增強(qiáng)方法,感興趣的可以了解一下2022-10-10Python OpenCV實(shí)現(xiàn)裁剪并保存圖片
這篇文章主要為大家詳細(xì)介紹了Python OpenCV實(shí)現(xiàn)裁剪并保存圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07django的安裝和創(chuàng)建應(yīng)用過(guò)程詳解
這篇文章主要介紹了django的安裝和創(chuàng)建應(yīng)用,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07python鏈接Oracle數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了python鏈接Oracle數(shù)據(jù)庫(kù)的方法,實(shí)例分析了Python使用cx_Oracle模塊操作Oracle數(shù)據(jù)庫(kù)的相關(guān)技巧,需要的朋友可以參考下2015-06-06Webots下載安裝?+?Pycharm聯(lián)調(diào)使用教程
Webots是一個(gè)開(kāi)源的三維移動(dòng)機(jī)器人模擬器,它最初是作為研究移動(dòng)機(jī)器人中各種控制算法的研究工具開(kāi)發(fā)的,自2018年12月起,Webots作為開(kāi)源軟件發(fā)布,并獲得Apache 2.0許可證,這篇文章主要介紹了Webots下載安裝?+?Pycharm聯(lián)調(diào)?,需要的朋友可以參考下2023-02-02