竞赛 深度学习+opencv+python实现昆虫识别 -图像识别 昆虫识别

文章目录

  • 0 前言
  • 1 课题背景
  • 2 具体实现
  • 3 数据收集和处理
  • 3 卷积神经网络
    • 2.1卷积层
    • 2.2 池化层
    • 2.3 激活函数:
    • 2.4 全连接层
    • 2.5 使用tensorflow中keras模块实现卷积神经网络
  • 4 MobileNetV2网络
  • 5 损失函数softmax 交叉熵
    • 5.1 softmax函数
    • 5.2 交叉熵损失函数
  • 6 优化器SGD
  • 7 学习率衰减策略
  • 6 最后

0 前言

🔥 优质竞赛项目系列,今天要分享的是

🚩 **基于深度学习的昆虫识别算法研究与实现 **

该项目较为新颖,适合作为竞赛课题方向,学长非常推荐!

🥇学长这里给一个题目综合评分(每项满分5分)

  • 难度系数:3分
  • 工作量:4分
  • 创新点:4分

🧿 更多资料, 项目分享:

https://gitee.com/dancheng-senior/postgraduate

1 课题背景

中国是农业大国,在传统的农业生产中,经常会受到病虫害问题的困扰。在解决病虫害问题时,第一步是识别昆虫。在传统的昆虫识别方法中,昆虫专家根据专业知识观察昆虫的外部特征,并对照相关的昆虫图鉴进行识别,费时费力。如今,传统的昆虫识别方法逐渐被昆虫图像识别技术代替。目前常用的昆虫识别技术有图像识别法、微波雷达检测法、生物光子检测法、取样检测法、近红外及高光谱法、声测法等。近年来,随着人工智能的迅速发展,深度学习技术在处理自然语言、机器视觉等方面取得了很多成果,随着深度学习的发展,已经有研究人员开始将深度学习技术应用于昆虫的图像识别。文章旨在利用基于深度学习的图像识别技术解决昆虫识别问题,希望能给现实生活中的病虫害识别问题提供新的解决问题的思路。

2 具体实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3 数据收集和处理

数据是深度学习的基石
数据的主要来源有: 百度图片, 必应图片, 新浪微博, 百度贴吧, 新浪博客和一些专业的昆虫网站等
爬虫爬取的图像的质量参差不齐, 标签可能有误, 且存在重复文件, 因此必须清洗。清洗方法包括自动化清洗, 半自动化清洗和手工清洗。
自动化清洗包括:

  • 滤除小尺寸图像.
  • 滤除宽高比很大或很小的图像.
  • 滤除灰度图像.
  • 图像去重: 根据图像感知哈希.

半自动化清洗包括:

  • 图像级别的清洗: 利用预先训练的昆虫/非昆虫图像分类器对图像文件进行打分, 非昆虫图像应该有较低的得分; 利用前一阶段的昆虫分类器对图像文件 (每个文件都有一个预标类别) 进行预测, 取预标类别的概率值为得分, 不属于原预标类别的图像应该有较低的得分. 可以设置阈值, 滤除很低得分的文件; 另外利用得分对图像文件进行重命名, 并在资源管理器选择按文件名排序, 以便于后续手工清洗掉非昆虫图像和不是预标类别的图像.
  • 类级别的清洗

手工清洗: 人工判断文件夹下图像是否属于文件夹名所标称的物种, 这需要相关的昆虫学专业知识, 是最耗时且枯燥的环节。

3 卷积神经网络

卷积神经网络(Convolutional Neural
Netwoek,CNN)是一种前馈神经网络,它的人工神经元可以局部响应周围的神经元,每个神经元都接收一些输入,并做一些点积计算。它通常包含卷积层、激活层、池化层、全连接层。
在这里插入图片描述

2.1卷积层

卷积核相当于一个滑动窗口,示意图中3x3大小的卷积核依次划过6x6大小的输入数据中的对应区域,并与卷积核滑过区域做矩阵点乘,将所得结果依次填入对应位置即可得到右侧4x4尺寸的卷积特征图,例如划到右上角3x3所圈区域时,将进行0x0+1x1+2x1+1x1+0x0+1x1+1x0+2x0x1x1=6的计算操作,并将得到的数值填充到卷积特征的右上角。

