基于MobileNetV2的Transfer Learning模型,实现物体检测(附源码)

news2024/9/25 5:23:33

文章目录

  • 一、MobileNet
    • 1. 深度可分离卷积(Depthwise separable convolution)
    • 2. MobileNet V1
    • 3. MobileNet V2
  • 二、物体检测源码(基于MobileNetV2)

一、MobileNet

1. 深度可分离卷积(Depthwise separable convolution)

  深度可分离卷积是卷积神经网络中对标准的卷积计算进行改进所得到的算法,它通过拆分空间维度和通道(深度)维度的相关性,减少了卷积计算所需要的参数个数,并被证明是轻量化网络的有效设计。

  深度可分离卷积由逐深度卷积(Depthwise)和逐点卷积(Pointwise)构成,如下图:
在这里插入图片描述
  对比于标准卷积,逐深度卷积将卷积核拆分成为单通道形式,在不改变输入特征图像的深度的情况下,对每一通道进行卷积操作,这样就得到了和输入特征图通道数一致的输出特征图;逐点卷积就是1×1卷积,主要作用就是对特征图进行升维和降维。

2. MobileNet V1

  MobileNet是针对手机等嵌入式设备提出的一种轻量级的深层神经网络,该网络结构在VGG的基础上使用逐深度卷积和逐点卷积的组合,在保证不损失太大精度的同时降低了模型参数量。
  MobileNet V1是MobileNet的第一个版本,是一个类似于VGG的直筒结构,如下图:
在这里插入图片描述
  整个网络结构中除了第一层是标准的卷积层,最后添加了一层全连接层以外,其余部分都是由深度可分离卷积(3×3×通道数的逐深度卷积 + 1×1×通道数×过滤器个数的逐点卷积)构成的,减少了计算的参数数量,提高了计算的效率。
  除了上述优点之外,MobileNet V1还存在以下几个问题:
  1) 结构问题:
  V1结构过于简单,没有复用图像特征,即没有concat 等操作进行特征融合,而ResNet, DenseNet等结构已经证明复用图像特征的有效性。
  2) 逐深度卷积问题:
  在处理低维数据(比如逐深度卷积)时,ReLU函数会造成信息的丢失。
  逐深度卷积由于本身的计算特性决定它自己没有改变通道数的能力,上一层给它多少通道,它就只能输出多少通道。所以如果上一层给的通道数本身很少的话,逐深度卷积层也只能在低维空间提取特征,因此效果不够好。

3. MobileNet V2

  为了解决以上问题,MobileNet V2应用而生。
  MobileNet V2提出ReLU会破坏在低维空间的数据,而高维空间影响比较少。下图的整个过程就是对一个n维空间中的一个“数据”(Input)做ReLU运算,然后(利用T的逆矩阵T-1恢复)对比ReLU之后的结果与Input的结果相差有多大。
在这里插入图片描述
  当n = 2和3时,与Input相比有很大一部分的信息已经丢失了。而当n = 15到30,还是有相当多的地方被保留了下来。也就是说,对低维度做ReLU运算,很容易造成信息的丢失。而在高维度进行ReLU运算的话,信息的丢失则会很少。
  在整体结构上,V2使用了跟V1类似的深度可分离结构,不同之处也正对应着V1中逐深度卷积的缺点改进,如下图:
在这里插入图片描述
  主要有以下两个变化:
  1) V2去掉了第二个PW(逐点卷积)的激活函数改为线性激活。 V2作者称其为 Linear Bottleneck。原因如上所述,是因为作者认为激活函数在高维空间能够有效的增加非线性,而在低维空间时则会破坏特征,不如线性的效果好。
  2) V2在DW(逐深度卷积)之前新加了一个PW卷积。 目的是用来升维,经过PW升维之后,DW可以在相对的更高维度进行更好的特征提取。
  除此之外,MobileNet V2还参照ResNet做了如下改进:在这里插入图片描述
  3) Shortcut Connection: 将输出与输入相加,可以防止过深的网络层次中特征的丢失。
  至此,以上改进后的结构被称为Inverted Residual Block with Linear Bottleneck。而最终的V2网络结构如下图:
在这里插入图片描述
  注:V3的变化不再详述。

二、物体检测源码(基于MobileNetV2)

import os, re, time, json
import PIL.Image, PIL.ImageFont, PIL.ImageDraw
import numpy as np
import tensorflow as tf
from matplotlib import pyplot as plt
import tensorflow_datasets as tfds
import cv2

