PaddleServing图像语义分割部署实践

2023-05-16

目录

一、任务概述

二、官方示例部署

2.1 安装PaddleServing

2.2 导出静态图模型

2.3 转换为serving模型

 2.4 启动服务

 2.5 客户端请求

三、基于PipeLine的抠图功能部署

3.1 基于深度学习的抠图功能测试

3.1.1 算法库下载

3.1.2 抠图算法说明

3.1.3 抠图算法测试

3.2 基于PipeLine的Serving部署

3.2.1 转换为serving部署模型

3.2.2 设置config.yml部署配置文件

3.2.3 编写服务端脚本文件

3.2.4 客户端调用

四、小结


一、任务概述

本教程基于PaddleServing实现图像语义分割模型部署。首先我们会按照官方示例将部署流程跑一边,然后我们逐步调整代码和配置,基于更通用的PipeLine模式全流程实现抠图功能部署。

二、官方示例部署

2.1 安装PaddleServing

从官网下载最新稳定离线版whl文件进行安装,各组件安装命令如下:

  • 安装客户端:pip install paddle-serving-client
  • 安装服务器端(CPU或者GPU版二选一):
    • 安装CPU服务端:pip install paddle-serving-server
    • 安装GPU服务端:pip install paddle-serving-server-gpu
  • 安装工具组件:pip install paddle-serving-app

在安装时为了加速可以添加百度镜像源参数:

-i https://mirror.baidu.com/pypi/simple

2.2 导出静态图模型

一般来说我们使用PaddlePaddle动态图训练出来的模型如果直接部署,其推理效率是比较低的。为了能够实现高效、稳定部署,我们需要将训练好的模型转换为静态图模型。

导出示例请参考官网说明。

这里我们使用官网示例给出的转换好的模型进行操作。

下载静态图模型:

wget https://paddleseg.bj.bcebos.com/dygraph/demo/bisenet_demo_model.tar.gz
tar zxvf bisenet_demo_model.tar.gz

解压后看到模型文件夹中内容如下所示:

 然后我们准备一张用于测试的街景图像:

 到这里,需要的部署数据都准备好了。

2.3 转换为serving模型

为了能够使用Paddle Serving工具实现AI服务器云部署,我们需要前面准备好的静态图模型转换为Paddle Serving可以使用的部署模型。

我们将使用paddle_serving_client.convert工具进行转换,具体命令如下:

python -m paddle_serving_client.convert \
    --dirname ./bisenetv2_demo_model \
    --model_filename model.pdmodel \
    --params_filename model.pdiparams

执行完成后,当前目录下的serving_server文件夹保存服务端模型和配置,serving_client文件夹保存客户端模型和配置,如下图所示:

 2.4 启动服务

按照官方示例,我们使用paddle_serving_server.serve的RPC服务模式,详细信息请参考文档。(需要注意的是,这种模式本质上是C/S架构,优势是响应快,缺点是在客户端需要安装相应的库并需要编写预处理代码)

我们在服务器端使用27008端口。

python3 -m paddle_serving_server.serve \
    --model serving_server \
    --thread 10 \
    --port 27008 \
    --ir_optim

启动后如果我们机器上没有安装对应版本的tensorrt,那么启动会出现如下错误:

error while loading shared libraries: libnvinfer.so.6: cannot open shared object file: No such file or directory

我们需要下载tensorrt库并将其添加到自己的环境变量去(注意tensortrt版本要与我们安装的paddle-serving-server-gpu版本一致)。相关解决方案请参考博客。安装完成后需要导入环境变量:

打开并编辑bashrc文件:

vim ~/.bashrc

在文件最后添加:

export LD_LIBRARY_PATH=/home/suser/copy/TensorRT-6.0.1.8/lib:$LD_LIBRARY_PATH

保存修改后把相关文件进行拷贝:

sudo cp TensorRT-6.0.1.8/targets/x86_64-linux-gnu/lib/libnvinfer.so.6  /usr/lib/

最后使用下面的命令使其生效:

source ~/.bashrc

重新启动服务端就可以正常跑起来了。

 2.5 客户端请求

客户端采用python脚本进行访问请求。

完整请求代码如下:

import os
import numpy as np
import argparse
from PIL import Image as PILImage

from paddle_serving_client import Client
from paddle_serving_app.reader import Sequential, File2Image, Resize, CenterCrop
from paddle_serving_app.reader import RGB2BGR, Transpose, Div, Normalize

def get_color_map_list(num_classes, custom_color=None):
    num_classes += 1
    color_map = num_classes * [0, 0, 0]
    for i in range(0, num_classes):
        j = 0
        lab = i
        while lab:
            color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j))
            color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j))
            color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j))
            j += 1
            lab >>= 3
    color_map = color_map[3:]

    if custom_color:
        color_map[:len(custom_color)] = custom_color
    return color_map

def get_pseudo_color_map(pred, color_map=None):
    pred_mask = PILImage.fromarray(pred.astype(np.uint8), mode='P')
    if color_map is None:
        color_map = get_color_map_list(256)
    pred_mask.putpalette(color_map)
    return pred_mask


def parse_args():
    parser = argparse.ArgumentParser(description='')
    parser.add_argument(
        "--serving_client_path",
        help="The path of serving_client file.",
        type=str,
        required=True)
    parser.add_argument(
        "--serving_ip_port",
        help="The serving ip.",
        type=str,
        default="127.0.0.1:9292",
        required=True)
    parser.add_argument(
        "--image_path", help="The image path.", type=str, required=True)
    return parser.parse_args()


def run(args):
    client = Client()
    client.load_client_config(
        os.path.join(args.serving_client_path, "serving_client_conf.prototxt"))
    client.connect([args.serving_ip_port])

    seq = Sequential([
        File2Image(), RGB2BGR(), Div(255),
        Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5], False), Transpose((2, 0, 1))
    ])

    img = seq(args.image_path)
    fetch_map = client.predict(
        feed={"x": img}, fetch=["save_infer_model/scale_0.tmp_1"])

    result = fetch_map["save_infer_model/scale_0.tmp_1"]
    color_img = get_pseudo_color_map(result[0])
    color_img.save("./result.png")
    print("The segmentation image is saved in ./result.png")


if __name__ == '__main__':
    args = parse_args()
    run(args)

然后使用下面的命令启动:

python test.py \
    --serving_client_path serving_client \
    --serving_ip_port 127.0.0.1:27008 \
    --image_path cityscapes_demo.png

运行后可能会出现下面的错误:

 libcrypto.so.10: cannot open shared object file: No such file or directory

解决方案如下:

wget https://paddle-serving.bj.bcebos.com/others/centos_ssl.tar 
tar xf centos_ssl.tar 
rm -rf centos_ssl.tar
sudo mv libcrypto.so.1.0.2k /usr/lib/libcrypto.so.1.0.2k
sudo mv libssl.so.1.0.2k /usr/lib/libssl.so.1.0.2k 
sudo ln -sf /usr/lib/libcrypto.so.1.0.2k /usr/lib/libcrypto.so.10 
sudo ln -sf /usr/lib/libssl.so.1.0.2k /usr/lib/libssl.so.10 
sudo ln -sf /usr/lib/libcrypto.so.10 /usr/lib/libcrypto.so
sudo ln -sf /usr/lib/libssl.so.10 /usr/lib/libssl.so

修改后重新执行客户端请求代码,结果如下图所示:

I0423 10:45:54.155158 20937 naming_service_thread.cpp:202] brpc::policy::ListNamingService("127.0.0.1:27008"): added 1
I0423 10:46:01.689276 20937 general_model.cpp:490] [client]logid=0,client_cost=7292.98ms,server_cost=6954.06ms.
The segmentation image is saved in ./result.png

执行完成后,分割的图片保存在当前目录的"result.png"。

分割结果如下图所示:

 想要彻底停止服务可以使用下面的命令:

ps -ef | grep serving | awk '{print $2}' | xargs kill -9
ps -ef | grep web_service | awk '{print $2}' | xargs kill -9

从整个执行上来分析,这种基于RPC的方式有个明显的缺点,就是需要客户端来实现所有的预处理和后处理操作。这对于跨语言的应用任务来说是比较麻烦的,例如我们如果采用java作为前台语言,那么就只能使用java来执行图像相关预处理和后处理。为了解决这个问题,paddle serving提供了java版的客户端,其本质是一个封装好的基于java的图像预处理工具。前端程序员还是需要手工编写客户端代码,协调合作时比较麻烦。

下面我们将使用另一种pipeline的方法,所有的预处理和后处理也一起交给服务端去做,这样就彻底跟前端功能剥离开来,前后端之间通过http接口进行通讯。这种方式相对于RPC模式来说速度会慢一些,但是很显然,其通用性更好。

下面我们就使用一个更加具体的抠图任务来实现整个的PipeLine部署。

三、基于PipeLine的抠图功能部署

3.1 基于深度学习的抠图功能测试

3.1.1 算法库下载

首先从github官网下载最新的paddleseg套件,也可以从我的gitee镜像上下载(速度会快一些):

git clone https://gitee.com/binghai228/PaddleSeg.git

然后进行安装:

cd PaddleSeg
pip install -r requirements.txt -i https://mirror.baidu.com/pypi/simple
python setup.py install

注意如果本地安装失败,也可以使用在线安装方式:

cd PaddleSeg
pip install -r requirements.txt -i https://mirror.baidu.com/pypi/simple
pip install paddleseg==2.5.0

cd到Matting文件夹中:

cd PaddleSeg/Matting

这个文件夹下面就是PaddleSeg官方在维护的抠图算法套件。

3.1.2 抠图算法说明

Matting(精细化分割/影像去背/抠图)是指借由计算前景的颜色和透明度,将前景从影像中撷取出来的技术,可用于替换背景、影像合成、视觉特效,在电影工业中被广泛地使用。影像中的每个像素会有代表其前景透明度的值,称作阿法值(Alpha),一张影像中所有阿法值的集合称作阿法遮罩(Alpha Matte),将影像被遮罩所涵盖的部分取出即可完成前景的分离。

相关功能实现效果如下:

PaddleSeg套件提供多种场景人像抠图模型, 可根据实际情况选择相应模型。这里我们选择PP-Matting-512模型进行部署应用。读者也可以参照官网教程自行训练模型,然后转为静态图模型使用。本教程更偏重算法部署,对于算法原理和训练本教程不再深入阐述,对深度学习抠图有兴趣的读者可以参考我的另一篇博客了解相关算法原理。

3.1.3 抠图算法测试

首先下载训练好的模型。如下图所示:

模型下载后解压放置在Matting/data文件夹下。

然后我们下载PPM-100数据集用于后续测试。下载下来后解压放置在Matting/data目录下。

最终我们可以使用下面的脚本命令进行测试:

python deploy/python/infer.py \
    --config data/pp-matting-hrnet_w18-human_512/deploy.yaml \
    --image_path data/PPM-100/val/fg/ \
    --save_dir output/results

推理完成后在output/results目录下保存了抠图后的测试图像结果。

部分效果如下:

 

  从效果上分析,整体抠图性能还是比较好的。

 当然,对于一些复杂的照片抠图效果还是有待再提高的,例如下面的示例:

  

3.2 基于PipeLine的Serving部署

3.2.1 转换为serving部署模型

使用paddle_serving_client.convert工具进行转换,具体命令如下:

python -m paddle_serving_client.convert \
    --dirname ./data/pp-matting-hrnet_w18-human_512 \
    --model_filename model.pdmodel \
    --params_filename model.pdiparams

执行完成后,当前目录下的serving_server文件夹保存服务端模型和配置,serving_client文件夹保存客户端模型和配置,如下图所示:

 我们打开serving_server_conf.prototxt文件,其内容如下所示:

feed_var {
  name: "img"
  alias_name: "img"
  is_lod_tensor: false
  feed_type: 1
  shape: 3
}
fetch_var {
  name: "tmp_75"
  alias_name: "tmp_75"
  is_lod_tensor: false
  fetch_type: 1
  shape: 1
}

根据这个文件,我们在写部署代码的时候需要注意对应的输入、输出变量名称,这里输入变量名为img,输出变量名为tmp_75。

3.2.2 设置config.yml部署配置文件

在当前目录下新建config.yml文件,内容如下:

dag:
  #op资源类型, True, 为线程模型;False,为进程模型
  is_thread_op: false
  #使用性能分析, True,生成Timeline性能数据,对性能有一定影响;False为不使用
  use_profile: false
  # tracer:
  #   interval_s: 30
#http端口, rpc_port和http_port不允许同时为空。当rpc_port可用且http_port为空时,不自动生成http_port
http_port: 27008

#worker_num: 2      #最大并发数。当build_dag_each_worker=True时, 框架会创建worker_num个进程,每个进程内构建grpcSever和DAG

#build_dag_each_worker, False,框架在进程内创建一条DAG;True,框架会每个进程内创建多个独立的DAG
build_dag_each_worker: false

#rpc端口, rpc_port和http_port不允许同时为空。当rpc_port为空且http_port不为空时,会自动将rpc_port设置为http_port+1
#rpc_port: 27009

op:
  matting:
    #并发数,is_thread_op=True时,为线程并发;否则为进程并发
    concurrency: 4
    local_service_conf:
      #client类型,包括brpc, grpc和local_predictor.local_predictor不启动Serving服务,进程内预测
      client_type: local_predictor
      # device_type, 0=cpu, 1=gpu, 2=tensorRT, 3=arm cpu, 4=kunlun xpu
      device_type: 1
      #计算硬件ID,当devices为""或不写时为CPU预测;当devices为"0", "0,1,2"时为GPU预测,表示使用的GPU卡
      devices: '0,1,2,3'
      #Fetch结果列表,model中fetch_var的alias_name为准, 如果没有设置则全部返回
      fetch_list:
      - tmp_75
      #模型路径
      model_config: serving_server/

如果要部署自己的模型请根据注释结合自己需要部署的模型参数对照着进行修改。

3.2.3 编写服务端脚本文件

新建web_service.py文件,内容如下:

# 导入依赖库
import numpy as np
import cv2
from paddle_serving_app.reader import *
import base64
from paddle_serving_server.web_service import WebService, Op