https://img-blog.csdnimg.cn/e1d4a146d12c4348bbc24790333cf8ba.png

2.2 池化层

池化操作又称为降采样,提取网络主要特征可以在达到空间不变性的效果同时,有效地减少网络参数,因而简化网络计算复杂度,防止过拟合现象的出现。在实际操作中经常使用最大池化或平均池化两种方式,如下图所示。虽然池化操作可以有效的降低参数数量,但过度池化也会导致一些图片细节的丢失,因此在搭建网络时要根据实际情况来调整池化操作。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-
UTsB7AhE-1658995487680)(C:\Users\Administrator\AppData\Roaming\Typora\typora-
user-images\image-20220709114210181.png)]

2.3 激活函数:

激活函数大致分为两种,在卷积神经网络的发展前期,使用较为传统的饱和激活函数,主要包括sigmoid函数、tanh函数等;随着神经网络的发展,研宄者们发现了饱和激活函数的弱点,并针对其存在的潜在问题,研宄了非饱和激活函数,其主要含有ReLU函数及其函数变体

2.4 全连接层

在整个网络结构中起到“分类器”的作用,经过前面卷积层、池化层、激活函数层之后,网络己经对输入图片的原始数据进行特征提取,并将其映射到隐藏特征空间,全连接层将负责将学习到的特征从隐藏特征空间映射到样本标记空间,一般包括提取到的特征在图片上的位置信息以及特征所属类别概率等。将隐藏特征空间的信息具象化,也是图像处理当中的重要一环。

2.5 使用tensorflow中keras模块实现卷积神经网络

