【强化学习系列】Gym库使用——创建自己的强化学习环境2:拆解官方标准模型源码/规范自定义类+打包自定义环境

news2024/9/21 14:37:34

       

目录

一、 官方标准环境的获取与理解

二、根据官方环境源码修改自定义

        1.初始化__init__()

        2.重置环境 reset()

三、打包环境

        1.注册与创建自定义环境

        2.环境规范化


         在本文的早些时候,曾尝试按照自己的想法搭建自定义的基于gym强化学习环境。

        【强化学习系列】Gym库使用——创建自己的强化学习环境1:单一环境创建测试

        但由于学艺不精,在矢量化环境时遇到诸多障碍。仔细检查发现自己编写的环境存在很多不符合gym官方规范的地方,因此,索性从零重新研读官方介绍文档,从头开始解读官方标准环境源码,以帮助规范化自己搭建环境的类参数设置。

        官方文档地址:https://gymnasium.farama.org/

一、 官方标准环境的获取与理解

        通过打印 gym.envs.registry.keys() 获得的字典,就可以获得官方默认的所有可以直接调用的环境。

import gymnasium as gym

# 官方支持的所有游戏环境
print(gym.envs.registry.keys())

        通过官方给出的运行和可视化代码即可,先通过“人为”(‘human’)来玩游戏。

import gymnasium as gym

# 官方支持的所有游戏环境
print(gym.envs.registry.keys())

# 可视化
env = gym.make("MountainCarContinuous-v0", render_mode="human")
observation, info = env.reset()

for _ in range(1000):
    action = env.action_space.sample()  # agent policy that uses the observation and info
    observation, reward, terminated, truncated, info = env.step(action)
    if terminated or truncated:
        observation, info = env.reset()
env.close()

        下面展示一些官方游戏的可视化窗口的部分画面。只需根据最开始获得的游戏包装好的字典名,修改上述代码中环境实例化的make中第一个参数名即可,实际运行是动态的。

 

        首先从官方给出的Github地址下载好Gymnaium的源码——Gymnaium官方Github地址

        在官方文档目录的 ENVIRONMENTS 中介绍相关环境的一些基本关键信息,包括其状态动作空间、交互逻辑、奖励逻辑等。

        其中所有环境的源码保存在Github文件的 gymnaium/envs/ 目录下。

        本文选择经典环境下的山地车(Mountain Car)为源码拆解的例子。首先看起官方文档的环境介绍,其动作空间是一个三种可能的离散空间,分别对应小车的向左向右加速或不加速。

        其状态空间是一个连续的二维空间,分别是小车在x轴的位置,取值范围是(-1.2,0.6);小车速度,取值范围是(-0.07,0.07)。

        目标是让小车登顶,初始化是将小车随机放在山谷的一个位置,初始速度为0,如果小车登顶或超过200步操作,则退出游戏。

二、根据官方环境源码修改自定义

        在理解游戏逻辑的基础上,进入其源码处查看并学习其代码类编写的规范性。

        1.初始化__init__()

        首先是环境的初始化。其在传入参数上设计两个内容:一是选择环境可视化,二是设置汽车的初始速度为0。

        还可以看到其可视化模式的选择是定义了一个metadata的字典,其定义状态空间和动作空间的逻辑和我们之前无异。

        值得注意的规范细节是,对于初始化是numpy数组向量时,都需标准其定义的数据类型dtype

        总结官方环境源码初始化的内容,可以得出初始化的个关键内容——状态环境的取值范围(如所处空间的大小——游戏里的地图,到边缘就不能探索了)、实体状态属性值(如小车的速度、或者游戏里角色的血量蓝条等等)、可视化窗口的一些变量。       

        现在回到自定义环境的逻辑(参考文章——单一自定义环境测试),新的环境中状态空间是图片上的目标检测框,是一个四维的(左上右下四坐标列表)连续空间。动作空间是一个四种可能的离散空间——上扩或缩、下扩或缩。其框坐标的取值范围受到图片高宽的影响。因此初始化必须传入原图地址信息,以获取高宽范围。

        参考总结的内容,可以梳理清晰自定义环境的一些设置,状态空间上肯定超出图片高宽或者小于0的框无意义,因此很容易定义状态空间。更关键的是实体属性,在这里原始的框就是需要移动的实体,像小车会有速度一样,这里我们定义初始框包含几个实际物体对应框(虚拟的框,在状态空间环境中不存在)视为该框的属性值。同时一张图里也可能有多个框存在,如果不希望当前框和环境中其他存在的框存在高度重叠,也可以设置重叠度检查属性。

        2.重置环境 reset()

        查看官方的初始化环境函数,可以看到其对于函数传参有规范化的标准,其初始的状态控制放在options字典数据中传入,并且采取了强制关键字的模式(*后的参数需要以关键字对应方式传入)因此改进我们自定义的reset函数。

         再将真实框可视化进背景,即可运行reset得到可视化结果。

         以上可视化所有完整代码如下。