# 定义数据来源
data_dir = "/content/drive/My Drive/TF3 C3 W1 Data/"

# 工具函数:画方框,物体检测需要在图片上画方框
def draw_bounding_box_on_image(image, ymin, xmin, ymax, xmax, color=(255, 0, 0), thickness=5):
    """
    Args:
      image: PIL.Image object.
      ymin: 方框左下y坐标
      xmin: 方框左下x坐标
      ymax: 方框右上y坐标
      xmax: 方框右上x坐标
      color: 框线颜色,默认红色
      thickness: 线宽,默认4
    """
    image_width = image.shape[1]
    image_height = image.shape[0]
    cv2.rectangle(image, (int(xmin), int(ymin)), (int(xmax), int(ymax)), color, thickness)

def draw_bounding_boxes_on_image(image, boxes, color=[], thickness=5):
    """
    Args:
      image: PIL.Image object.
      boxes: 二维numpy数组[N, 4]: (ymin, xmin, ymax, xmax)
             坐标会标准化到[0, 1]范围内
      color: 框线颜色,默认红色
      thickness: 线宽,默认4
    """
    boxes_shape = boxes.shape
    if not boxes_shape:
        return
    if len(boxes_shape) != 2 or boxes_shape[1] != 4:
        raise ValueError('Input must be of size [N, 4]')
    for i in range(boxes_shape[0]):
        draw_bounding_box_on_image(image, boxes[i, 1], boxes[i, 0], boxes[i, 3],
                                 boxes[i, 2], color[i], thickness)


def draw_bounding_boxes_on_image_array(image, boxes, color=[], thickness=5):
    """
    Args:
      image: a numpy array object.
      boxes: 二维numpy数组[N, 4]: (ymin, xmin, ymax, xmax)
             坐标会标准化到[0, 1]范围内
      color: 框线颜色,默认红色
      thickness: 线宽,默认4
      display_str_list_list: 每个方框上的标注
    """
    draw_bounding_boxes_on_image(image, boxes, color, thickness)
    return image

# Matplotlib画图的通用配置
plt.rc('image', cmap='gray')
plt.rc('grid', linewidth=0)
plt.rc('xtick', top=False, bottom=False, labelsize='large')
plt.rc('ytick', left=False, right=False, labelsize='large')
plt.rc('axes', facecolor='F8F8F8', titlesize="large", edgecolor='white')
plt.rc('text', color='a8151a')
plt.rc('figure', facecolor='F0F0F0')# Matplotlib fonts
MATPLOTLIB_FONT_DIR = os.path.join(os.path.dirname(plt.__file__), "mpl-data/fonts/ttf")

# 工具函数:画带预测结果的图片,用于验证结果
def display_digits_with_boxes(images, pred_bboxes, bboxes, iou, title, boxes_normalized=False):
    n = len(images)

    fig = plt.figure(figsize=(20, 4))
    plt.title(title)
    plt.yticks([])
    plt.xticks([])
  
    for i in range(n):
		ax = fig.add_subplot(1, 10, i+1)
		bboxes_to_plot = []
		if (len(pred_bboxes) > i):
			bbox = pred_bboxes[i]
        	bbox = [bbox[0] * images[i].shape[1], bbox[1] * images[i].shape[0], bbox[2] * images[i].shape[1], bbox[3] * images[i].shape[0]]
        	bboxes_to_plot.append(bbox)

		if (len(bboxes) > i):
        	bbox = bboxes[i]
        	if bboxes_normalized == True:
				bbox = [bbox[0] * images[i].shape[1],bbox[1] * images[i].shape[0], bbox[2] * images[i].shape[1], bbox[3] * images[i].shape[0] ]
			bboxes_to_plot.append(bbox)

		img_to_draw = draw_bounding_boxes_on_image_array(image=images[i], boxes=np.asarray(bboxes_to_plot), color=[(255,0,0), (0, 255, 0)])
		plt.xticks([])
		plt.yticks([])
    
		plt.imshow(img_to_draw)

		if len(iou) > i :
        	color = "black"
        	if (iou[i][0] < iou_threshold):
				color = "red"
        	ax.text(0.2, -0.3, "iou: %s" %(iou[i][0]), color=color, transform=ax.transAxes)
        
# 工具函数:画训练曲线工具,观测训练结果用
def plot_metrics(metric_name, title, ylim=5):
    plt.title(title)
    plt.ylim(0,ylim)
    plt.plot(history.history[metric_name],color='blue',label=metric_name)
    plt.plot(history.history['val_' + metric_name],color='green',label='val_' + metric_name)