class MattingOp(Op):
    '''
    定义抠图算子
    '''
    def init_op(self):
        '''
        初始化
        '''
        self.img_preprocess = Sequential([
            #BGR2RGB(), 
            Div(255.0),
            #Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], False),
            Resize(512), Transpose((2, 0, 1))
        ])
        self.ref_size = 512
        self.img_width = self.ref_size
        self.img_height = self.ref_size   

    def preprocess(self, input_dicts, data_id, log_id):
        '''
        预处理
        '''
        (_, input_dict), = input_dicts.items()
        imgs = []
        for key in input_dict.keys():
            # 解码图像
            data = base64.b64decode(input_dict[key].encode('utf8'))
            data = np.fromstring(data, np.uint8)
            im = cv2.imdecode(data, cv2.IMREAD_COLOR)
            self.im = im
            self.img_height,self.img_width,_ = im.shape
            # 短边对齐512,长边设置为32整数倍(根据算法模型要求)
            im_h, im_w, _ = im.shape  
            if im_w >= im_h:
                im_rh = self.ref_size
                im_rw = int(im_w *1.0 / im_h * self.ref_size)
            elif im_w < im_h:
                im_rw = self.ref_size
                im_rh = int(im_h *1.0 / im_w * self.ref_size)      
            im_rw = im_rw - im_rw % 32
            im_rh = im_rh - im_rh % 32
            im = cv2.resize(im,(im_rw,im_rh))
            
            # cv2转tensor
            im = self.img_preprocess(im)
            imgs.append({
                "img": im[np.newaxis, :],
                # "im_shape":np.array(list(im.shape[1:])).reshape(-1)[np.newaxis, :],
                # "scale_factor": np.array([1.0, 1.0]).reshape(-1)[np.newaxis, :],
            })
        
        # 准备输入数据
        feed_dict = {
            "img": np.concatenate(
                [x["img"] for x in imgs], axis=0),
            # "im_shape": np.concatenate(
            #     [x["im_shape"] for x in imgs], axis=0),
            # "scale_factor": np.concatenate(
            #     [x["scale_factor"] for x in imgs], axis=0)
        }
        #for key in feed_dict.keys():
        #    print(key, feed_dict[key].shape)
        return feed_dict, False, None, ""

    def postprocess(self, input_dicts, fetch_dict, data_id, log_id):
        '''
        后处理
        '''
        # 取出掩码图
        alpha = fetch_dict["tmp_75"]
        alpha = alpha.squeeze(0).squeeze(0)
        alpha = (alpha * 255).astype('uint8')  
        alpha = cv2.resize(
                    alpha, (self.img_width, self.img_height), interpolation=cv2.INTER_NEAREST)
        alpha = alpha[:, :, np.newaxis]
        clip = np.concatenate([self.im, alpha], axis=-1)  
        print(clip.shape)
        _, buffer_img = cv2.imencode('.png', clip)  # 在内存中编码为png格式
        img64 = base64.b64encode(buffer_img)  
        img64 = str(img64, encoding='utf-8')  # bytes转换为str类型 
        
        #封装成字典返回
        res_dict = {
            "alpha":img64
        }
        return res_dict, None, ""


class MattingService(WebService):
    '''
    定义服务
    '''
    def get_pipeline_response(self, read_op):
        matting_op = MattingOp(name="matting", input_ops=[read_op])
        return matting_op
    

# 创建服务
matting_service = MattingService(name="matting")
# 加载配置文件
matting_service.prepare_pipeline_config("config.yml")
# 启动服务
matting_service.run_service()

上述代码做了注释,读者可以自行阅读分析。

最后使用下面的命令启动服务:

python web_service.py

3.2.4 客户端调用

这里需要注意,由于我们采用了Pipeline模式,所有的图像预处理和后处理操作都放在了服务端,因此,客户端不需要加载额外的库,也不需要进行相关图像预处理代码编写。因此,我们可以采用任何客户端方式(浏览器、脚本、移动端等),只需要按照http restful协议传送相关json数据即可。

本文为了简单,采用python脚本来作为客户端(也可以仿照这个脚本使用postman进行测试)。新建脚本文件pipeline_http_client.py,具体代码如下:

# 导入依赖库
import numpy as np
import requests
import json
import cv2
import base64

def cv2_to_base64(image):
    return base64.b64encode(image).decode('utf8')


# 定义http接口
url = "http://127.0.0.1:27008/matting/prediction"

# 打开待预测的图像文件
with open('./data/PPM-100/val/fg/1.jpg', 'rb') as file:
    image_data1 = file.read()

# 采用base64编码图像文件
image = cv2_to_base64(image_data1)

# 按照特定格式封装成字典
data = {"key": ["image"], "value": [image]}

# 发送请求
r = requests.post(url=url, data=json.dumps(data))

# 解析返回值
r = r.json()

# 解码返回的图像数据
img = r["value"][0]
img = bytes(img, encoding='utf-8')  # str转bytes
img = base64.b64decode(img)  # base64解码
img = np.asarray(bytearray(img), dtype="uint8")
img = cv2.imdecode(img, cv2.IMREAD_UNCHANGED)

# 保存图像到本地
if img is None:
    print('call error')
else:
    cv2.imwrite('result.png',img)
    print('完成')

执行预测:

python3 pipeline_http_client.py

四、小结