import pygame
import gym
from gym import spaces
import numpy as np
from typing import Optional

class My_Env(gym.Env):
    metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 1}
    def __init__(self, render_mode: Optional[str] = None, image_path: Optional[str] = None):
        super(My_Env, self).__init__()
        # 读取当前图像信息
        if image_path != None:
            self.img = pygame.image.load(image_path)
            self.width, self.height = self.img.get_width(), self.img.get_height()
        else:
            self.width, self.height = 100, 100
            print('env need background')

        self.real = None  # 真实框数据
        # 定义框属性值
        self.box_num = None  # 初始预测框数量
        self.overlap = None  # 包含真实框个数
        self.box_iou = None  # 重叠度检查

        # 创建动作和状态空间范围
        self.action_space = spaces.Discrete(4)
        self.observation_space = spaces.Box(low=np.array([0,0,0,0],dtype=np.float32), high=np.array([self.width, self.height,
                                            self.width, self.height],dtype=np.float32), shape=(4,), dtype=np.float32)
        # 定义模式:人类可视化or机器人训练
        assert render_mode is None or render_mode in self.metadata["render_modes"]
        self.render_mode = render_mode
        self.window = None  # 可视化窗口
        self.clock = None   # 可视化时钟
        self.window_size = (600,600)  # 窗口大小
        self.background = None  # 背景图
        self.scale_x = None     # x横轴缩放比
        self.scale_y = None     # y竖轴缩放比

    def reset(
            self,
            *,
            seed: Optional[int] = None,
            options: Optional[dict] = None) :
        real = options['real_box']   # 真实框数据
        box = options['box']   # 初始框数据
        self.real = np.array(real, dtype=np.float32).reshape(-1, 4)
        self.state = np.array(box,dtype=np.float32).reshape(-1,4)   # 规范数据格式和形状

        if self.render_mode == 'human':
            self.background = pygame.transform.scale(self.img, self.window_size) # 设置背景图
            # 计算x和y方向的缩放比例
            self.scale_x = self.window_size[0]/ self.width
            self.scale_y = self.window_size[1] / self.height
            self.render()
        return self.state

    def step(self, action):  # 动作序列{0:上扩, 1:上缩, 2:下扩, 3:下缩}
        # 根据action生成移动数组
        movement_np = np.zeros_like(self.state,dtype=np.float32)
        for i,act in enumerate(action):
            if act == 0:
                movement_np[i,1] = 10
            elif act == 1:
                movement_np[i,1] = -10
            elif act == 2:
                movement_np[i,3] = 10
            elif act == 3:
                movement_np[i,3] = -10
        # 移动当前状态框
        self.state += movement_np
        self.state = np.clip(self.state, self.observation_space.low, self.observation_space.high)

        return self.state

    def render(self):
        # 初始化窗口和时钟
        if self.window is None and self.render_mode == 'human':
            pygame.init()
            pygame.display.init()
            self.window = pygame.display.set_mode(self.window_size)
        if self.clock is None and self.render_mode == 'human':
            self.clock = pygame.time.Clock()
        # 重新绘制背景,以清除上一层
        if self.background is not None:
            self.window.blit(self.background,(0,0))
            for real in list(self.real):
                rect_real = [real[0], real[1], abs(real[2]-real[0]), abs(real[3]-real[1])]
                # 根据x和y方向的缩放比例计算每个矩形框的新位置
                stretched_rectangle = [
                    rect_real[0] * self.scale_x,  # x 坐标
                    rect_real[1] * self.scale_y,  # y 坐标
                    rect_real[2] * self.scale_x,  # 宽度
                    rect_real[3] * self.scale_y  # 高度
                ]
                pygame.draw.rect(self.window, (255, 0, 0), stretched_rectangle, 2)  # 绘制矩形框,线宽为2
        # 绘制框
        for box in list(self.state):
            rect = [box[0], box[1], abs(box[2]-box[0]), abs(box[3]-box[1])]
            # 根据x和y方向的缩放比例计算每个矩形框的新位置
            stretched_rectangle = [
                rect[0] * self.scale_x,  # x 坐标
                rect[1] * self.scale_y,  # y 坐标
                rect[2] * self.scale_x,  # 宽度
                rect[3] * self.scale_y  # 高度
            ]
            pygame.draw.rect(self.window, (0, 255, 0), stretched_rectangle, 3)  # 绘制矩形框,线宽为3
        # 更新显示
        pygame.display.flip()
        self.clock.tick(self.metadata["render_fps"])

    def close(self):
        pygame.quit()