# 工具函数:数据预处理,图像大小设置及标准化,方框坐标标准化
def read_image_tfds(image, bbox):
    image = tf.cast(image, tf.float32)
    shape = tf.shape(image)

    factor_x = tf.cast(shape[1], tf.float32)
    factor_y = tf.cast(shape[0], tf.float32)

    image = tf.image.resize(image, (224, 224,))
    image = image/127.5
    image -= 1

    bbox_list = [bbox[0] / factor_x, 
                 bbox[1] / factor_y, 
                 bbox[2] / factor_x, 
                 bbox[3] / factor_y]
    
    return image, bbox_list

# 工具函数:获取图像副本,并调用图像预处理函数
def read_image_with_shape(image, bbox):
    original_image = image
    image, bbox_list = read_image_tfds(image, bbox)
    return original_image, image, bbox_list

# 工具函数:从data中获取图像,及其真实坐标
def read_image_tfds_with_original_bbox(data):
    image = data["image"]
    bbox = data["bbox"]

    shape = tf.shape(image)
    factor_x = tf.cast(shape[1], tf.float32) 
    factor_y = tf.cast(shape[0], tf.float32)

    bbox_list = [bbox[1] * factor_x, 
                 bbox[0] * factor_y, 
                 bbox[3] * factor_x, 
                 bbox[2] * factor_y]
    return image, bbox_list

# 工具函数:dataset转换成图像和坐标的numpy list
# 查看图片和方框时使用
def dataset_to_numpy_util(dataset, batch_size=0, N=0):
    take_dataset = dataset.shuffle(1024)

    if batch_size > 0:
        take_dataset = take_dataset.batch(batch_size)
  
    if N > 0:
        take_dataset = take_dataset.take(N)
  
    if tf.executing_eagerly():
        ds_images, ds_bboxes = [], []
        for images, bboxes in take_dataset:
            ds_images.append(images.numpy())
            ds_bboxes.append(bboxes.numpy())
        
    return (np.array(ds_images), np.array(ds_bboxes))

# 工具函数:dataset转换成图片副本、标准化后的图片、标准化后的边框坐标
# 用于对于图片的原始边框和预测边框
def dataset_to_numpy_with_original_bboxes_util(dataset, batch_size=0, N=0):

    normalized_dataset = dataset.map(read_image_with_shape)
    if batch_size > 0:
        normalized_dataset = normalized_dataset.batch(batch_size)
  
    if N > 0:
        normalized_dataset = normalized_dataset.take(N)

    if tf.executing_eagerly():
        ds_original_images, ds_images, ds_bboxes = [], [], []
        
    for original_images, images, bboxes in normalized_dataset:
        ds_images.append(images.numpy())
        ds_bboxes.append(bboxes.numpy())
        ds_original_images.append(original_images.numpy())

    return np.array(ds_original_images), np.array(ds_images), np.array(ds_bboxes)

# 随机取部分训练图片,并展示
def get_visualization_training_dataset():      
    dataset, info = tfds.load("caltech_birds2010", split="train", with_info=True, data_dir=data_dir, download=False)
    visualization_training_dataset = dataset.map(read_image_tfds_with_original_bbox, 
                                                 num_parallel_calls=16)
    return visualization_training_dataset

visualization_training_dataset = get_visualization_training_dataset()
(visualization_training_images, visualization_training_bboxes) = dataset_to_numpy_util(visualization_training_dataset, N=10)
display_digits_with_boxes(np.array(visualization_training_images), np.array([]), np.array(visualization_training_bboxes), np.array([]), "training images and their bboxes")

# 随机取部分验证图片,并展示
def get_visualization_validation_dataset():
    dataset = tfds.load("caltech_birds2010", split="test", data_dir=data_dir, download=False)
    visualization_validation_dataset = dataset.map(read_image_tfds_with_original_bbox, num_parallel_calls=16)
    return visualization_validation_dataset

visualization_validation_dataset = get_visualization_validation_dataset()
(visualization_validation_images, visualization_validation_bboxes) = dataset_to_numpy_util(visualization_validation_dataset, N=10)
display_digits_with_boxes(np.array(visualization_validation_images), np.array([]), np.array(visualization_validation_bboxes), np.array([]), "validation images and their bboxes")