class CNN(tf.keras.Model):def __init__(self):super().__init__()self.conv1 = tf.keras.layers.Conv2D(filters=32,             # 卷积层神经元(卷积核)数目kernel_size=[5, 5],     # 感受野大小padding='same',         # padding策略(vaild 或 same)activation=tf.nn.relu   # 激活函数)self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)self.conv2 = tf.keras.layers.Conv2D(filters=64,kernel_size=[5, 5],padding='same',activation=tf.nn.relu)self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2)self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,))self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)self.dense2 = tf.keras.layers.Dense(units=10)def call(self, inputs):x = self.conv1(inputs)                  # [batch_size, 28, 28, 32]x = self.pool1(x)                       # [batch_size, 14, 14, 32]x = self.conv2(x)                       # [batch_size, 14, 14, 64]x = self.pool2(x)                       # [batch_size, 7, 7, 64]x = self.flatten(x)                     # [batch_size, 7 * 7 * 64]x = self.dense1(x)                      # [batch_size, 1024]x = self.dense2(x)                      # [batch_size, 10]output = tf.nn.softmax(x)return output

4 MobileNetV2网络

简介

MobileNet网络是Google最近提出的一种小巧而高效的CNN模型,其在accuracy和latency之间做了折中。

主要改进点

相对于MobileNetV1,MobileNetV2 主要改进点:

  • 引入倒残差结构,先升维再降维,增强梯度的传播,显著减少推理期间所需的内存占用(Inverted Residuals)
  • 去掉 Narrow layer(low dimension or depth) 后的 ReLU,保留特征多样性,增强网络的表达能力(Linear Bottlenecks)
  • 网络为全卷积,使得模型可以适应不同尺寸的图像;使用 RELU6(最高输出为 6)激活函数,使得模型在低精度计算下具有更强的鲁棒性
  • MobileNetV2 Inverted residual block 如下所示,若需要下采样,可在 DW 时采用步长为 2 的卷积
  • 小网络使用小的扩张系数(expansion factor),大网络使用大一点的扩张系数(expansion factor),推荐是5~10,论文中 t = 6 t = 6t=6

倒残差结构(Inverted residual block

ResNet的Bottleneck结构是降维->卷积->升维,是两边细中间粗

而MobileNetV2是先升维(6倍)-> 卷积 -> 降维,是沙漏形。
在这里插入图片描述区别于MobileNetV1,
MobileNetV2的卷积结构如下:
在这里插入图片描述
因为DW卷积不改变通道数,所以如果上一层的通道数很低时,DW只能在低维空间提取特征,效果不好。所以V2版本在DW前面加了一层PW用来升维。

同时V2去除了第二个PW的激活函数改用线性激活,因为激活函数在高维空间能够有效地增加非线性,但在低维空间时会破坏特征。由于第二个PW主要的功能是降维,所以不宜再加ReLU6。
在这里插入图片描述
tensorflow相关实现代码

import tensorflow as tfimport numpy as npfrom tensorflow.keras import layers, Sequential, Modelclass ConvBNReLU(layers.Layer):def __init__(self, out_channel, kernel_size=3, strides=1, **kwargs):super(ConvBNReLU, self).__init__(**kwargs)self.conv = layers.Conv2D(filters=out_channel, kernel_size=kernel_size, strides=strides, padding='SAME', use_bias=False,name='Conv2d')self.bn = layers.BatchNormalization(momentum=0.9, epsilon=1e-5, name='BatchNorm')self.activation = layers.ReLU(max_value=6.0)   # ReLU6def call(self, inputs, training=False, **kargs):x = self.conv(inputs)x = self.bn(x, training=training)x = self.activation(x)return xclass InvertedResidualBlock(layers.Layer):def __init__(self, in_channel, out_channel, strides, expand_ratio, **kwargs):super(InvertedResidualBlock, self).__init__(**kwargs)self.hidden_channel = in_channel * expand_ratioself.use_shortcut = (strides == 1) and (in_channel == out_channel)layer_list = []# first bottleneck does not need 1*1 convif expand_ratio != 1:# 1x1 pointwise convlayer_list.append(ConvBNReLU(out_channel=self.hidden_channel, kernel_size=1, name='expand'))layer_list.extend([# 3x3 depthwise conv layers.DepthwiseConv2D(kernel_size=3, padding='SAME', strides=strides, use_bias=False, name='depthwise'),layers.BatchNormalization(momentum=0.9, epsilon=1e-5, name='depthwise/BatchNorm'),layers.ReLU(max_value=6.0),#1x1 pointwise conv(linear) # linear activation y = x -> no activation functionlayers.Conv2D(filters=out_channel, kernel_size=1, strides=1, padding='SAME', use_bias=False, name='project'),layers.BatchNormalization(momentum=0.9, epsilon=1e-5, name='project/BatchNorm')])self.main_branch = Sequential(layer_list, name='expanded_conv')def call(self, inputs, **kargs):if self.use_shortcut:return inputs + self.main_branch(inputs)else:return self.main_branch(inputs)  


5 损失函数softmax 交叉熵

5.1 softmax函数

Softmax函数由下列公式定义
在这里插入图片描述
softmax 的作用是把 一个序列,变成概率。

在这里插入图片描述

softmax用于多分类过程中,它将多个神经元的输出,映射到(0,1)区间内,所有概率的和将等于1。

python实现

def softmax(x):shift_x = x - np.max(x)    # 防止输入增大时输出为nanexp_x = np.exp(shift_x)return exp_x / np.sum(exp_x)

PyTorch封装的Softmax()函数

dim参数:

  • dim为0时,对所有数据进行softmax计算

  • dim为1时,对某一个维度的列进行softmax计算

  • dim为-1 或者2 时,对某一个维度的行进行softmax计算

    import torch
    x = torch.tensor([2.0,1.0,0.1])
    x.cuda()
    outputs = torch.softmax(x,dim=0)
    print("输入:",x)
    print("输出:",outputs)
    print("输出之和:",outputs.sum())
    

5.2 交叉熵损失函数

定义如下:
在这里插入图片描述
python实现

def cross_entropy(a, y):return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))# tensorflow version
loss = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y), reduction_indices=[1]))# numpy version
loss = np.mean(-np.sum(y_*np.log(y), axis=1))