if __name__=='__main__':
    # 加载本地图片
    image_path = './jpg/000000000025.jpg'  # 替换为你的图片路径
    # 加载本地框
    box_path = './box/000000000025.json'
    with open(box_path, 'r') as f:
        box_dict_list = json.load(f)
    box_list = []
    for box_dict in box_dict_list:
        box = box_dict['box']
        box_list.append(box)

    box2 = [(0,3),(1,0),(1,1),(1,2),(1,3)]
    box1 = [(0,0),(0,1),(1,0),(1,1),(2,1),(3,1),(4,1),(4,2),(4,3),(5,1),(5,2),(5,3),(5,4),(6,1),(6,2),(6,3),(6,4),(7,1),(7,2),(7,3),(7,4),(7,5),(8,1),(8,2),(8,3),(8,4),(8,5)]
    real_list = []
    for i in range(len(box_list)):
        bbox = box_list[i]
        wid = bbox[2]-bbox[0]
        hgt = bbox[3]-bbox[1]
        gap = 30
        for w in range(wid//gap):
            for h in range(hgt//gap):
                if i ==0:
                    if (h,w) in box1:
                        real = [bbox[0]+w*gap, bbox[1]+h*gap, bbox[0]+(w+1)*gap, bbox[1]+(h+1)*gap]
                        real_list.append(real)
                elif i ==1:
                    if (h,w) in box2:
                        real = [bbox[0] + w * gap, bbox[1] + h * gap, bbox[0] + (w + 1) * gap, bbox[1] + (h + 1) * gap]
                        real_list.append(real)

    env = My_Env(render_mode='human', image_path=image_path)

    state = env.reset(options={'real_box':real_list, 'box':box_list})
    while True:
        env.render()

三、打包环境

        1.注册与创建自定义环境

        为了更方便的加载环境以及为后续矢量化做准备,需要将自定义的环境打包并使用官方推荐的gymnasium.make来加载环境,这样的加载方式会帮助检查环境的合规性。

        首先新建项目文件,将所有环境代码放入一个子文件中方便修改,然后再新建一个注册环境的子文件用于测试环境能否被成功注册。

        在测试环境下(有图片和框数据的文件夹),先注册环境在创建环境。

import gymnasium as gym
import json
from gymnasium.envs.registration import register

# 注册环境
register(
    id='detect_env-v0',
    entry_point='my_gym_env.detect_env:Detect_Env',
)

# 加载本地图片
image_path = './jpg/000000000025.jpg'  # 替换为你的图片路径
# 加载本地框
box_path = './box/000000000025.json'
real_path = './real/000000000025.json'
with open(box_path, 'r') as f:
    box_dict_list = json.load(f)
box_list = []
for box_dict in box_dict_list:
    box = box_dict['box']
    box_list.append(box)
with open(real_path, 'r') as f:
    real_list = json.load(f)


env = gym.make('detect_env-v0', render_mode='human', image_path=image_path)

state = env.reset(options={'real_box':real_list, 'box':box_list})
while True:
    env.render()

        2.环境规范化

        运行上面的测试代码,虽然能加载出环境演示,但是make会检测环境创建源码,并对不规范报错,这方便我们进行修改规范源码。

        ① reset 报错与规范

         这里报错的原因是,在定义环境重置 reset 时,没有返回 info 信息。补一个空字典即可。

UserWarning: WARN: The result returned by `env.reset()` was not a tuple of the form `(obs, info)`, where `obs` is a observation and `info` is a dictionary containing additional information. Actual type: `<class 'numpy.ndarray'>`

        ② observation_space 状态空间报错与规范

        改完后报新的错。这是说在重置环境时传入的状态空间与__init__ 中定义的状态空间形状不匹配。

UserWarning: WARN: The obs returned by the `reset()` method is not within the observation space.
  logger.warn(f"{pre} is not within the observation space.")

        检查环境中定义的空间必须是固定形状的,(,4)的定义会默认为(1,4),其不能自由匹配维度,但是作为图片中的目标框,其第一个维度数量是不固定的。因此一种解决方式是选择所有图片内的最大框数作为初始化状态空间第一维度,不够的地方用零填充。

# 设置最大框数
self.box_max_num = 20
# 创建动作和状态空间范围
self.action_space = spaces.Discrete(4)
self.observation_space = spaces.Box(low=np.zeros((self.box_max_num,4),dtype=np.float32),
                                            high=np.tile([self.width, self.height,self.width, self.height],(self.box_max_num,1)),
                                            shape=(self.box_max_num,4), dtype=np.float32)

# 初始传入框信息
state = np.array(box,dtype=np.float32).reshape(-1,4)   # 规范数据格式和形状

# 填充零使得状态达到指定维度
self.state = np.pad(state,((0,self.box_max_num-state.shape[0]),(0,0)), mode='constant', constant_values=0)

        规范后不再报错,此时打印环境的状态检查。

        ③ step 报错与规范

        继续在加入动作空间的输入,解决step中的规范性问题。首先如果要输入动作,需要知道当前图片中实际需要操作的框的数量(不能根据状态空间判断,因为状态中框数(第一行维度)被强制设为最大框数), 因此在重置环境时就要返回当前图片的框数,作为每个框生成多少个动作的标准,在官方的规范代码中,可将这些额外信息写入info字典中返回。

info = {'box_num':state.shape[0]}
return self.state, info

        在测试代码中添加采样动作和step操作,并打印状态的变化numpy数组。注意此处环境还没有设计奖励函数,因此定义环境操作reward都是0。

env = gym.make('detect_env-v0', render_mode='human', image_path=image_path)

state,info = env.reset(options={'real_box':real_list, 'box':box_list})
print(0, state)
for epid in range(50):
    action = [env.action_space.sample() for _ in range(info['box_num'])]
    state, reward, _, _, _ = env.step(action)
    print(epid+1, state)
    env.render()

         下图中,红色箭头表示右较大框的状态变化,黄色箭头代表左较小框的状态变化。此处为方便可视化,将最大框数量设为3。以20像素为步长变化检测框。

         加上具体动作打印结果可视化。

        至此,环境规范化告一段落,完整代码放在下面方便复制取走。终于可以开始后续矢量化环境操作了。

        自定义检测环境完整代码。

import pygame
import gymnasium as gym
from gymnasium import spaces
import numpy as np
from typing import Optional

class Detect_Env(gym.Env):
    metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 1}
    def __init__(self, render_mode: Optional[str] = None, image_path: Optional[str] = None):
        super(Detect_Env, self).__init__()
        # 读取当前图像信息
        if image_path != None:
            self.img = pygame.image.load(image_path)
            self.width, self.height = self.img.get_width(), self.img.get_height()
        else:
            self.width, self.height = 100, 100
            print('env need background')

        self.real = None  # 真实框数据
        # 定义框属性值
        self.box_num = None  # 初始预测框数量
        self.overlap = None  # 包含真实框个数
        self.box_iou = None  # 重叠度检查

        # 设置最大框数
        self.box_max_num = 3
        # 创建动作和状态空间范围
        self.action_space = spaces.Discrete(4)
        self.observation_space = spaces.Box(low=np.zeros((self.box_max_num,4),dtype=np.float32),
                                            high=np.tile([self.width, self.height,self.width, self.height],(self.box_max_num,1)),
                                            shape=(self.box_max_num,4), dtype=np.float32)
        # 定义模式:人类可视化or机器人训练
        assert render_mode is None or render_mode in self.metadata["render_modes"]
        self.render_mode = render_mode
        self.window = None  # 可视化窗口
        self.clock = None   # 可视化时钟
        self.window_size = (600,600)  # 窗口大小
        self.background = None  # 背景图
        self.scale_x = None     # x横轴缩放比
        self.scale_y = None     # y竖轴缩放比

    def reset(
            self,
            *,
            seed: Optional[int] = None,
            options: Optional[dict] = None) :
        real = options['real_box']   # 真实框数据
        box = options['box']   # 初始框数据
        self.real = np.array(real, dtype=np.float32).reshape(-1, 4)
        state = np.array(box,dtype=np.float32).reshape(-1,4)   # 规范数据格式和形状

        # 填充零使得状态达到指定维度
        self.state = np.pad(state,((0,self.box_max_num-state.shape[0]),(0,0)), mode='constant', constant_values=0)

        if self.render_mode == 'human':
            self.background = pygame.transform.scale(self.img, self.window_size) # 设置背景图
            # 计算x和y方向的缩放比例
            self.scale_x = self.window_size[0]/ self.width
            self.scale_y = self.window_size[1] / self.height
            self.render()

        info = {'box_num':state.shape[0]}
        return self.state, info

    def step(self, action):  # 动作序列{0:上扩, 1:上缩, 2:下扩, 3:下缩}
        # 根据action生成移动数组
        movement_np = np.zeros_like(self.state,dtype=np.float32)
        for i,act in enumerate(action):
            if act == 0:
                movement_np[i,1] = 20
            elif act == 1:
                movement_np[i,1] = -20
            elif act == 2:
                movement_np[i,3] = 20
            elif act == 3:
                movement_np[i,3] = -20
        # 移动当前状态框
        self.state += movement_np
        self.state = np.clip(self.state, self.observation_space.low, self.observation_space.high)

        return np.array(self.state, dtype=np.float32),0,False,False,{}

    def render(self):
        # 初始化窗口和时钟
        if self.window is None and self.render_mode == 'human':
            pygame.init()
            pygame.display.init()
            self.window = pygame.display.set_mode(self.window_size)
        if self.clock is None and self.render_mode == 'human':
            self.clock = pygame.time.Clock()
        # 重新绘制背景,以清除上一层
        if self.background is not None:
            self.window.blit(self.background,(0,0))
            for real in list(self.real):
                rect_real = [real[0], real[1], abs(real[2]-real[0]), abs(real[3]-real[1])]
                # 根据x和y方向的缩放比例计算每个矩形框的新位置
                stretched_rectangle = [
                    rect_real[0] * self.scale_x,  # x 坐标
                    rect_real[1] * self.scale_y,  # y 坐标
                    rect_real[2] * self.scale_x,  # 宽度
                    rect_real[3] * self.scale_y  # 高度
                ]
                pygame.draw.rect(self.window, (255, 0, 0), stretched_rectangle, 2)  # 绘制矩形框,线宽为2
        # 绘制框
        for box in list(self.state):
            rect = [box[0], box[1], abs(box[2]-box[0]), abs(box[3]-box[1])]
            # 根据x和y方向的缩放比例计算每个矩形框的新位置
            stretched_rectangle = [
                rect[0] * self.scale_x,  # x 坐标
                rect[1] * self.scale_y,  # y 坐标
                rect[2] * self.scale_x,  # 宽度
                rect[3] * self.scale_y  # 高度
            ]
            pygame.draw.rect(self.window, (0, 255, 0), stretched_rectangle, 3)  # 绘制矩形框,线宽为3
        # 更新显示
        pygame.display.flip()
        self.clock.tick(self.metadata["render_fps"])

    def close(self):
        pygame.quit()

        项目文件层级结构。

        测试可视化完整代码。

import gymnasium as gym
import json
from gymnasium.envs.registration import register

# 注册环境
register(
    id='detect_env-v0',
    entry_point='my_gym_env.detect_env:Detect_Env',
)

# 加载本地图片
image_path = './jpg/000000000025.jpg'  # 替换为你的图片路径
# 加载本地框
box_path = './box/000000000025.json'
real_path = './real/000000000025.json'
with open(box_path, 'r') as f:
    box_dict_list = json.load(f)
box_list = []
for box_dict in box_dict_list:
    box = box_dict['box']
    box_list.append(box)
with open(real_path, 'r') as f:
    real_list = json.load(f)

env = gym.make('detect_env-v0', render_mode='human', image_path=image_path)

state,info = env.reset(options={'real_box':real_list, 'box':box_list})
print(0, state)
for epid in range(50):
    action = [env.action_space.sample() for _ in range(info['box_num'])]
    print('action', action)
    state, reward, _, _, _ = env.step(action)
    print(epid+1, state)
    env.render()

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

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

相关文章

什么是自然语言处理

自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是计算机科学领域与人工智能领域中的一个重要方向。它研究的是如何实现人与计算机之间使用自然语言进行有效通信的各种理论和方法。以下是关于自然语言处理的详细解释&#xff1a; 一、定义与概述 定义&…

字符编码发展史2 — ISO-8859-N

2.2. 第二个阶段 本地化 2.2.1. ANSI2.2.2. ISO/IEC 8859-N 2.2.2.1. 什么是ISO/IEC 8859-N?2.2.2.2. ISO 8859-1的编码表 上一篇《字符编码发展史1 — ASCII和EASCII》我们讲解了字符编码的起源ASCII和EASCII。本篇我们将继续讲解字符编码的第二个发展阶段中的ISO 8859-N。…

西门子200SMART全面讲解

200 SMART 全面讲解工控人加入PLC工业自动化精英社群 工控人加入PLC工业自动化精英社群

如何打造出强悍的谷歌搜索关键词优化方案揭密

搭建一个成功的关键词优化规划是促进网站在谷歌搜索引擎中取得更强曝光和流量重要。本文将为你揭露七个秘笈&#xff0c;帮助自己打造出强悍的谷歌搜索关键词优化方案。1.目标制定在进行优化关键词以前&#xff0c;必须明确自己的目标。你希望用谷歌搜索引擎获得更多浏览量和访…

C++速通LeetCode简单第5题-回文链表

解法1&#xff0c;堆栈O(n)简单法&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListN…

一款屏幕录制和视频剪辑软件Camtasia 2024

Camtasia 2024是一款屏幕录制和视频剪辑软件&#xff0c;软件提供了强大的屏幕录像、视频的剪辑和编辑、视频菜单制作、视频剧场、视频播放功能、视屏白板等功能&#xff0c;支持在任何颜色模式下轻松地记录屏幕动作&#xff0c;有需要的朋友快来下载吧&#xff01; Camtasia 2…

1.pytest基础知识(默认的测试用例的规则以及基础应用)

一、pytest单元测试框架 1&#xff09;什么是单元测试框架 单元测试是指再软件开发当中&#xff0c;针对软件的最小单位&#xff08;函数&#xff0c;方法&#xff09;进行正确性的检查测试。 2&#xff09;单元测试框架 java&#xff1a;junit和testing python&#xff1a;un…

电脑怎么禁用软件?5个方法速成,小白必入!

电脑禁用软件的方法多种多样&#xff0c;以下是五种简单易行的方法. 适合不同需求的用户&#xff0c;特别是电脑小白。 1. 使用任务管理器禁用启动项 操作步骤&#xff1a;按下“Ctrl Shift Esc”组合键&#xff0c;打开任务管理器。 切换到“启动”选项卡&#xff0c;找到…

计算机毕业设计 家电销售展示平台的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【活动预告】平陆县2024年“我有拿手戏”群众文艺大展演

庆祝中华人民共和国成立75周年 7天10场演出3场展览 文化给您精彩 “礼赞新中国逐梦新时代” 【活动预告】 平陆县“庆祝中华人民共和国成立75周年”文艺晚会暨“我有拿手戏”群众文艺大展演 7天10场演出3场展览给您精彩 01 平陆县2024年“庆祝中华人民共和国成立75周年”…

小阿轩yx-案例:Prometheus监控kubernetes环境构建

小阿轩yx-案例&#xff1a;Prometheus监控kubernetes环境构建 前言 传统架构中比较流行的监控工具有 Zabbix、Nagios 等&#xff0c;这些监控工具对于 Kubernetes 这类云平台的监控不是很友好&#xff0c;特别是当 Kubernetes 集群中有了成千上万的容器后更是如此 基于 kube…

662. 二叉树最大宽度 BFS 力扣

662. 二叉树最大宽度 已解答 中等 相关标签 相关企业 给你一棵二叉树的根节点 root &#xff0c;返回树的 最大宽度 。 树的 最大宽度 是所有层中最大的 宽度 。 每一层的 宽度 被定义为该层最左和最右的非空节点&#xff08;即&#xff0c;两个端点&#xff09;之间的长…

【工具变量】数字乡村试点区县DID(2000-2023)

数据简介&#xff1a;数字乡村试点政策是中国政府为了推动乡村数字化转型、促进乡村振兴而实施的一项重要战略。根据2020年7月18日中国政府网发布的《关于开展国家数字乡村试点工作的通知》&#xff0c;试点工作的目标是到2021年底&#xff0c;试点地区数字乡村建设取得明显成效…

自动泊车系统中的YOLOv8 pose关键点车位线检测

自动泊车系统中的YOLOv8关键点车位线检测技术解析 引言 随着智能驾驶技术的快速发展&#xff0c;自动泊车功能成为了现代汽车的重要组成部分。它不仅能够提高驾驶的安全性&#xff0c;还能在一定程度上解决城市停车难的问题。在自动泊车系统中&#xff0c;准确识别停车位的位置…

微信 SDK、NCF 、CO2NET、Senparc.AI 近期重大更新,欢迎解锁

◾️更新基础库&#xff0c;包括缓存、APM、CO2NET 核心模块等全面升级&#xff1b; ◾️更新支付接口调用过程中的 SM&#xff08;国密&#xff09;和 RSA 判断方式&#xff1b; ◾️更新微信 SDK 接口 Senparc.Weixin SDK ✍️v2024.9.10 更新基础库&#xff0c;包括缓…

解决nginx代理SSE接口的响应没有流式返回

目录 现象原来的nginx配置解决 现象 前后端分离的项目&#xff0c;前端访问被nginx反向代理的后端SSE接口&#xff0c;预期是流式返回&#xff0c;但经常是很久不响应&#xff0c;一响应全部结果一下子都返回了。查看后端项目的日志&#xff0c;响应其实是流式产生的。推测是n…

Python 课程16-OpenCV

前言 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个广泛使用的开源计算机视觉库&#xff0c;旨在为实时图像处理提供高效的计算工具。它提供了数百种算法和函数&#xff0c;用于处理图像和视频。OpenCV 在工业、学术研究和个人项目中应用广泛&…

鸿蒙开发之ArkUI 界面篇 十二 背景属性

backgroundColor背景色(纯颜色&#xff0c;没法实现立体感之类高级效果)、 backgroundImage背景图(一般是设计师设计好的图)、 backgroundImageSize背景图尺寸(用于调整背景图的尺寸)、 backgroundImagePosition背景图位置(用于调整背景图的位置)。 背景图的添加是属性backgrou…

在家找不到手机?除了语音助手,还可以用远程控制!

总说手机有定位功能&#xff0c;但手机定位一般只能用于室外较大范围&#xff0c;例如在某个街角交叉位置、某个公园位置&#xff0c;某幢楼的某层位置。如果是在室内&#xff0c;例如自己家&#xff0c;手机定位就显得没那么好用了。 在家里怎么找突然“失踪”的手机&#xff…

Android RecycleView 深度解析与面试题梳理

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 引言 在 Android 开发中&#xff0c;列表和网格布局是非常常见的界面元素&#xff0c;它们用于展示大量数据集合。RecyclerView 是 Android 提…