# 准备模型数据
BATCH_SIZE = 64
def get_training_dataset(dataset):
    dataset = dataset.map(read_image_tfds, num_parallel_calls=16)
    dataset = dataset.shuffle(512, reshuffle_each_iteration=True)
    dataset = dataset.repeat()
    dataset = dataset.batch(BATCH_SIZE)
    dataset = dataset.prefetch(-1) 
    return dataset

def get_validation_dataset(dataset):
    dataset = dataset.map(read_image_tfds, num_parallel_calls=16)
    dataset = dataset.batch(BATCH_SIZE)
    dataset = dataset.repeat()
    return dataset

training_dataset = get_training_dataset(visualization_training_dataset)
validation_dataset = get_validation_dataset(visualization_validation_dataset)

# MobileNet的Transfer模型
def feature_extractor(inputs):
    mobilenet_model = tf.keras.applications.mobilenet_v2.MobileNetV2(input_shape=(224,224,3), include_top=False, weights='imagenet')
    feature_extractor = mobilenet_model(inputs)
    return feature_extractor

# 顶层模型
def dense_layers(features):
    x = tf.keras.layers.GlobalAveragePooling2D()(features)    
    x = tf.keras.layers.Flatten()(x)
    x = tf.keras.layers.Dense(1024, activation='relu')(x)
    x = tf.keras.layers.Dense(512, activation='relu')(x)
    return x

# 边框输出
def bounding_box_regression(x):
    bounding_box_regression_output = tf.keras.layers.Dense(4, name='bounding_box')(x)
    return bounding_box_regression_output

# 整体模型
def final_model(inputs):
    feature_cnn = feature_extractor(inputs)
    last_dense_layer = dense_layers(feature_cnn) 
    bounding_box_output = bounding_box_regression(last_dense_layer)
    model = tf.keras.Model(inputs=inputs, outputs=bounding_box_output)
    return model

# 模型编译
def define_and_compile_model():
    inputs = tf.keras.layers.Input(shape=(224, 224, 3))
    model = final_model(inputs)
    model.compile(optimizer=tf.keras.optimizers.SGD(momentum=0.9), loss={'bounding_box':'mse'})
    return model

# 模型训练
EPOCHS = 50
BATCH_SIZE = 64

length_of_training_dataset = tf.data.experimental.cardinality(visualization_training_dataset).numpy()

length_of_validation_dataset = tf.data.experimental.cardinality(visualization_validation_dataset).numpy()

steps_per_epoch = length_of_training_dataset//BATCH_SIZE # epoch内训练步进次数
if length_of_training_dataset % BATCH_SIZE > 0:
    steps_per_epoch += 1

validation_steps = length_of_validation_dataset//BATCH_SIZE # epoch内验证步进次数
if length_of_validation_dataset % BATCH_SIZE > 0:
    validation_steps += 1

history = model.fit(training_dataset, steps_per_epoch=steps_per_epoch, validation_data=validation_dataset, validation_steps=validation_steps, epochs=EPOCHS)

# 损失评价
loss = model.evaluate(validation_dataset, steps=validation_steps)

# 模型保存
model.save("xxx.h5")

# 工具函数:对比实际值和预测值得准确性
def intersection_over_union(pred_box, true_box):
    xmin_pred, ymin_pred, xmax_pred, ymax_pred =  np.split(pred_box, 4, axis = 1)
    xmin_true, ymin_true, xmax_true, ymax_true = np.split(true_box, 4, axis = 1)

    # Calculate coordinates of overlap area between boxes
    xmin_overlap = np.maximum(xmin_pred, xmin_true)
    xmax_overlap = np.minimum(xmax_pred, xmax_true)
    ymin_overlap = np.maximum(ymin_pred, ymin_true)
    ymax_overlap = np.minimum(ymax_pred, ymax_true)

    # Calculates area of true and predicted boxes
    pred_box_area = (xmax_pred - xmin_pred) * (ymax_pred - ymin_pred)
    true_box_area = (xmax_true - xmin_true) * (ymax_true - ymin_true)

    # Calculates overlap area and union area.
    overlap_area = np.maximum((xmax_overlap - xmin_overlap),0)  * np.maximum((ymax_overlap - ymin_overlap), 0)
    union_area = (pred_box_area + true_box_area) - overlap_area

    # Defines a smoothing factor to prevent division by 0
    smoothing_factor = 1e-10

    # Updates iou score
    iou = (overlap_area + smoothing_factor) / (union_area + smoothing_factor)

    return iou

