D*算法图文详解

news2024/11/23 15:05:42

前面学习了Dijkstra以及A* 算法的基本原理,对于这两种算法而言,我们都能在有解的情况下找到一条沿着起点到达终点的路径。然而,这两个算法本身都是基于静态地图的,也就是说:当机器人找到路径后开始沿着起点向终点运动的过程中,默认地图是不会发生变化的,但是实际上大部分时候我们知道地图是动态的,随时存在一个突然出现的障碍物挡在原有的路径上,那么这时候原有的路径就失效了。一种简单粗暴的方式当然是从当前点开始重新执行一次Dijkstra或者A* 搜索,但是这样效率会很低,例如障碍物只挡住了当前位置一点点,而因此执行一次全局搜索显然是不太合适的,那么有什么方式能够快速且有效的找到一条合适的路径吗?答案就是:D*

基本逻辑

D* 算法的出现,本身就是为了解决动态路径规划的。它的基本思路如下:

1、初始时,使用Dijkstra算法寻找到一条从起点到达终点的路径。注意一点的是在传统的Dijkstra算法中,只维护了一个代价值f[s],但是D* 中对于每个点维护了两个不同的代价值k[s]与h[s]。此外,注意D*的算法是从终点往起点搜索的,这点比较重要要注意一下,因为涉及到后面沿着parent的搜索。

2、机器人沿着找到的路径前进,假设在某个位置突然出现了一个障碍物。

3、算法将该点处的h值更新为无穷大(不可达),同时调用process_state处理状态,直到算法找到新的路径或者找完了发现没有路径跳出

我们一条条往下看,首先看第一条,初始搜索。这个基本原理与Dijkstra基本上是一致的,区别在于D* 为了维护一个准确的到达目标点的代价值所以它的搜索似乎是从终点往起点搜索的。此外,关于k[s]与h[s]的定义,原论文中是这么定义的:

For each state X on the OPEN list (i.e., r ( X ) = OPEN ) , the key function,k(G, X ) , is defined to be equal to the minimum of h(G, X )
before modification and all values assumed by h(G, X ) since X was placed on the OPEN list.

即每个点,其存在两个值k[s]与h[s],而k[s]则等于搜索过程中该点的最小代价值,即h[s]中遇到的最小的那个。这个有什么用呢?后面会讲到,让我们继续往下看。

在找到一条路径后,机器人开始沿着这条路径前进,直到触发障碍物,此时会讲该点的h值进行修改。修改规则在论文中是这么定义的:
在这里插入图片描述
这其中使用到了一个函数insert。这个函数在论文中没有细讲,翻看了一些其他人的文章中,统一的逻辑是这么处理的:

def insert(self, s, h_new):
        """
        insert node into OPEN set.
        :param s: node
        :param h_new: new or better cost to come value
        """

        if self.t[s] == 'NEW':
            self.k[s] = h_new
        elif self.t[s] == 'OPEN':
            self.k[s] = min(self.k[s], h_new)
        elif self.t[s] == 'CLOSED':
            self.k[s] = min(self.h[s], h_new)

        self.h[s] = h_new
        self.t[s] = 'OPEN'
        self.OPEN.add(s)

对于路径上是障碍物的点,它的h(x)为inf即无穷,所以这里算法只改变该点的h值而不会改变其k值,同时这个点被加入到open list内,然后算法会调用process_state函数进行处理,对于process_state,原论文中是这么定义的:
在这里插入图片描述
这里就是D* 算法的精髓所在了。当算法进入这个函数时,首先获取OPEN list中k值最小的点,并将该点从OPEN list中删除:

 kold = GET - KMIN( ) ; DELETE(X)

而h(x)的计算则是与Dijkstra算法或者A*算法中的定义一致,代表了当前点到目标点的代价值。当一个点被识别为障碍物时,它的h值会变成inf,因此该点的h值肯定是大于K值的,这种状态在论文中被定义为RAISE的状态,即k(G,X)<h(G,X),另外如果k(G,X)=h(G、X)则为LOWER的状态。论文中提到:

If X is a RAISE state, its path cost may not be optimal.

D* 使用OPEN列表上的RAISE状态来传播有关路径成本增加的信息,使用LOWER状态来传播关于路径成本减少的信息。即,如果X是RAISE状态,则其路径成本可能不是最优的。这也比较好理解,本来第一次规划完的时候k(s)=h(s),而遇到障碍物后h(s)增加,即该点前往目标点的代价值因为障碍物而增加了,那么此时这个点的路径大部分情况下就不是最优的了,都要撞了嘛。

而同时,我们也会根据这个状态在该点及附近开始搜索出一条新的路径,搜索的方式就是按照上述的process_state来循环更新open list中每个点及其附近点的信息,直到满足:

if k_min >= self.h[s]:

这里k_min是指open list中最小的k值,s则是之前遇到障碍物的点,这里之前在执行modify的过程中它的值边变成了inf,但是后面在process_state中其值会被改变,同时改变的还有它周围的一些点,这里的含义也就是说算法从遇到障碍物的点开始带方向的搜索,其搜索方向就是偏向于目标点的,因为相对而言这些点的k值更小,更容易被进行搜索,这样就避免了一些无意义的搜索。

示例

我们通过一个简单的例子看一下这个问题:
在这里插入图片描述
假设当前存在如上一张地图,起点坐标为(5,5),终点为(45,25),算法初始时使用Dijkstra算法寻找到了一条最优路径,这条路径经过的点为:

[(5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (9, 10), (9, 11), (9, 12), (9, 13), (9, 14), (9, 15), (9, 16), (10, 16), (11, 16), (12, 16), (13, 16), (14, 16), (15, 16), (16, 16), (17, 16), (18, 16), (19, 16), (20, 16), (21, 16), (22, 15), (23, 14), (24, 14), (25, 14), (26, 14), (27, 14), (28, 14), (29, 14), (30, 14), (31, 14), (32, 15), (33, 16), (34, 17), (35, 18), (36, 19), (37, 20), (38, 21), (39, 22), (40, 23), (41, 24), (42, 25), (43, 25), (44, 25), (45, 25)]

然后我们在(9,10)这个位置插入一个障碍物。此时机器人从起点开始向终点前进。当机器人到达(9,9)时会发现下一个点是障碍物:
在这里插入图片描述
图中右上角代表当前的k值,刚开始时k值与h值是一样的,所以这里没有列出h值,箭头方向代表点的父系方向。

此时算法会调用modify函数修改(9,9)的h值,因为下个点是障碍物,所以(9,9)的h值会变成inf。

然后算法再调用process_state来处理这个点。首先,算法会进入第一个IF语句:
在这里插入图片描述
从当前点附近寻找能使当前点代价值更小的,当然由于这个点目前是最优路径上的点,所以其周围的点的k值都应该是比它要大的。所以这里的if语句目前是不会进入的。算法遍历周围的8个点但是没有进行点的参数改变。然后算法进入第二个IF语句:
在这里插入图片描述
前面我们知道在修改点位信息的时候k值是没有改变的,所以这里会进ELSE部分,对当前点周围点再次进行遍历,但是这里的判断条件就不一样了。这里会根据两个条件判断:该点的父系是否是(9,9)以及它的值是否会跟之前不一致。例如对于点(8,8),当前它的父系是(9,9),所以它会进第二个IF语句,但是对于(8,9),它的父系不是(9,9),所以它会进第二个else。

这些判断会改变当前点周围8个邻近点的状态,初始时,这些点的状态为:

(8,10) h: 47.798989873223334 parent: (9, 11) k: 47.798989873223334
(9,10) h: 47.38477631085024 parent: (9, 11) k: 47.38477631085024
(10,10) h: 47.798989873223334 parent: (9, 11) k: 47.798989873223334
(8,9) h: 48.798989873223334 parent: (9, 10) k: 48.798989873223334
(9,9) h: 48.38477631085024 parent: (9, 10) k: 48.38477631085024
(10,9) h: 48.798989873223334 parent: (9, 10) k: 48.798989873223334
(8,8) h: 49.798989873223334 parent: (9, 9) k: 49.798989873223334
(9,8) h: 49.38477631085024 parent: (9, 9) k: 49.38477631085024
(10,8) h: 49.798989873223334 parent: (9, 9) k: 49.798989873223334

经过一次遍历后变为:

(8,10) h: 47.798989873223334 parent: (9, 11) k: 47.798989873223334
(9,10) h: 47.38477631085024 parent: (9, 11) k: 47.38477631085024
(10,10) h: 47.798989873223334 parent: (9, 11) k: 47.798989873223334
(8,9) h: 48.798989873223334 parent: (9, 10) k: 48.798989873223334
(9,9) h: inf parent: (9, 10) k: 48.38477631085024
(10,9) h: 48.798989873223334 parent: (9, 10) k: 48.798989873223334
(8,8) h: inf parent: (9, 9) k: 49.798989873223334
(9,8) h: inf parent: (9, 9) k: 49.38477631085024
(10,8) h: inf parent: (9, 9) k: 49.798989873223334

在这里插入图片描述
图中浅蓝色的代表当前遍历时被改变的点,可以看到它们的h值目前都暂时变成了inf。
同时刚才遍历的一部分点被插入到了OPEN list内,例如(8,9),而(8,10),(9,10),(10,10)由于不满足条件其不会被插入OPEN list中,因此现在k值最小的就变成了(8,9)。

同样的,遍历(8,9)周围的点,找到并修改它的值,这个时候,由于一些点的h值的变化,导致了它们的父系会发生变化,例如(9,9)的父系会从(9,10)变成(8,9),因为(9,10)是障碍物,它们之间的代价值是无穷大,显然走(8,9)代价值会小很多,因此(9,9)的下一步被改为了(8,9)。

然后继续循环,算法会依次遍历:(9,9)、(8,9):
在这里插入图片描述

(10, 9):
在这里插入图片描述

(9, 8):
在这里插入图片描述

(8, 9):
在这里插入图片描述

(8, 10):
在这里插入图片描述

等一系列顺序,最后修改后的结果为:

(9,9)的父系变成(8,9)
(8,9)的父系变为(8,10)
(8,10)的父系变为(8,11)

新路径为:
在这里插入图片描述

而(8,11)的父系为(9,12),也就绕过了障碍物,至此,一条新的路径就被规划出来了:
在这里插入图片描述

附上代码:

import os
import sys
import math
import matplotlib.pyplot as plt


class DStar:
    def __init__(self, s_start, s_goal, xI, xG):
        self.s_start, self.s_goal = s_start, s_goal
        self.x_range = 51  # size of background
        self.y_range = 31
        self.motions = [(-1, 0), (-1, 1), (0, 1), (1, 1),
                        (1, 0), (1, -1), (0, -1), (-1, -1)]  # feasible input set
        self.obs = self.obs_map()
        self.xI, self.xG = xI, xG
        self.u_set = self.motions
        self.obs = self.obs
        self.x = self.x_range
        self.y = self.y_range

        self.fig = plt.figure()
        self.flag = True
        self.OPEN = set()
        self.t = dict()
        self.PARENT = dict()
        self.h = dict()
        self.k = dict()
        self.path = []
        self.visited = set()
        self.count = 0

    def init(self):
        for i in range(self.x_range):
            for j in range(self.y_range):
                self.t[(i, j)] = 'NEW'
                self.k[(i, j)] = 0.0
                self.h[(i, j)] = float("inf")
                self.PARENT[(i, j)] = None

        self.h[self.s_goal] = 0.0
    def update_obs(self, obs):
        self.obs = obs

    def animation(self, path, visited, name):
        self.plot_grid(name)
        self.plot_visited(visited)
        self.plot_path(path)
        plt.show()

    def plot_grid(self, name):
        obs_x = [x[0] for x in self.obs]
        obs_y = [x[1] for x in self.obs]

        plt.plot(self.xI[0], self.xI[1], "bs")
        plt.plot(self.xG[0], self.xG[1], "gs")
        plt.plot(obs_x, obs_y, "sk")
        plt.title(name)
        plt.axis("equal")

    def plot_visited(self, visited, cl='gray'):
        if self.xI in visited:
            visited.remove(self.xI)

        if self.xG in visited:
            visited.remove(self.xG)

        count = 0

        for x in visited:
            count += 1
            plt.plot(x[0], x[1], color=cl, marker='o')
            plt.gcf().canvas.mpl_connect('key_release_event',
                                         lambda event: [exit(0) if event.key == 'escape' else None])

            if count < len(visited) / 3:
                length = 20
            elif count < len(visited) * 2 / 3:
                length = 30
            else:
                length = 40
            #
            # length = 15

            if count % length == 0:
                plt.pause(0.001)
        plt.pause(0.01)

    def plot_path(self, path, cl='r', flag=False):
        path_x = [path[i][0] for i in range(len(path))]
        path_y = [path[i][1] for i in range(len(path))]

        if not flag:
            plt.plot(path_x, path_y, linewidth='3', color='r')
        else:
            plt.plot(path_x, path_y, linewidth='3', color=cl)

        plt.plot(self.xI[0], self.xI[1], "bs")
        plt.plot(self.xG[0], self.xG[1], "gs")

        plt.pause(0.01)

    def obs_map(self):
        """
        Initialize obstacles' positions
        :return: map of obstacles
        """

        x = self.x_range
        y = self.y_range
        obs = set()

        for i in range(x):
            obs.add((i, 0))
        for i in range(x):
            obs.add((i, y - 1))

        for i in range(y):
            obs.add((0, i))
        for i in range(y):
            obs.add((x - 1, i))

        for i in range(10, 21):
            obs.add((i, 15))
        for i in range(15):
            obs.add((20, i))

        for i in range(15, 30):
            obs.add((30, i))
        for i in range(16):
            obs.add((40, i))

        return obs
    def run(self, s_start, s_end):
        self.init()
        self.insert(s_end, 0)

        while True:
            self.process_state()
            if self.t[s_start] == 'CLOSED':
                break

        self.path = self.extract_path(s_start, s_end)
        print(self.path)
        self.plot_grid("Dynamic A* (D*)")
        self.plot_path(self.path)
        self.fig.canvas.mpl_connect('button_press_event', self.on_press)
        plt.show()

    def on_press(self, event):
        x, y = event.xdata, event.ydata
        if x < 0 or x > self.x - 1 or y < 0 or y > self.y - 1:
            print("Please choose right area!")
        else:
            x, y = int(x), int(y)
            if (x, y) not in self.obs:
                print("Add obstacle at: s =", x, ",", "y =", y)
                self.obs.add((x, y))
                self.update_obs(self.obs)

                s = self.s_start
                self.visited = set()
                self.count += 1
                self.flag = True
                self.numb = 0
                while s != self.s_goal:
                    #print("s is in:",s)
                    self.numb += 1
                    if self.numb > 1000:
                        print("no route")
                        break
                    if self.is_collision(s, self.PARENT[s]):
                        if self.flag == True:
                            self.flag = False
                            self.s_start = s
                        self.modify(s)
                        continue
                    
                    s = self.PARENT[s]
                self.path = self.extract_path(self.s_start, self.s_goal)
                print(self.path)
                plt.cla()
                self.plot_grid("Dynamic A* (D*)")
                self.plot_visited(self.visited)
                self.plot_path(self.path)

            self.fig.canvas.draw_idle()

    def extract_path(self, s_start, s_end):
        path = [s_start]
        s = s_start
        while True:
            s = self.PARENT[s]
            path.append(s)
            if s == s_end:
                return path

    def process_state(self):
        s = self.min_state()  # get node in OPEN set with min k value
        self.visited.add(s)
        
        if s is None:
            return -1  # OPEN set is empty
        k_old = self.get_k_min()  # record the min k value of this iteration (min path cost)
        self.delete(s)  # move state s from OPEN set to CLOSED set
        self.t[s] = 'CLOSED'
        # k_min < h[s] --> s: RAISE state (increased cost)
        if k_old < self.h[s]:
            for s_n in self.get_neighbor(s):
                
                if self.h[s_n] <= k_old and \
                        self.h[s] > self.h[s_n] + self.cost(s_n, s):
                    # update h_value and choose parent
                    self.PARENT[s] = s_n
                    self.h[s] = self.h[s_n] + self.cost(s_n, s)

        # s: k_min >= h[s] -- > s: LOWER state (cost reductions)
        if k_old == self.h[s]:
            for s_n in self.get_neighbor(s):
                if self.t[s_n] == 'NEW' or \
                        (self.PARENT[s_n] == s and self.h[s_n] != self.h[s] + self.cost(s, s_n)) or \
                        (self.PARENT[s_n] != s and self.h[s_n] > self.h[s] + self.cost(s, s_n)):

                    # Condition:
                    # 1) t[s_n] == 'NEW': not visited
                    # 2) s_n's parent: cost reduction
                    # 3) s_n find a better parent
                    self.PARENT[s_n] = s
                    self.insert(s_n, self.h[s] + self.cost(s, s_n))
        else:
            for s_n in self.get_neighbor(s):
                if self.t[s_n] == 'NEW' or \
                        (self.PARENT[s_n] == s and self.h[s_n] != self.h[s] + self.cost(s, s_n)):

                    # Condition:
                    # 1) t[s_n] == 'NEW': not visited
                    # 2) s_n's parent: cost reduction
                    self.PARENT[s_n] = s
                    self.insert(s_n, self.h[s] + self.cost(s, s_n))
                else:
                    if self.PARENT[s_n] != s and \
                            self.h[s_n] > self.h[s] + self.cost(s, s_n):

                        # Condition: LOWER happened in OPEN set (s), s should be explored again
                        self.k[s] = self.h[s]
                        self.insert(s, self.h[s])
                    else:
                        if self.PARENT[s_n] != s and \
                                self.h[s] > self.h[s_n] + self.cost(s_n, s) and \
                                self.t[s_n] == 'CLOSED' and \
                                self.h[s_n] > k_old:

                            # Condition: LOWER happened in CLOSED set (s_n), s_n should be explored again
                            self.insert(s_n, self.h[s_n])
        return self.get_k_min()

    def min_state(self):
        """
        choose the node with the minimum k value in OPEN set.
        :return: state
        """

        if not self.OPEN:
            return None

        return min(self.OPEN, key=lambda x: self.k[x])

    def get_k_min(self):
        """
        calc the min k value for nodes in OPEN set.
        :return: k value
        """

        if not self.OPEN:
            return -1

        return min([self.k[x] for x in self.OPEN])

    def insert(self, s, h_new):
        """
        insert node into OPEN set.
        :param s: node
        :param h_new: new or better cost to come value
        """

        if self.t[s] == 'NEW':
            self.k[s] = h_new
        elif self.t[s] == 'OPEN':
            self.k[s] = min(self.k[s], h_new)
        elif self.t[s] == 'CLOSED':
            self.k[s] = min(self.h[s], h_new)

        self.h[s] = h_new
        self.t[s] = 'OPEN'
        self.OPEN.add(s)

    def delete(self, s):
        """
        delete: move state s from OPEN set to CLOSED set.
        :param s: state should be deleted
        """

        if self.t[s] == 'OPEN':
            self.t[s] = 'CLOSED'

        self.OPEN.remove(s)

    def modify(self, s):
        """
        start processing from state s.
        :param s: is a node whose status is RAISE or LOWER.
        """
        self.modify_cost(s)
        while True:
            if not self.OPEN:
                print("no route")
                break
            k_min = self.process_state()

            if k_min >= self.h[s]:
                break

    def modify_cost(self, s):
        # if node in CLOSED set, put it into OPEN set.
        # Since cost may be changed between s - s.parent, calc cost(s, s.p) again

        if self.t[s] == 'CLOSED':
            self.insert(s, self.h[self.PARENT[s]] + 100*self.cost(s, self.PARENT[s]))

    def get_neighbor(self, s):
        nei_list = set()

        for u in self.u_set:
            s_next = tuple([s[i] + u[i] for i in range(2)])
            if s_next not in self.obs:
                nei_list.add(s_next)

        return nei_list

    def cost(self, s_start, s_goal):
        """
        Calculate Cost for this motion
        :param s_start: starting node
        :param s_goal: end node
        :return:  Cost for this motion
        :note: Cost function could be more complicate!
        """

        if self.is_collision(s_start, s_goal):
            return float("inf")

        return math.hypot(s_goal[0] - s_start[0], s_goal[1] - s_start[1])

    def is_collision(self, s_start, s_end):
        if s_start in self.obs:
            return True
        if s_end in self.obs:
            return True

        if s_start[0] != s_end[0] and s_start[1] != s_end[1]:
            if s_end[0] - s_start[0] == s_start[1] - s_end[1]:
                s1 = (min(s_start[0], s_end[0]), min(s_start[1], s_end[1]))
                s2 = (max(s_start[0], s_end[0]), max(s_start[1], s_end[1]))
            else:
                s1 = (min(s_start[0], s_end[0]), max(s_start[1], s_end[1]))
                s2 = (max(s_start[0], s_end[0]), min(s_start[1], s_end[1]))

            if s1 in self.obs or s2 in self.obs:
                return True

        return False

    def plot_path(self, path):
        px = [x[0] for x in path]
        py = [x[1] for x in path]
        plt.plot(px, py, linewidth=2)
        plt.plot(self.s_start[0], self.s_start[1], "bs")
        plt.plot(self.s_goal[0], self.s_goal[1], "gs")

    def plot_visited(self, visited):
        color = ['gainsboro', 'lightgray', 'silver', 'darkgray',
                 'bisque', 'navajowhite', 'moccasin', 'wheat',
                 'powderblue', 'skyblue', 'lightskyblue', 'cornflowerblue']

        if self.count >= len(color) - 1:
            self.count = 0

        for x in visited:
            plt.plot(x[0], x[1], marker='s', color=color[self.count])


def main():
    s_start = (5, 5)
    s_goal = (45, 25)
    dstar = DStar(s_start, s_goal,s_start,s_goal)
    dstar.run(s_start, s_goal)


if __name__ == '__main__':
    main()

注意:

1、上述代码的源码本来是来自于《路径规划算法》这篇文章的代码分享,但是在使用的过程中发现了一个问题:原论文中提到的是从遇到障碍物的位置开始规划,但是这篇文章中每次遇到障碍物后提取路径还是会从最初的起点开始提取,于是这篇文章中的代码中我发现了这么一个问题:当我连续在同一个点附近不断添加障碍物时,从最初的起点开始提取出来到往终点的路径会出现问题:
在这里插入图片描述
其实它的真实路径应该是这样子:
在这里插入图片描述
出现前面这种问题的原因是在对障碍物周围点的遍历时没有能够修改到足够远的点的父系,导致了从初始点开始的路径并没有被整个的修改掉。这里不确定是D* 算法本身的算法缺陷还是说我对它的文章理解还存在一定的问题。
2、D* 算法虽然会对路径上的障碍物点进行重新规划,但是对于现有的障碍物消除后的路径规划是无法进行的。即如果你将一个障碍物放到路径上,机器人绕行了,然后你去除了障碍物,下次机器人还是会进行绕行,并不会恢复最初的路径。这也是D* 算法的一点缺陷了。

不过总体而言D* 确实是一种非常优异的算法。

参考:
1、《路径规划算法》
2、《D*路径搜索算法原理解析及Python实现》
3、《D*算法超详解》
4、《D*算法原理与程序详解(Python)》
5、《D*算法(Dynamic A Star)》

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

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

相关文章

不断探索创新 促进中国信息技术发展——南京宏控科技有限公司董事长应富忠

应富忠&#xff0c;男&#xff0c;现任南京宏控科技有限公司董事长、电子系统工程高级工程师&#xff08;技术五级&#xff09;、自动化系统注册工程师&#xff0c;先后被评为“研究所级青年突击手”、“研究所级先进工作者”、“研究所级优秀共产党员”、“南京市级考级优秀”…

微服务保护-授权规则/规则持久化

授权规则 基本规则 授权规则可以对调用方的来源做控制&#xff0c;有白名单和黑名单两种方式。 白名单&#xff1a;来源&#xff08;origin&#xff09;在白名单内的调用者允许访问 黑名单&#xff1a;来源&#xff08;origin&#xff09;在黑名单内的调用者不允许访问 点…

未解之迷——晶振问题导致SWD烧录时芯片no target connected,切换内部晶振后解决了

我所讲的情况是网上总结之外的另一种情况。不是Reset 后卡时间烧录&#xff0c;也不是烧录器问题&#xff0c;引脚问题等。而是STM32CubeMX软件生成问题。 芯片&#xff1a;STM32F103C8T6 某天我做了一块板子&#xff0c;按正常流程烧录&#xff0c;第一次可以烧录&#xff0c…

让Pegasus天马座开发板吃上STM8S标准库

WeCanStudio官方仓库的示例工程,只提供基于STM8S003寄存器方式来开发Pegasus天马座开发板。在此,我将基于官方的工程示例&#xff0c;将STM8S标准库移植到工程中。 先上图&#xff0c;看运行结果: main.c文件 #include "config.h" #include "delay.h"#de…

灯具欧盟CE认证,EMC/LVD/MD等欧盟指令介绍

CE认证产品范围 ​1.灯具类产品; ​2.家用电器设备、电动工具; ​3.个人电脑及其周边设备; ​4.音视频产品; ​5.无线产品; ​6.通讯类类产品; ​7.玩具类产品; ​8.安防产品; ​9.工业机械。 CE认证所需资料 ​1.一般2-3个测试样品; ​2.电路原理图; ​3.产品说明…

基于哈希表对unordered_map和unordered_set的封装

本章完整代码gitee仓库&#xff1a;对unordered_map和unordered_set的封装、unordered_map和unordered_set源码 文章目录 &#x1f36d;1. 哈希表的改造&#x1f36c;1.1 模板参数的改造&#x1f36c;1.2 增加迭代器&#x1f36c;1.3 返回值的修改 &#x1f37c;2. 对unordered…

[计算机入门] 电源选项设置

3.10 电源选项设置 有时候我们的电脑一段时间没有用&#xff0c;会自己关掉屏幕或者直接睡眠&#xff0c;这是电源选项没有设置好导致的。 1、打开控制面板&#xff0c;打开其中的电源选项 2、点击左侧上方的选择关闭显示器的时间 3、进入到编辑计划设置界面&#xff0c;在…

听GPT 讲Istio源代码--pilot(6)

在 Istio 中&#xff0c;Pilot 是 Istio 控制平面的一个重要组件&#xff0c;它具有以下作用&#xff1a; 流量管理: Pilot 负责管理和配置服务之间的网络流量。它通过与底层的服务发现机制&#xff08;如 Kubernetes 或 Consul&#xff09;集成&#xff0c;监测服务注册和注销…

C数据结构二.练习题

一.求级数和 2.求最大子序列问题:设给定一个整数序列 ai.az..,a,(可能有负数).设计一个穷举算法,求a 的最大值。例如,对于序列 A {1,-1,1,-1,-1,1,1,1,1.1,-1,-1.1,-1,1,-1},子序列 A[5..9](1,1,1,1,1)具有最大值5 3.设有两个正整数 m 和n,编写一个算法 gcd(m,n),求它们的最大公…

WhatsApp无法收到验证码怎么办?别急,我来教你

最近收到好多小伙伴的问题咨询&#xff0c;而且大多是同一个问题&#xff1a;“WhatsApp无法验证手机号&#xff0c;也就是手机接收不到6位数字的短信验证码&#xff0c;这可如何是好&#xff1f;” 短信验证码收不到&#xff0c;连点几次重复短信后等待时间越来越久点击致电给…

B-小美的子序列(贪心)-- 牛客周赛 Round 12

示例1 输入 3 3 abc def ghi 输出 NO 示例2 输入 8 2 nm ex it td ul qu ac nt 输出 YES 说明 第1行选择第2个字母。 第2行选择第1个字母。 第3行选择第1个字母。 第4行选择第1个字母。 第5行选择第2个字母。 第6行选择第2个字母。 第7行选择第1个字母。 第8行选择第…

Idea创建springboot项目

1、选择file—>new –->project 2、选择“Spring Initializr”&#xff0c;点击“next”&#xff0c;进入工程信息配置界面修改配置信息. 备注&#xff1a;type类型选择“Maven(Generate a Maven based project achieve)”&#xff0c;生成工程路径。 3、点击next按钮&a…

【uniapp+vue3+u-picker】获取中国省市区数据结构,省市区数据三级联动json文件完整版,已实现三级联动效果

前言: 这个功能的实现,中间耽误了几天,在大佬的帮助下终于实现效果,匿名感谢xx大佬 要实现的效果如下: 1、首先需要获取省市区的数据,不考虑后端返数据,自己使用json文件的话,需要获取到完整的中国省市区数据 有个很不错的github源码可供参考,Administrative-divisio…

RK3568开发笔记(十):开发板buildroot固件移植开发的应用Demo,启动全屏显示

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/133021990 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

few shot目标检测survey paper笔记(迁移学习)

paper: Few-Shot Object Detection: A Comprehensive Survey (CVPR2021) meta learning需要复杂的情景训练&#xff0c;而迁移学习仅需在一个single-branch结构上做两步训练。 常用的结构是Faster R-CNN&#xff0c;下面是Faster R-CNN的结构图。 RPN的修改 当样本数量很少时…

Vue的进阶使用--模板语法应用

目录 前言 一. Vue的基础语法 1.插值 1.1文本插值 1.2HTML插值 1.3属性插值 1.4Vue演示三元条件运算 2 指令 2.1if&&else指令&#xff08;v-if/v-else-if/v-else&#xff09; 2.2 v-for 指令 2.3 v-on指令(动态参数) 2.4知识点补充之v-if与v-show的区别 3.过…

著名书法家傅成洪在香港第八届“一带一路”高峰论坛上展示艺术与合作的融合

香港第八届“一带一路”高峰论坛于9月13日至14日在香港隆重举行&#xff0c;吸引了来自海内外的6000多名嘉宾&#xff0c;共同回顾“一带一路”倡议的历程&#xff0c;并展望未来的投资和商贸机遇。这一庆祝活动恰逢“一带一路”倡议的10周年&#xff0c;主题定为“携手十载 共…

[Python进阶] Pyinstaller打包模式

5.3 Pyinstaller打包模式 Pyinstaller将Python源码打包成程序有2种打包的方式&#xff1a; 单文件夹模式&#xff1a;指打包后将所有的程序文件放在一个文件夹内。 单文件模式&#xff1a;打包后只有一个可执行文件&#xff0c;全部的依赖文件都已经被打包进去了。 5.3.1 单文…

Linux 信号相关

int kill(pid_t pid, int sig); -功能&#xff1a;给某个进程pid 发送某个信号 参数sig可以使用宏值或者和它对应的编号 参数pid&#xff1a; >0 &#xff1b;将信号发给指定的进程 0&#xff1b;将信号发送给当前的进程组 -1&#xff1b;发送给每一个有权限接受这个信号的…

Postman应用——Variable变量设置(Global、Environment和Collection)

文章目录 Global变量设置Environment变量设置Collection变量设置Global、Environment环境变量预览 Global、Environment和Collection变量使用&#xff0c;点击查看。 Global变量设置 全局变量设置&#xff0c;作用域是所有Collection、Folder和Request&#xff0c;全局变量只有…