本教程以PaddleServing部署为目标,以语义分割(抠图)案例贯穿整个部署环节,最终成功实现服务器线上部署和调用。通过本教程的学习,可以快速将训练好的深度学习模型进行上线,同时具备良好的稳定性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PaddleServing图像语义分割部署实践 的相关文章

  • 多智能体系统编队算法仿真--python3实现

    初始条件 xff1a 智能体位置随机生成所有智能体位置全局可知目标多边形位置给定所有个体运行相同算法 xff0c 根据环境来决定自己动作 目标 xff1a 形成均匀多边形分布 xff0c 所谓的 均匀 效果如下图 xff1a 即是多边形上间
  • 大众点评美食评论爬虫

    大家都知道的 xff0c 大众点评用了css反爬 xff0c 脑壳疼 评论文字使用SVG替换 然后还需要登录才能查看全部评论 xff0c 也就是要带cookie了 xff0c 此外时不时跳验证码 xff0c 验证码还有几种 xff0c 这帮
  • Python大佬手把手教你利用wxPython模块编写界面程序

    wxPython是一个开发桌面端图形界面的跨平台函数库 xff0c 开发语言为Python xff0c 它是基于C 43 43 的函数库wxWidgets的封装 很多人学习python xff0c 不知道从何学起 很多人学习python x
  • 什么是栈,栈存储结构详情

    什么是栈 xff0c 栈存储结构详情 同顺序表和链表一样 xff0c 栈也是用来存储逻辑关系为一对一数据的线性存储结构 xff0c 如图所示 从图1我们看到 xff0c 栈存储结构与之前学的线性存储有所差异 xff0c 这源于栈对数存和取的
  • C++cmath数学常用库中的代码介绍

    今天小编来分享一波C 43 43 cmath数学常用库中的常用代码 首先 xff0c 这些所有的代码都来自cmath库 xff0c 所以必须先引用cmath头文件 xff0c 即 xff1a include lt cmath gt 1 绝对
  • vue-cli-service Axios 持续返还401

    后端 xff1a 在登录超时或没有登录的情况 xff0c 所有请求都会拒绝并且返还HTTP状态码401 前端 xff1a 前端将认证的Token放到loadStorage中 xff0c 在认证失效时候清空 在接到HTTP状态码为401时 x
  • 载波相位测量

    1 简述GPS载波相位测量的基本原理 载波相位测量的观测量是GPS接收机所接收的卫星载波信号与接收机本振参考信号的相位差 利用接收机测定载波相位观测值 xff0c 经基线向量解算以获得两个同步观测站之间的基线向量坐标差 2 在高精度GPS测
  • PJLib开发杂谈

    去年公司搞了一个智慧乡村的项目 xff0c 其中涉及到视频监控的问题 电信提供的解决方案 xff0c 我们的平台只需要跟电信对接HLS流就可以了 这就有一个问题 xff0c 原来公司安装的监控无法接入到自己的平台 xff0c 好尴尬啊 于是
  • 开发GB28181监控平台前期准备总结

    首先得准备PJLIB的编译 xff0c 这个搜一下 xff0c 下载以后它是有VS的工程文件的 xff0c 所以编译很方便 得到这个库以后 xff0c 就可以编写SIP服务程序了 xff0c 服务程序可以验证GB28181的相关通讯流程 x
  • esp8266 丢失固件 丢失程序问题

    1 首先esp8266 丢失固件 丢失程序问题已经解决 2 解决方法 我们制作了一个固件保护主板 xff0c 提供2种供电接口 xff0c 支持5v稳压 串口电平保护 xff0c 固件保护 xff0c 反电动势保护 xff0c 支持复位按键
  • printf重定向

    1 printf与fputc 对于 printf 函数相信大家都不陌生 xff0c 第一个C语言程序就是使用 printf 函数在屏幕上的控制台打印出Hello World xff0c 之后使用 printf 函数输出各种类型的数据 xff
  • ESP32_BLUFI代码移植过程遇到的问题

    1 先是运行esp32官方给的例程 xff0c 出现了错误报错如下 xff1a esp image Image length 1053648 doesn t fit in partition length 1048576 boot Fact
  • Java 中的 Iterator 迭代器详解

    x1f366 Iterator 接口 在程序开发中 xff0c 经常需要遍历集合中的所有元素 针对这种需求 xff0c JDK 专门提供了一个接口 java util Iterator Iterator 接口也是 Java 集合中的一员 x
  • 三.【NodeJs入门学习】POST接口

    上一节我们学习了get接口 xff0c 这一节我们自己来写一下post接口 1 复习一下 先复习一下上一节中get请求的步骤 上图是在入口app js中处理get请求 xff0c 先拿到请求的url xff0c 然后设置了一个函数handl
  • 多进程和多线程比较

    原文 xff1a http blog csdn net lishenglong666 article details 8557215 很详细 对比维度 多进程 多线程 总结 数据共享 同步 数据共享复杂 xff0c 需要用IPC xff1b
  • C++ 之头文件声明定义

    最近在学习 c 43 43 在编译与链接过程中遇到了一些定义与声明的问题 经过多处查阅资料 基本解惑 现记录与此 希望让后面人少走些弯路 C 43 43 的头文件应该用什么扩展名 目前业界的常用格式如下 implementation fil
  • arduino修改串口缓冲区大小的三种办法

    由于SoftwareSerial h默认只接收64字节串行缓冲区 xff0c Arduino会将之后接收到的数据丢弃 xff0c 不满足业务需求 以下三种方法是笔者参考网上各种资料总结出来 xff0c 对于WEMOS D1 R2 xff0c
  • C语言调用libcurl的一个简单例子

    首先我们创建一个php页面 xff1a lt meta http equiv 61 span class hljs string 34 Content Type 34 span content 61 span class hljs stri
  • 【C++】类构造函数、析构函数的调用顺序「完整版」

    一 全局变量 静态变量和局部变量 全局变量在程序开始时调用构造函数 在程序结束时调用析构函数 静态变量在所在函数第一次被调用时调用构造函数 在程序结束时调用析构函数 xff0c 只调用一次 局部变量在所在的代码段被执行时调用构造函数 xff
  • linux下使用shell发送http请求

    本文主要介绍如何在linux下使用shell发送http请求 一 curl 1 get请求 curl命令默认下就是使用get方式发送http请求 curl www span class hljs preprocessor baidu spa