PyTorch实现
交叉熵函数分为二分类(torch.nn.BCELoss())和多分类函数(torch.nn.CrossEntropyLoss()

# 二分类 损失函数loss = torch.nn.BCELoss()l = loss(pred,real)# 多分类损失函数loss = torch.nn.CrossEntropyLoss()

6 优化器SGD

简介
SGD全称Stochastic Gradient Descent,随机梯度下降,1847年提出。每次选择一个mini-
batch,而不是全部样本,使用梯度下降来更新模型参数。它解决了随机小批量样本的问题,但仍然有自适应学习率、容易卡在梯度较小点等问题。
在这里插入图片描述
pytorch调用方法:

torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)

相关代码:

    def step(self, closure=None):"""Performs a single optimization step.Arguments:closure (callable, optional): A closure that reevaluates the modeland returns the loss."""loss = Noneif closure is not None:loss = closure()for group in self.param_groups:weight_decay = group['weight_decay'] # 权重衰减系数momentum = group['momentum'] # 动量因子,0.9或0.8dampening = group['dampening'] # 梯度抑制因子nesterov = group['nesterov'] # 是否使用nesterov动量for p in group['params']:if p.grad is None:continued_p = p.grad.dataif weight_decay != 0: # 进行正则化# add_表示原处改变,d_p = d_p + weight_decay*p.datad_p.add_(weight_decay, p.data)if momentum != 0:param_state = self.state[p] # 之前的累计的数据,v(t-1)# 进行动量累计计算if 'momentum_buffer' not in param_state:buf = param_state['momentum_buffer'] = torch.clone(d_p).detach()else:# 之前的动量buf = param_state['momentum_buffer']# buf= buf*momentum + (1-dampening)*d_pbuf.mul_(momentum).add_(1 - dampening, d_p)if nesterov: # 使用neterov动量# d_p= d_p + momentum*bufd_p = d_p.add(momentum, buf)else:d_p = buf# p = p - lr*d_pp.data.add_(-group['lr'], d_p)return loss

7 学习率衰减策略

余弦退火衰减
这可以理解为是一种带重启的随机梯度下降算法。在网络模型更新时,由于存在很多局部最优解,这就导致模型会陷入局部最优解,即优化函数存在多个峰值。这就要求,当模型陷入局部最优解时,能够跳出去,并且继续寻找下一个最优解,直到找到全局最优解。要使得模型跳出局部最优解,就需

多周期的余弦退火衰减示意图如下:
在这里插入图片描述
相关代码实现

# ----------------------------------------------------------------------- ## 多周期余弦退火衰减# ----------------------------------------------------------------------- ## eager模式防止graph报错tf.config.experimental_run_functions_eagerly(True)# ------------------------------------------------ #import math# 继承自定义学习率的类class CosineWarmupDecay(keras.optimizers.schedules.LearningRateSchedule):'''initial_lr: 初始的学习率min_lr: 学习率的最小值max_lr: 学习率的最大值warmup_step: 线性上升部分需要的steptotal_step: 第一个余弦退火周期需要对总stepmulti: 下个周期相比于上个周期调整的倍率print_step: 多少个step并打印一次学习率'''# 初始化def __init__(self, initial_lr, min_lr, warmup_step, total_step, multi, print_step):# 继承父类的初始化方法super(CosineWarmupDecay, self).__init__()# 属性分配self.initial_lr = tf.cast(initial_lr, dtype=tf.float32)self.min_lr = tf.cast(min_lr, dtype=tf.float32)self.warmup_step = warmup_step  # 初始为第一个周期的线性段的stepself.total_step = total_step    # 初始为第一个周期的总stepself.multi = multiself.print_step = print_step# 保存每一个step的学习率self.learning_rate_list = []# 当前步长self.step = 0# 前向传播, 训练时传入当前step,但是上面已经定义了一个,这个step用不上def __call__(self, step):# 如果当前step达到了当前周期末端就调整if  self.step>=self.total_step:# 乘上倍率因子后会有小数,这里要注意# 调整一个周期中线性部分的step长度self.warmup_step = self.warmup_step * (1 + self.multi)# 调整一个周期的总step长度self.total_step = self.total_step * (1 + self.multi)# 重置step,从线性部分重新开始self.step = 0# 余弦部分的计算公式decayed_learning_rate = self.min_lr + 0.5 * (self.initial_lr - self.min_lr) *       \(1 + tf.math.cos(math.pi * (self.step-self.warmup_step) /        \(self.total_step-self.warmup_step)))# 计算线性上升部分的增长系数kk = (self.initial_lr - self.min_lr) / self.warmup_step # 线性增长线段 y=kx+bwarmup = k * self.step + self.min_lr# 以学习率峰值点横坐标为界,左侧是线性上升,右侧是余弦下降decayed_learning_rate = tf.where(self.step<self.warmup_step, warmup, decayed_learning_rate)# 每个epoch打印一次学习率if step % self.print_step == 0:# 打印当前step的学习率print('learning_rate has changed to: ', decayed_learning_rate.numpy().item())# 每个step保存一次学习率self.learning_rate_list.append(decayed_learning_rate.numpy().item())# 计算完当前学习率后step加一用于下一次self.step = self.step + 1# 返回调整后的学习率return decayed_learning_rate