# 预测测试
original_images, normalized_images, normalized_bboxes = dataset_to_numpy_with_original_bboxes_util(visualization_validation_dataset, N=500)
predicted_bboxes = model.predict(normalized_images, batch_size=32)


# 预测边框和实际边框对比
iou = intersection_over_union(predicted_bboxes, normalized_bboxes)
iou_threshold = 0.5

# 打印对比结果
print("Number of predictions where iou > threshold(%s): %s" % (iou_threshold, (iou >= iou_threshold).sum()))
print("Number of predictions where iou < threshold(%s): %s" % (iou_threshold, (iou < iou_threshold).sum()))

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

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

相关文章

智慧园区综合管理平台开发基本功能有哪些?

随着智慧城市建设与信息化发展&#xff0c;园区管理也需要更加智能便捷化&#xff0c;于是智慧园区管理系统开发应运而生。智慧园区综合管理系统就是利用物联网、大数据等技术工具&#xff0c;顺应产业园区升级发展需求&#xff0c;实现园区环境、设备、安全、基础管理、资源服…

【Linux】进程间通信——命名管道

文章目录 命名管道1. 见一见管道文件mkfifo函数管道文件的使用 2. 命名管道原理如何保证两个毫不相关的进程&#xff0c;看到的是同一个文件&#xff0c;并打开&#xff1f; 3. 用命名管道实现server&client通信如何使用makefile连续生成可执行程序comm.hpp文件server.cc 服…

如何通过 Baklib 平台实现知识共享(内含案例介绍)

在这个信息时代&#xff0c;知识被认为是最重要的资源之一。企业要想保持发展&#xff0c;就必须善于利用和管理知识。而知识管理则是一种涵盖人、过程和技术的活动&#xff0c;它通过收集、整理、传递和应用知识&#xff0c;使组织获得更高的效率、创造力和竞争力。本文将以知…

【Linux】八、Linux进程信号详解(一)

目录 一、认识信号 1.1 生活中的信号 1.2 将1.1的概念迁移到进程 1.3 信号概念 1.4 查看系统定义信号列表 1.5 man 7 signal 1.6 解释1.2的代码样例 1.7 信号处理常见方式概览 二、产生信号 2.1 signal函数 2.2 通过终端按键产生信号 2.3 调用系统函数向进程发信号…

前后端的身份认证【Node.js】

1. 前后端的身份认证 1.1 Web 开发模式 目前主流的 Web 开发模式有两种&#xff0c;分别是&#xff1a; &#xff08;1&#xff09;基于服务端渲染的传统 Web 开发模式 &#xff08;2&#xff09;基于前后端分离的新型 Web 开发模式 服务端渲染的传统 Web 开发模式 服务端渲染…

力扣面试题 08.06. 汉诺塔问题:思路分析+图文详解+代码实现

文章目录 第一部分&#xff1a;问题描述1.1 题目1.2 示例&#x1f340; 示例一&#x1f340; 示例二 1.3 提示 第二部分&#xff1a;思路分析第三部分&#xff1a;代码实现 第一部分&#xff1a;问题描述 1.1 题目 &#x1f3e0; 链接&#xff1a;面试题 08.06. 汉诺塔问题 -…

windows安装rabbitmq和环境erlang(最详细版,包括对应关系)

写在最前&#xff1a;不知何时起安装一个mq需要翻无数文章才能安上了&#xff0c;没有一个讲全的&#xff0c;这里写一个详细的教程。 删除旧版本对应关系: 1.在官方文档中找到RabbitMQ版本对应的Erlang版本重新下载安装包文档RabbitMQ Erlang Version Requirements — Rabbit…

大家副业都在做什么?csgo搬砖靠谱的副业推荐给你

从来没想过&#xff0c;以前只会玩CSGO的男孩子&#xff0c;现在居然能借助游戏赚到钱了&#xff01;甚至不需要什么专业的技巧&#xff0c;简简单单 在steam平台选择有利润的道具后&#xff0c;再上架到国内网易BUFF平台&#xff0c;赚取“信息差”差价而已&#xff01; 谁大…

itop-3568开发板驱动学习笔记(19)内核工作队列

《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记 文章目录 工作队列简介共享工作队列工作结构体初始化 work_struct调度工作队列函数共享工作队列实验 自定义工作队列创建工作队列函数调度和取消调度工作队列刷新工作队列函数删除工作队列函数 内核延时工作队列延时…

成功上岸字节35K,技术4面+HR面,耗时20天,真是不容易