随机推荐

  • 【STL真好用】1057 Stack C++(30)

    1057 Stack 30 分 Stack is one of the most fundamental data structures which is based on the principle of Last In First Ou
  • C++学习之头文件引用

    目录结构如下 test h的定义如下 xff1a ifndef TEST H define TEST H include lt vector gt include lt string gt using namespace std class
  • checksum 算法

    说明 checksum xff1a 总和检验码 xff0c 校验和 xff0c 可以理解为check xff08 校验 xff09 xff0c sum xff08 和 xff09 在数据处理和通信领域 xff0c 通过一定算法对传输的数据进
  • 解决cannot open shared object file: No such file or directory

    一 linux下调用动态库 so文件时提示 xff1a cannot open shared object file No such file or directory 解决办法 xff1a 1 此时ldd xxx查看依赖缺少哪些库 lib
  • cmake 使用(六)

    本文是 cmake 使用的第六篇 主要介绍如何设置编译器优化标志 上一篇的链接为 xff1a https blog csdn net QCZL CC article details 119825737 xff0c 主要介绍如何将自己的软件安
  • 8086寄存器介绍

    8086 有14个16位寄存器 xff0c 这14个寄存器按其用途可分为 1 通用寄存器 2 指令指针 3 标志寄存器和 4 段寄存器等4类 1 通用寄存器有8个 又可以分成2组 一组是数据寄存器 4个 另一组是指针寄存器及变址寄存器 4个
  • C++常用操作符:: -> . (例子详解)

    C 43 43 提供了三种访问类或者类对象的操作符 xff0c 他们是 双冒号 点 箭头 gt 这三种操作符有着各自的使用场景和定义 双冒号 A B 表示作用域运算符 A一定是一个类的名称或命名空间的名称 仅仅用于当B是A类 A命名空间的一
  • STM32中断优先级的分配以及中断原则

    STM32d的中断优先级由NVIC IPRx寄存器来配置 xff0c IPR的宽度为8bit所以原则上每个中断可配置的优先级为0 255 xff0c 数值越小优先级越高 xff0c 但对于大部分的 Cortex M3芯片都会精简设计 xff
  • 晶体管的结构、类型和三种组态

    晶体管有两大类型 双极型晶体管 BJT 和场效应管 FET 双极型晶体管又称为半导体三极管 晶体三极管 xff0c 简称晶体管 它由两个PN结组合而成 xff0c 有两种载流子参与导电是一种电流控制电流源器件 场效应管仅有一种载流子参与导电
  • STM32单片机基础09——重定向printf函数到串口输出的多种方法

    本文详细的介绍了如何重定向printf输出到串口输出的多种方法 xff0c 包括调用MDK微库 xff08 MicroLib xff09 的方法 xff0c 调用标准库的方法 xff0c 以及适用于 GNUC 系列编译器的方法 1 prin
  • STM32直流减速电机控制篇(一)PWM调速

    直流电机原理 下面是分析直流电机的物理模型图 其中 xff0c 固定部分有磁铁 xff0c 这里称作主磁极 xff1b 固定部分还有电刷 转动部分有环形铁芯和绕在环形铁芯上的绕组 直流电机的转动原理我就不再赘述 xff0c 比较简单易懂 直
  • STM32直流减速电机控制篇(二)编码器测速原理

    编码器 编码器是一种将角位移或者角速度转换成一连串电数字脉冲的旋转式传感器 xff0c 我们可以通过编码器测量到底位移或者速度信息 编码器从输出数据类型上分可以分为增量式编码器和绝对式编码器 从编码器检测原理上来分 xff0c 还可以分为光
  • STM32直流减速电机控制篇(三)编码器测速程序编写

    编程思路 任何一个程序的编写我们都应该先理清楚编程思路 xff0c 通过上一篇讲解的编码器测速原理我们应该知道要想通过编码器得知电机转速我们第一步就应该是捕获A相和B相输出的脉冲 因为电机速度的定义是单位时间内的转数 xff0c 所以第二步
  • GPIO模式

    开漏输出 只能输出低电平 xff0c 不能输出高电
  • 单片机485通信

    1 RS485简介 485 xff08 一般称作 RS485 EIA 485 xff09 是隶属于 OSI 模型物理层的电气特性规定为 2 线 xff0c 半双工 xff0c 多点信的标准 它的电气特性和 RS 232 大不一样 用缆线两端
  • Jetson Xavier NX 镜像制作、烧录及克隆

    以下所有方法仅适用于Jetson Xavier Nx 16G emmc版本 其他版本仅供参考 官方文档下载链接为https developer nvidia com embedded downloads search 61 Develope
  • Postman下载,安装,注册及登录教程

    目录 一 Postman简介 二 Postman的注册 1 首先下载Postman xff0c 进入官网 xff1a Download Postman Get Started for Free 2 安装Postman 3 找到所下载的app
  • 一文掌握fastapi微服务开发

    目录 一 概述 1 1 微服务 1 1 1 微服务的优势 1 1 2 微服务的缺点 1 2 为何使用Python开发微服务 1 3 FastAPI概述 二 开发 2 1 安装FastAPI 2 1 1 安装虚拟环境 2 1 2 创建虚拟环境
  • Windows通过SSH连接虚拟机中的ubuntu系统

    zz windows通过ssh连接虚拟机中的ubuntu步骤 音量 博客园
  • PaddleServing图像语义分割部署实践

    目录 一 任务概述 二 官方示例部署 2 1 安装PaddleServing 2 2 导出静态图模型 2 3 转换为serving模型 2 4 启动服务 2 5 客户端请求 三 基于PipeLine的抠图功能部署 3 1 基于深度学习的抠图