6 最后

🧿 更多资料, 项目分享:

https://gitee.com/dancheng-senior/postgraduate

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/104077.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt通过正则表达式筛选出字符串中的手机号

需求 用户需要聊天记录中含有11位的手机号码进行提醒的功能&#xff0c;所以需要在收到聊天消息后匹配查看是否存在手机号。如果找到然后提醒。 分析 主要的需求可以拆分为两点&#xff1a; 筛选出字符串里面的数字字符。通过正则匹配数字字符是否是11位手机号码。 一开始没…

Java设计模式之享元模式

享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过共享对象来减少内存使用和提高性能。在享元模式中&#xff0c;将对象分为可共享的内部状态和不可共享的外部状态。内部状态是对象共享的部分&#xff0c;而外部状态是对象的变化部分&…

【网安必读】CTF/AWD实战速胜指南《AWD特训营》

文章目录 前言&#x1f4ac;正文这本书好在哪❔这本书讲了什么❔文末送书 前言&#x1f4ac; 【文末送书】今天推荐一本网安领域优质书籍《AWD特训营》&#xff0c;本文将从其内容与优势出发&#xff0c;详细阐发其对于网安从业人员的重要性与益处。 正文 &#x1f52d;本书…

软考高级系统架构设计师系列之:深入理解设计模式

软考高级系统架构设计师系列之:深入理解设计模式 一、设计模式相关技术文章二、设计原则三、设计模式概念四、设计模式的分类五、创建性模式六、创建性模式-工厂方法模式七、创建性模式-抽象工厂模式八、创建性模式-构建器模式九、面向对象设计-结构性模式十、结构性模式-适配…

数据结构 | Huffman TreeCode

构造参考&#xff1a; 赫夫曼树_关于huffman树,权值相同-CSDN博客 编码参考&#xff1a; 【数据结构与算法】-哈夫曼树(Huffman Tree)与哈夫曼编码_数据结构哈夫曼树编码-CSDN博客

在springboot中如何开启Bean数据校验

①&#xff1a;添加JSR303规范坐标与Hibernate校验框架对应坐标 <dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId> </dependency><dependency><groupId>org.hibernate.validator<…

系统架构设计:20 论软件需求管理

目录 一 需求工程 1 需求开发 1.1 需求获取 1.1.1 软件需求的分类 1.1.2 需求获取方法

开启多线程渲染后出现大量的Crash信息

1&#xff09;开启多线程渲染后出现大量的Crash信息 2&#xff09;Unity是否有提供硬件接口将纹理从Gamma值转为Linear值 3&#xff09;屏幕特效如何适配不同分辨率 这是第356篇UWA技术知识分享的推送&#xff0c;精选了UWA社区的热门话题&#xff0c;涵盖了UWA问答、社区帖子等…

《论文阅读:Dataset Condensation with Distribution Matching》

点进去这篇文章的开源地址&#xff0c;才发现这篇文章和DC DSA居然是一个作者&#xff0c;数据浓缩写了三篇论文&#xff0c;第一篇梯度匹配&#xff0c;第二篇数据增强后梯度匹配&#xff0c;第三篇匹配数据分布。DC是匹配浓缩数据和原始数据训练一次后的梯度差&#xff0c;DS…