这次字节的面试&#xff0c;给我的感触很深&#xff0c;意识到基础的重要性。一共经历了五轮面试&#xff1a;技术4面&#xff0b;HR面。 下面看正文 本人自动专业毕业&#xff0c;压抑了五个多月&#xff0c;终于鼓起勇气&#xff0c;去字节面试&#xff0c;下面是我的面试过…

kali利用airgeddon破解WiFi (详细安装和使用教程)

目录 前言 一&#xff0c;软件&硬件环境 二&#xff0c;前期配置 Airgeddon安装和调试 #自带 #安装方法一 #安装方法二 #注意 网卡的配置 #打开服务 #加载网卡 三&#xff0c;运行操作 #检查 #主菜单 #打开监听模式 #查看周围可以攻击的网络 #截取…

vue - - - - - vue3全局配置挂载axios

vue3配置axios 1. 安装axios2. 配置拦截器3. vue.config.js代理配置4. 将axios全局挂载4. 文件内使用 1. 安装axios yarn add axios 2. 配置拦截器 创建文件 /src/utils/request.js "use strict";import Vue from "vue"; import axios from "axios&…

从现在起,请你不要用ChatGPT再做这4件事了

ChatGPT已经火爆了快半年了吧&#xff0c;紧接着国内也开始推出了各种仿制品&#xff0c;我甚至一度怀疑&#xff0c;如果人家没有推出ChatGPT&#xff0c;这些仿制品会不会出现。而很多人也嗨皮得不行&#xff0c;搭着梯子爬过高墙&#xff0c;用ChatGPT做各种觉得新鲜的事。但…

电脑可以开机但是无法进入到桌面怎么办?

电脑可以开机但是无法进入到桌面怎么办&#xff1f;有用户的电脑可以正确启动&#xff0c;但是电脑启动之后&#xff0c;却无法进入到系统桌面&#xff0c;而且卡在加载系统的页面中&#xff0c;或者是出现错误代码蓝屏了。这些情况其实都可以通过U盘来重装一个系统&#xff0c…

第七回:如何使用GirdView Widget

文章目录 概念介绍使用方法示例代码经验总结 我们在上一章回中介绍了Image Widget,本章回中将介绍 GirdView这种Widget&#xff0c;闲话休提&#xff0c;让我们一起Talk Flutter吧。 概念介绍 在Flutter中使用GirdView表示网格状的布局&#xff0c;类似日常办公中使用的Excel…

hashCode 如何计算?这一篇就够了!

介绍 hashCode 中文‘散列码’&#xff0c;存在的意义是加快查找速率&#xff0c;可以在常数时间内进行寻址操作。 存在意义 它被定义在 Object 中&#xff0c;而 Object 类是一切类的父类&#xff0c;所以所有的方法都具有这个方法。 Java 中 hashCode 计算方式如下&#x…

C2. Exam in BerSU (hard version)(思维 + 小数据范围)

Problem - C2 - Codeforces 简单版本和困难版本之间的唯一区别是约束。 如果你用Python写一个解决方案&#xff0c;那么最好用PyPy发送&#xff0c;以加快执行时间。 贝兰德州立大学的一场会议已经开始。许多学生正在参加考试。 波利格拉夫维奇要对N个学生进行考试。学生们将…

工业树莓派远程I/O控制套装—更高效、更灵活、更便捷

一、背景 在完整的生产过程中&#xff0c;许多传感器设备和执行设备不完全安装在同一位置&#xff0c;大多分散部署在各个生产环节中。如果采用本地控制的方式&#xff0c;就需要用到多个控制器&#xff0c;但是成本较高&#xff0c;且不利于管理&#xff0c;所以最理想的解决…

2.redis-持久化

01-Redis持久化 概述 Redis数据存储在缓存中&#xff0c;为了防止数据的意外丢失&#xff0c;确保数据安全性。所以&#xff0c;就有了redis持久化。 分类 RDB: Redis默认的持久化策略, 直接存储数据 AOF: 存储数据的操作过程. 02-RDB持久化之save指令 配置说明 # 设置rdb…

[pgrx开发postgresql数据库扩展]2.安装与开发环境的搭建

——前文再续&#xff0c;书接上一回。 前言 我上篇文章刚刚写完&#xff0c;pgx就全面改名为了 pgrx……&#xff0c;结果导致我都来不及把以前的文章改过来&#xff0c;所以以后遵循最新的命名方法。 pgrx的开发环境需求 pgrx目前仅支持在linux操作系统上进行开发&#xff…