idea怎么设置作者信息(详细)

目录 一&#xff1a;在Java类的开头自动注释作者名字和日期等信息 二&#xff1a;给Java的方法注释作者名字和日期等信息 1. 不可修改的模板&#xff1a;Postfix Completion 2. 可修改的模板&#xff1a;Live Templates tips&#xff1a;首先给大家推荐两款好用的免费软件&…

一文就懂大语言模型Llama2 7B+中文alpace模型本地部署

大语言模型Llama2 7B中文alpace模型本地部署 VX关注晓理紫并回复llama获取推理模型 [晓理紫] 1、Llama模型 一个由facebook发布的生成式语言模型&#xff0c;具体可以到其官方了解。 为了大家更好理解&#xff0c;这里把目录结构显示下一如下图。 2、 下载Llama并配置环境 …

系统架构设计:11 论湖仓一体架构及其应用

目录 一 湖仓一体(Lakehouse) 1 数据仓库 2 数据湖 3 数据仓库和数据湖 4 湖仓一体(Lakehouse)

什么是微服务架构

阅读“微服务架构”一词可能会让您直观地了解该术语的含义&#xff1a;计算架构中的小型服务。这个定义并不完全错误&#xff0c;但也不完全正确。 微服务架构通常被称为“打破整体”的一种方式。遗憾的是&#xff0c;这与《2001&#xff1a;太空漫游》无关&#xff0c;而是将…

Openstack部署

搭建基础环境 #网络 #防火墙 #用户用 #解析 #同步时间 实验角色 OpenStack01OpenStack02OpenStack03192.168.1.101192.168.1.102192.168.1.103srv1srv2srv3 同步时间 [rootsrv1]# yum install chrony -y [rootsrv1]# vim /etc/chrony.conf # 修改第3行&#xff0c;将NT…

为Mkdocs网站添加评论系统(以giscus为例)

官方文档&#xff1a;Adding a comment system 这里我同样推荐giscus 利用 GitHub Discussions 实现的评论系统&#xff0c;让访客借助 GitHub 在你的网站上留下评论和反应吧&#xff01;本项目深受 utterances 的启发。 开源。&#x1f30f;无跟踪&#xff0c;无广告&#…

标准差有两种常见的估计方法:有偏估计和无偏估计

当我们谈论标准差时&#xff0c;有两种常见的估计方法&#xff1a;有偏估计和无偏估计。 有偏估计&#xff08;Biased Estimate&#xff09;&#xff1a;有偏估计是指使用样本标准差来估计总体标准差&#xff0c;而不应用修正因子。这种估计方法在某些情况下可能导致总体标准差…

灾备建设中的网络传输

对于建设灾备系统&#xff0c;只要是网络可达即可进行数据备份保护。灾备中用的传输方式有很多种&#xff0c;比如网络传输&#xff0c;lan-free传输&#xff0c;网络加密传输等。 在这里给大家介绍下网络传输&#xff0c;灾备中的网络传输和平时大家熟知的是一样的。是指用一…

k8s使用

一、Kubernetes好处 ​ kubernetes&#xff0c;是一个全新的基于容器技术的分布式架构领先方案&#xff0c;是谷歌严格保密十几年的秘密武器----Borg系统的一个开源版本&#xff0c;于2014年9月发布第一个版本&#xff0c;2015年7月发布第一个正式版本。 ​ kubernetes的本质…

前端面试基础面试题——9

1.js 延迟加载的方式有哪些&#xff1f; 2.js同步和异步的区别&#xff1f; 3.什么是浏览器的同源政策&#xff1f; 4.介绍一下 js 的节流与防抖&#xff1f; 5.js 中的深浅拷贝实现&#xff1f; 6.Js 动画与 CSS 动画区别及相应实现 7.观察者模式和发布订阅模式有什么不同…

GIS小技术分享(一):python中json数据转geojson或者shp

1.环境需求 geopandspandasshapelyjsonpython3 2.输入数据&#xff08;path字段&#xff0c;线条&#xff09; [{"id": "586A685D568311B2A16F33FCD5055F7B","name": "普及江","path": "[[116.35178835446628,23.57…