MPC算法路径跟踪_Matlab实现
在机器人控制领域,模型预测控制(MPC)因其能够处理动态约束和多目标优化的特性,成为路径跟踪的热门方案。近期,我在 GitHub 上发现了 @Mr.Winter` 的MPC路径规划项目,其代码实现简洁且功能完整。本文将结合理论分析与代码解读,分享我的学习心得。
该项目实现了差分驱动机器人的 MPC 路径跟踪,主要功能包括:
动态前瞻点选择
角度优先的分层控制策略
约束条件下的二次规划优化
特别感谢作者Winter的开源贡献!
代码
function [pose, traj, flag] = mpc_plan(start, goal, varargin)% @file: mpc_plan.m
% @breif: Model Predicted Control (MPC) motion planning
% @author: Winter
% @update: 2023.2.7p = inputParser; addParameter(p, 'path', "none");addParameter(p, 'map', "none");parse(p, varargin{:});if isstring(p.Results.path) || isstring(p.Results.map)exception = MException('MyErr:InvalidInput', 'parameter `path` or `map` must be set.');throw(exception);end% pathpath = flipud(p.Results.path);path = path_interpolation(path, 5);% initial robotic staterobot.x = start(1);robot.y = start(2);robot.theta = start(3);robot.v = 0;robot.w = 0;% common parametersparam.dt = 0.1;%每次控制更新的时间间隔param.max_iteration = 2000;%最大迭代次数param.goal_dist_tol = 1.0;%目标距离容差,当车辆与目标点的距离小于这个容差时,认为车辆已经到达目标点param.rotate_tol = 0.5;%旋转容差,当车辆的朝向与目标朝向的偏差小于这个容差时,认为车辆的朝向已经达到要求。param.lookahead_time = 1.0;%控制器向前预测的时间范围,用于确定前瞻距离param.min_lookahead_dist = 1.0;%最小前瞻距离param.max_lookahead_dist = 2.5;%最大前瞻距离param.max_v_inc = 0.5;%最大线速度增量param.max_v = 1.0;param.min_v = 0.0;param.max_w_inc = pi / 2;%最大角速度增量param.max_w = pi / 2;param.min_w = 0.0;% MPC parametersparam.Q = diag([1, 1, 1]);%状态权重矩阵param.R = diag([2, 2]);%控制输入权重矩阵param.p = 12;%预测时域长度。表示 MPC 算法向前预测的步数param.m = 8;%控制时域长度。表示 MPC 算法在每个控制周期内优化的控制输入步数param.u_min = [param.min_v; param.min_w];param.u_max = [param.max_v; param.max_w];param.du_min = [param.min_v; -param.max_w_inc];param.du_max = [param.max_v_inc; param.max_w_inc];% return valueflag = false;pose = [];traj = [];%轨迹% main loopiter = 0;u_p = [0, 0];%先前的控制误差while iter < param.max_iterationiter = iter + 1;% break until goal reachedif shouldRotateToGoal([robot.x, robot.y], goal, param)flag = true;break;end% get the particular point on the path at the lookahead distance%获取路径上前瞻距离处的特定点[lookahead_pt, theta_trj, kappa] = getLookaheadPoint(robot, path, param);% calculate velocity commande_theta = regularizeAngle(robot.theta - goal(3)) / 10;if shouldRotateToGoal([robot.x, robot.y], goal, param)%是否需要旋转到目标点if ~shouldRotateToPath(abs(e_theta), 0.0, param)u = [0, 0];elseu = [0, angularRegularization(robot, e_theta / param.dt, param)];%将线速度设为 0,角速度经过规范化处理后赋值给 u,表示机器人只进行旋转运动endelsee_theta = regularizeAngle( ...atan2(real(lookahead_pt(2)) - robot.y, real(lookahead_pt(1)) - robot.x) - robot.theta ...) / 10;%从机器人当前位置到前瞻点的方向角度,减去机器人当前的朝向角度 robot.theta,并进行角度规范化和除以 10 的操作if shouldRotateToPath(abs(e_theta), pi / 4, param)u = [0, angularRegularization(robot, e_theta / param.dt, param)];else%不需要旋转到路径方向,使用模型预测控制(MPC)计算控制指令% current states = [robot.x, robot.y, robot.theta];% desired states_d = [real(lookahead_pt), theta_trj];% refered input 参考输入u_r = [robot.v, theta_trj - robot.theta];% control[u, u_p] = mpcControl(s, s_d, u_r, u_p, robot, param);endend% input into robotic kinematicrobot = f(robot, u, param.dt);pose = [pose; robot.x, robot.y, robot.theta];end
end%%
function robot = f(robot, u, dt)% robotic kinematicF = [ 1 0 0 0 00 1 0 0 00 0 1 0 00 0 0 0 00 0 0 0 0];B = [dt * cos(robot.theta) 0dt * sin(robot.theta) 00 dt1 00 1];x = [robot.x; robot.y; robot.theta; robot.v; robot.w];x_star = F * x + B * u';robot.x = x_star(1); robot.y = x_star(2); robot.theta = x_star(3);robot.v = x_star(4); robot.w = x_star(5);
endfunction theta = regularizeAngle(angle)theta = angle - 2.0 * pi * floor((angle + pi) / (2.0 * pi));
end
%比较机器人当前位置与目标位置的欧几里得距离和目标距离容差,来判断机器人是否需要执行旋转操作以到达目标位姿。如果距离小于容差,返回 true
function flag = shouldRotateToGoal(cur, goal, param)%{Whether to reach the target pose through rotation operationParameters----------cur: tuplecurrent pose of robotgoal: tuplegoal pose of robotReturn----------flag: booltrue if robot should perform rotation%}flag = hypot(cur(1) - goal(1), cur(2) - goal(2)) < param.goal_dist_tol;
end
%判断机器人是否需要通过旋转操作来纠正其跟踪路径,即判断机器人当前的朝向与路径期望朝向之间的角度偏差是否超出了可容忍的范围
function flag = shouldRotateToPath(angle_to_path, tol, param)%{Whether to correct the tracking path with rotation operationParameters----------angle_to_path: float the angle deviationtol: float[None]the angle deviation tolerenceReturn----------flag: booltrue if robot should perform rotation%}if tol == 0.0flag = angle_to_path > param.rotate_tol;elseflag = angle_to_path > tol;end
end
%对机器人的角速度进行正则化处理,确保角速度及其变化量在设定的允许范围内
function w = angularRegularization(robot, w_d, param)%{Angular velocity regularizationParameters----------w_d: floatreference angular velocity inputReturn----------w: floatcontrol angular velocity output%}w_inc = w_d - robot.w;if abs(w_inc) > param.max_w_incw_inc =param.max_w_inc * sign(w_inc);endw = robot.w + w_inc;if abs(w) > param.max_ww = param.max_w * sign(w);endif abs(w) < param.min_ww = param.min_w * sign(w) ;end
endfunction v = linearRegularization(robot, v_d, param)%{Linear velocity regularizationParameters----------v_d: floatreference velocity inputReturn----------v: floatcontrol velocity output%}v_inc = v_d - robot.v;if abs(v_inc) > param.max_v_incv_inc = param.max_v_inc * sign(v_inc);endv = robot.v + v_inc;if abs(v) > param.max_vv = param.max_v * sign(v);endif abs(v) < param.min_vv = param.min_v * sign(v);end
end
%计算机器人的前瞻距离
function d = getLookaheadDistance(robot, param)d = robot.v * param.lookahead_time;if d < param.min_lookahead_distd = param.min_lookahead_dist;endif d > param.max_lookahead_distd = param.max_lookahead_dist;end
endfunction [pt, theta, kappa] = getLookaheadPoint(robot, path, param)%{Find the point on the path that is exactly the lookahead distance away from the robotReturn----------lookahead_pt: tuplelookahead pointtheta: floatthe angle on trajectorykappa: floatthe curvature on trajectory%}% Find the first pose which is at a distance greater than the lookahead distance、%找到第一个距离机器人大于等于前瞻距离的点dist_to_robot = [];[pts_num, ~] = size(path);% 使用 size 函数获取路径 `path` 的大小,`pts_num` 表示路径上点的数量,`~` 表示忽略路径点的维度信息(因为这里只关心点的数量)for i=1:pts_num% 使用 hypot 函数计算路径上第 `i` 个点到机器人当前位置的欧几里得距离dist_to_robot(end + 1) = hypot(path(i, 1) - robot.x, path(i, 2) - robot.y);end%%先找到路径上距离机器人最近的点,然后根据前瞻距离,从该最近点开始向后搜索,找到第一个距离机器人大于等于前瞻距离的点。[~, idx_closest] = min(dist_to_robot);idx_goal = pts_num - 1; idx_prev = idx_goal - 1;%初始化目标点和前一个点的索引lookahead_dist = getLookaheadDistance(robot, param);for i=idx_closest:pts_numif hypot(path(i, 1) - robot.x, path(i, 2) - robot.y) >= lookahead_distidx_goal = i;break;endendif idx_goal == pts_num - 1% If the no pose is not far enough, take the last pose%之前的搜索中没有找到距离机器人大于等于前瞻距离的点,可能路径上所有点距离机器人都不够远 将路径上倒数第二个点作为前瞻点pt = [path(idx_goal, 1), path(idx_goal, 2)];elseif idx_goal == 1idx_goal = idx_goal + 1;end% find the point on the line segment between the two poses% that is exactly the lookahead distance away from the robot pose (the origin)% This can be found with a closed form for the intersection of a segment and a circle%找到路径上距离机器人当前位置恰好为前瞻距离(lookahead_dist)的点,并计算该点所在路径线段的切线角度idx_prev = idx_goal - 1;px = path(idx_prev, 1); py = path(idx_prev, 2);gx = path(idx_goal, 1); gy = path(idx_goal, 2);% transform to the robot frame so that the circle centers at (0,0)%将线段的两个端点坐标从全局坐标系转换到以机器人位置为原点的局部坐标系prev_p = [px - robot.x, py - robot.y];goal_p = [gx - robot.x, gy - robot.y];i_points = circleSegmentIntersection(prev_p, goal_p, lookahead_dist);%计算线段与圆的交点pt = [i_points(1, 1) + robot.x, i_points(1, 2) + robot.y];%全局坐标系下的前瞻点坐标end% calculate the angle on trajectory%计算路径上 idx_prev 到 idx_goal 这两点所构成线段的切线角度theta = atan2(path(idx_goal, 2) - path(idx_prev, 2), path(idx_goal, 1) - path(idx_prev, 1));% calculate the curvature on trajectory 曲率if idx_goal == 2idx_goal = idx_goal + 1;endidx_prev = idx_goal - 1;idx_pprev = idx_prev - 1;%计算由这三个相邻点构成的三角形的三条边长a = hypot(path(idx_prev, 1) - path(idx_goal, 1), path(idx_prev, 2) - path(idx_goal, 2));b = hypot(path(idx_pprev, 1) - path(idx_goal, 1), path(idx_pprev, 2) - path(idx_goal, 2));c = hypot(path(idx_pprev, 1) - path(idx_prev, 1), path(idx_pprev, 2) - path(idx_prev, 2));cosB = (a * a + c * c - b * b) / (2 * a * c);sinB = sin(acos(cosB));%计算向量叉积来确定曲线的弯曲方向。叉积的正负可以表示曲线是向左弯曲还是向右弯曲cross = (path(idx_prev, 1) - path(idx_pprev, 1)) * ...(path(idx_goal, 2) - path(idx_pprev, 2)) - ...(path(idx_prev, 2) - path(idx_pprev, 2)) * ...(path(idx_goal, 1) - path(idx_pprev, 1));kappa = 2 * sinB / b *sign(cross); %曲率kappa= b/2sinB,sign(cross) 是为了给曲率加上方向信息,正曲率表示曲线向左弯曲,负曲率表示曲线向右弯曲。
endfunction i_points = circleSegmentIntersection(p1, p2, r)x1 = p1(1); x2 = p2(1);y1 = p1(2); y2 = p2(2);dx = x2 - x1; dy = y2 - y1;dr2 = dx * dx + dy * dy;%线段长度的平方D = x1 * y2 - x2 * y1;% the first element is the point within segmentd1 = x1 * x1 + y1 * y1;d2 = x2 * x2 + y2 * y2;dd = d2 - d1;delta = sqrt(r * r * dr2 - D * D);%delta 的值决定了线段与圆的相交情况if delta >= 0if delta == 0%线段与圆相切i_points = [D * dy / dr2, -D * dx / dr2];else%线段与圆有两个交点i_points = [(D * dy + sign(dd) * dx * delta) / dr2, ...(-D * dx + sign(dd) * dy * delta) / dr2; ...(D * dy - sign(dd) * dx * delta) / dr2, ...(-D * dx - sign(dd) * dy * delta) / dr2];%sign(dd) 函数用于返回 dd 的符号(正为 1,负为 -1),用于确定交点的具体位置。endelsei_points = [];end
endfunction path_new = path_interpolation(path, n)for i=1:npath_new = path;path_inter = path_new(1:end - 1, :) + diff(path_new) / 2;path = zeros(length(path_new) + length(path_inter), 2);path(1:2:end, :) = path_new;path(2:2:end, :) = path_inter;end
endfunction [u, u_p_new] = mpcControl(s, s_d, u_r, u_p, robot, param)%{Execute MPC control process.Parameters----------s: tuplecurrent states_d: tupledesired stateu_r: tuplerefered controlu_p: tupleprevious control errorReturn----------u: np.ndarraycontrol vector%}dim_u = 2; dim_x = 3;dt = param.dt;% state vector (5 x 1)x = [s - s_d, u_p]';% original state matrixA = eye(3);A(1, 3) = -u_r(1) * sin(s_d(3)) * dt;A(2, 3) = u_r(1) * cos(s_d(3)) * dt;% original control matrixB = zeros(3, 2);B(1, 1) = cos(s_d(3)) * dt;B(2, 1) = sin(s_d(3)) * dt;B(3, 2) = dt;% discrete iteration Ricatti equationP = param.Q;% state matrix (5 x 5)A = [A, B; zeros(dim_u, dim_x), eye(dim_u)];% control matrix (5 x 2)B = [B; eye(dim_u)];% output matrix (3 x 5)C = [eye(dim_x), zeros(dim_x, dim_u)];% mpc state matrix (3p x 5)S_x_cell = cell(param.p, 1);%预测时域长度。表示 MPC 算法向前预测的步数for i=1:param.pS_x_cell{i, 1} = C * A ^ i;endS_x = cell2mat(S_x_cell);% mpc control matrix (3p x 2m)S_u_cell = cell(param.p, param.m);for i = 1:param.pfor j = 1:param.mif j <= iS_u_cell{i, j} = C * A ^ (i - j) * B;elseS_u_cell{i, j} = zeros(dim_x, dim_u);endendendS_u = cell2mat(S_u_cell);% optimizationYr = zeros(3 * param.p, 1); % (3p x 1)Q = kron(eye(param.p), param.Q); % (3p x 3p)R = kron(eye(param.m), param.R); % (2m x 2m)H = S_u' * Q * S_u + R; % (2m x 2m)g = S_u' * Q * (S_x * x - Yr); % (2m x 1)% constriantsA_I = kron(tril(ones(param.m, param.m)), diag([1, 1]));U_min = kron(ones(param.m, 1), param.u_min);U_max = kron(ones(param.m, 1), param.u_max);U_k_1 = kron(ones(param.m, 1), u_p');% boundarydU_min = kron(ones(param.m, 1), param.du_min);dU_max = kron(ones(param.m, 1), param.du_max);% solveoptions = optimoptions('quadprog', 'MaxIterations', 100, 'TolFun', 1e-16, 'Display','off');dU_opt = quadprog(H, g, [-A_I; A_I], [-U_min + U_k_1; U_max - U_k_1], [], [], dU_min, dU_max, [], options);% first elementdu = [dU_opt(1), dU_opt(2)];% real controlu = du + u_p + u_r;u = [linearRegularization(robot, u(1), param), angularRegularization(robot, u(2), param)];u_p_new = u - u_r;
end
这段代码是一个基于模型预测控制(MPC)的路径跟踪算法,用于控制机器人沿给定路径移动并到达目标点。以下是按代码执行顺序的详细解释:
1. 主函数入口 mpc_plan
function [pose, traj, flag] = mpc_plan(start, goal, varargin)
- 输入参数:
start
: 机器人初始位姿[x, y, theta]
goal
: 目标位姿[x, y, theta]
varargin
: 可选参数(路径path
和地图map
)
- 输出:
pose
: 机器人位姿序列traj
: 轨迹flag
: 是否成功到达目标
2. 参数解析与验证
p = inputParser;
addParameter(p, 'path', "none");
addParameter(p, 'map', "none");
parse(p, varargin{:});if isstring(p.Results.path) || isstring(p.Results.map)throw(MException('MyErr:InvalidInput', 'path/map must be matrix.'));
end
- 功能:检查输入参数是否为有效矩阵(非字符串),否则抛出异常。
- 这段代码的主要功能是对输入参数进行解析和验证,确保
path
和map
参数为有效的非字符串类型,以下是详细解释:
1. 创建输入参数解析器
p = inputParser;
inputParser
是 MATLAB 中用于解析函数输入参数的类。通过创建inputParser
对象p
,可以方便地定义和管理函数的输入参数。这个对象就像是一个参数管理的容器,后续可以向其中添加不同类型的参数。
2. 添加参数到解析器
addParameter(p, 'path', "none");
addParameter(p, 'map', "none");
addParameter
是inputParser
对象的一个方法,用于向解析器中添加参数。- 第一个参数
p
是inputParser
对象,指定要添加参数的解析器。 - 第二个参数是参数的名称,这里分别是
'path'
和'map'
,表示路径和地图信息。 - 第三个参数是参数的默认值,这里都设置为字符串
"none"
。这意味着如果调用函数时没有显式提供path
或map
参数,它们将被默认设置为"none"
。
- 第一个参数
3. 解析输入参数
parse(p, varargin{:});
parse
是inputParser
对象的另一个方法,用于解析输入参数。- 第一个参数
p
是要执行解析操作的inputParser
对象。 varargin
是 MATLAB 中用于处理可变数量输入参数的特殊变量。varargin{:}
表示将varargin
中的所有元素展开作为parse
方法的输入。通过调用parse
方法,解析器会根据之前定义的参数规则,将输入的参数赋值给相应的参数名。
- 第一个参数
4. 验证参数类型
if isstring(p.Results.path) || isstring(p.Results.map)exception = MException('MyErr:InvalidInput', 'parameter `path` or `map` must be set.');throw(exception);
end
isstring
是 MATLAB 的一个函数,用于判断一个变量是否为字符串类型。p.Results.path
和p.Results.map
分别表示解析器解析后得到的path
和map
参数的值。- 如果
path
或map
中的任意一个参数是字符串类型,说明它们可能还是默认值"none"
,没有被正确设置为有效的数据类型,则会执行以下操作:MException
是 MATLAB 中用于创建异常对象的函数。这里创建了一个异常对象exception
,其标识符为'MyErr:InvalidInput'
,错误消息为'parameter
pathor
mapmust be set.'
,表示path
或map
参数必须被正确设置。throw
函数用于抛出异常,当调用该函数时,程序会停止当前的执行流程,并显示异常信息,提示用户输入的参数不符合要求。
3. 路径预处理
path = flipud(p.Results.path); % 反转路径点顺序(假设原路径是终点到起点)
path = path_interpolation(path, 5); % 插值5次,增加路径密度function path_new = path_interpolation(path, n)for i=1:npath_new = path;path_inter = path_new(1:end - 1, :) + diff(path_new) / 2;path = zeros(length(path_new) + length(path_inter), 2);path(1:2:end, :) = path_new;path(2:2:end, :) = path_inter;end
end
path_interpolation
:- 逻辑:每次将线段中点插入路径,例如初始路径长度为N,插值后变为N + (N-1)*2^n。
- 作用:提高路径平滑度,便于后续跟踪。
该函数通过n
次循环,每次在原路径相邻点之间插入中点,从而增加路径的点数,使路径更加平滑。最终返回经过n
次插值后的新路径path_new
。例如,假设原路径有 3 个点,经过 1 次插值后会变成 5 个点(在原有的 2 个间隔中各插入 1 个中点);经过 2 次插值后会进一步增加点数,路径会更加平滑。
函数定义和参数说明
function path_new = path_interpolation(path, n)
- 函数名:
path_interpolation
- 输入参数:
path
:一个二维矩阵,每一行代表路径上一个点的坐标,通常为[x, y]
形式。n
:一个正整数,表示插值的次数。
- 输出参数:
path_new
:经过n
次插值操作后的新路径,同样是一个二维矩阵,行数比原路径更多,路径更加平滑。
每次插值的具体步骤
-
保存当前路径
path_new = path;
在每次插值开始时,将当前的路径
path
赋值给path_new
,方便后续操作。 -
计算路径点之间的中点
path_inter = path_new(1:end - 1, :) + diff(path_new) / 2;
path_new(1:end - 1, :)
:选取path_new
矩阵中除最后一行之外的所有行,也就是路径上除最后一个点之外的所有点。diff(path_new)
:计算path_new
矩阵中相邻行之间的差值,得到相邻点之间的向量。diff(path_new) / 2
:将相邻点之间的向量除以 2,得到中点相对于前一个点的偏移量。path_new(1:end - 1, :) + diff(path_new) / 2
:将偏移量加到前一个点上,得到相邻两点之间的中点坐标。最终path_inter
是一个矩阵,每一行代表相邻两点之间的中点坐标。
-
创建新的路径矩阵
path = zeros(length(path_new) + length(path_inter), 2);
创建一个新的零矩阵
path
,其行数为path_new
的行数加上path_inter
的行数,列数为 2,用于存储插值后的新路径。 -
合并原路径点和中点
path(1:2:end, :) = path_new; path(2:2:end, :) = path_inter;
path(1:2:end, :)
:选取path
矩阵中所有奇数行,将原路径path_new
赋值给这些奇数行。path(2:2:end, :)
:选取path
矩阵中所有偶数行,将计算得到的中点path_inter
赋值给这些偶数行。
4. 机器人状态初始化
robot.x = start(1);
robot.y = start(2);
robot.theta = start(3);
robot.v = 0; % 初始速度为0
robot.w = 0; % 初始角速度为0
5. 参数设置
5.1 控制参数
param.dt = 0.1; % 控制周期0.1秒
param.max_iteration = 2000; % 最大迭代次数
param.goal_dist_tol = 1.0; % 目标距离容差(米)当车辆与目标点的距离小于这个容差时,认为车辆已经到达目标点
param.rotate_tol = 0.5; % 角度容差(弧度)当车辆的朝向与目标朝向的偏差小于这个容差时,认为车辆的朝向已经达到要求。
param.lookahead_time = 1.0; % 前瞻时间,决定前瞻距离d = v*T
param.min_lookahead_dist = 1.0; % 最小前瞻距离
param.max_lookahead_dist = 2.5; % 最大前瞻距离
param.max_v_inc = 0.5; % 线速度最大增量
param.max_v = 1.0; % 线速度上限
param.min_v = 0.0; % 线速度下限
param.max_w_inc = pi/2; % 角速度最大增量
param.max_w = pi/2; % 角速度上限
param.min_w = 0.0; % 角速度下限
5.2 MPC参数
param.Q = diag([1, 1, 1]); % 状态误差权重(x,y,theta权重相同)
param.R = diag([2, 2]); % 控制输入权重(速度和角速度权重为2)
param.p = 12; % 预测时域(未来12步)
param.m = 8; % 控制时域(优化前8步控制输入)
param.u_min = [0; -pi/2]; % 控制输入下限(v≥0,w≥-pi/2)
param.u_max = [1; pi/2]; % 控制输入上限
param.du_min = [0; -pi/2]; % 控制增量下限(v增量≥0,w增量≥-pi/2)
param.du_max = [0.5; pi/2]; % 控制增量上限
6. 主循环
iter = 0;
u_p = [0, 0]; % 前一次控制误差
while iter < param.max_iterationiter = iter + 1;% 终止条件:到达目标点if shouldRotateToGoal([robot.x, robot.y], goal, param)flag = true;break;end% 步骤1:获取前瞻点[lookahead_pt, theta_trj, kappa] = getLookaheadPoint(robot, path, param);% 步骤2:计算角度误差e_theta = regularizeAngle(robot.theta - goal(3)) / 10;% 步骤3:控制决策if shouldRotateToGoal([robot.x, robot.y], goal, param)% 优先旋转到目标点if ~shouldRotateToPath(abs(e_theta), 0.0, param)u = [0, 0];elseu = [0, angularRegularization(robot, e_theta / param.dt, param)];endelse% 计算路径方向误差e_theta = regularizeAngle(atan2(lookahead_pt(2)-robot.y, lookahead_pt(1)-robot.x) - robot.theta) / 10;if shouldRotateToPath(abs(e_theta), pi/4, param)% 优先旋转到路径方向u = [0, angularRegularization(robot, e_theta / param.dt, param)];else% MPC计算控制输入s = [robot.x, robot.y, robot.theta];s_d = [lookahead_pt, theta_trj];u_r = [robot.v, theta_trj - robot.theta];[u, u_p] = mpcControl(s, s_d, u_r, u_p, robot, param);endend% 步骤4:更新机器人状态robot = f(robot, u, param.dt);pose = [pose; robot.x, robot.y, robot.theta];
end
这段代码是 mpc_plan
函数中的主循环部分,主要实现了机器人的路径跟踪和运动控制,通过不断迭代更新机器人的状态,使其朝着目标点移动。以下是对代码的详细解释:
-
初始化变量
iter = 0; u_p = [0, 0]; % 先前的控制误差
iter
:迭代计数器,初始化为 0,用于记录当前的迭代次数。u_p
:先前的控制误差,初始化为[0, 0]
,在后续的 MPC 控制中会不断更新。
-
主循环
while iter < param.max_iterationiter = iter + 1;
while
循环:只要迭代次数iter
小于最大迭代次数param.max_iteration
,就会继续执行循环体中的代码。iter = iter + 1
:每次迭代时,迭代计数器iter
加 1。
-
检查是否到达目标点
if shouldRotateToGoal([robot.x, robot.y], goal, param)flag = true;break; end
shouldRotateToGoal
函数:用于判断机器人当前位置[robot.x, robot.y]
与目标位置goal
之间的距离是否小于目标距离容差param.goal_dist_tol
。如果满足条件,则认为机器人已经接近目标点,需要进行旋转操作以到达目标位姿。flag = true
:将标志变量flag
设置为true
,表示机器人已经到达目标点。break
:跳出while
循环,结束路径跟踪过程。
-
获取前瞻点
[lookahead_pt, theta_trj, kappa] = getLookaheadPoint(robot, path, param);
getLookaheadPoint
函数:根据机器人的当前状态robot
、路径path
和参数param
,计算路径上前瞻距离处的特定点。lookahead_pt
:前瞻点的坐标。theta_trj
:前瞻点处路径的切线角度。kappa
:前瞻点处路径的曲率。
-
计算角度误差
e_theta = regularizeAngle(robot.theta - goal(3)) / 10;
regularizeAngle
函数:将角度归一化到[-π, π]
范围内。robot.theta - goal(3)
:计算机器人当前朝向与目标朝向之间的角度误差。/ 10
:对角度误差进行缩放,以避免过大的控制输入。
-
控制决策
接近目标点时的控制决策if shouldRotateToGoal([robot.x, robot.y], goal, param)if ~shouldRotateToPath(abs(e_theta), 0.0, param)u = [0, 0];elseu = [0, angularRegularization(robot, e_theta / param.dt, param)];end
-
shouldRotateToGoal
函数:再次判断机器人是否接近目标点。 -
shouldRotateToPath
函数:判断机器人当前的朝向与目标朝向之间的角度偏差是否超出了可容忍的范围。- 如果角度偏差小于容差(
~shouldRotateToPath(abs(e_theta), 0.0, param)
),则将控制输入u
设置为[0, 0]
,表示机器人停止运动。 - 否则,将线速度设置为 0,角速度经过规范化处理后赋值给
u
,表示机器人只进行旋转运动。
未接近目标点时的控制决策
elsee_theta = regularizeAngle( ...atan2(real(lookahead_pt(2)) - robot.y, real(lookahead_pt(1)) - robot.x) - robot.theta ...) / 10;if shouldRotateToPath(abs(e_theta), pi / 4, param)u = [0, angularRegularization(robot, e_theta / param.dt, param)];elses = [robot.x, robot.y, robot.theta];s_d = [real(lookahead_pt), theta_trj];u_r = [robot.v, theta_trj - robot.theta];[u, u_p] = mpcControl(s, s_d, u_r, u_p, robot, param);end
- 计算从机器人当前位置到前瞻点的方向角度,减去机器人当前的朝向角度,并进行角度规范化和缩放。
shouldRotateToPath
函数:判断机器人当前的朝向与路径期望朝向之间的角度偏差是否超出了π/4
的范围。- 如果角度偏差大于
π/4
,则将线速度设置为 0,角速度经过规范化处理后赋值给u
,表示机器人只进行旋转运动。 - 否则,使用模型预测控制(MPC)计算控制指令。
s
:机器人的当前状态,包括位置和朝向。s_d
:期望状态,即前瞻点的坐标和切线角度。u_r
:参考输入,包括机器人的当前线速度和期望的角速度变化。mpcControl
函数:根据当前状态、期望状态、参考输入和先前的控制误差,计算最优的控制输入u
和更新后的控制误差u_p
。
- 如果角度偏差大于
- 如果角度偏差小于容差(
-
-
更新机器人状态
robot = f(robot, u, param.dt); pose = [pose; robot.x, robot.y, robot.theta];
f
函数:机器人的运动学模型,根据当前状态robot
、控制输入u
和时间步长param.dt
,更新机器人的状态。pose
:记录机器人的位姿序列,将更新后的机器人位置和朝向添加到pose
矩阵中。
7. 运动学模型 f
function robot = f(robot, u, dt)F = eye(5); % 单位矩阵(前3行对应位置和角度,后两行固定为0)B = [dt*cos(robot.theta) 0;dt*sin(robot.theta) 0;0 dt;1 0;0 1];x = [robot.x; robot.y; robot.theta; robot.v; robot.w];x_star = F * x + B * u';robot.x = x_star(1);robot.y = x_star(2);robot.theta = x_star(3);robot.v = x_star(4);robot.w = x_star(5);
end
该函数接收三个输入参数:
robot
:一个结构体,包含机器人当前的状态信息,如位置(x, y)
、朝向theta
、线速度v
和角速度w
。u
:一个二维向量,包含控制输入,通常为[v_d, w_d]
,其中v_d
是期望的线速度,w_d
是期望的角速度。dt
:时间步长,即两次状态更新之间的时间间隔。
函数返回更新后的机器人状态 robot
。
代码详细解释
-
定义状态转移矩阵
F
F = [ 1 0 0 0 00 1 0 0 00 0 1 0 00 0 0 0 00 0 0 0 0];
矩阵
F
是一个 5 × 5 5\times5 5×5 的矩阵,用于描述机器人状态的自主演化部分。在这个模型中,位置(x, y)
和朝向theta
不受其他状态的直接影响(除了通过控制输入),而线速度v
和角速度w
在这里假设没有自主变化(即不考虑自身的加速度等因素),所以对应位置为 0。 -
定义控制输入矩阵
B
B = [dt * cos(robot.theta) 0dt * sin(robot.theta) 00 dt1 00 1];
矩阵
B
是一个 5 × 2 5\times2 5×2 的矩阵,用于描述控制输入u
对机器人状态的影响。具体分析如下:- 第一行
dt * cos(robot.theta)
:表示线速度输入v_d
对机器人x
坐标的影响。在时间步长dt
内,机器人在x
方向上的位移与线速度和当前朝向的余弦值有关。 - 第二行
dt * sin(robot.theta)
:表示线速度输入v_d
对机器人y
坐标的影响。在时间步长dt
内,机器人在y
方向上的位移与线速度和当前朝向的正弦值有关。 - 第三行
dt
:表示角速度输入w_d
对机器人朝向theta
的影响。在时间步长dt
内,机器人的朝向变化与角速度成正比。 - 第四行
1
:表示线速度输入v_d
直接影响机器人的线速度状态。 - 第五行
1
:表示角速度输入w_d
直接影响机器人的角速度状态。
- 第一行
-
构建状态向量
x
x = [robot.x; robot.y; robot.theta; robot.v; robot.w];
将机器人的当前状态
(x, y, theta, v, w)
组合成一个 5 × 1 5\times1 5×1 的列向量x
。 -
计算更新后的状态向量
x_star
x_star = F * x + B * u';
这是离散时间线性系统的状态更新公式,其中
F * x
表示状态的自主演化部分,B * u'
表示控制输入对状态的影响。 -
更新机器人状态
robot.x = x_star(1); robot.y = x_star(2); robot.theta = x_star(3); robot.v = x_star(4); robot.w = x_star(5);
将更新后的状态向量
x_star
中的元素分别赋值给机器人状态结构体robot
中的对应字段。
数学表达式推导
设机器人在 k k k 时刻的状态向量为 x k = [ x k , y k , θ k , v k , w k ] T \mathbf{x}_k = [x_k, y_k, \theta_k, v_k, w_k]^T xk=[xk,yk,θk,vk,wk]T,控制输入向量为 u k = [ v d k , w d k ] T \mathbf{u}_k = [v_{d_k}, w_{d_k}]^T uk=[vdk,wdk]T,时间步长为 Δ t \Delta t Δt。
则在 k + 1 k + 1 k+1 时刻的状态向量 x k + 1 \mathbf{x}_{k+1} xk+1 可以通过以下公式计算:
x k + 1 = F x k + B u k \mathbf{x}_{k+1} = \mathbf{F} \mathbf{x}_k + \mathbf{B} \mathbf{u}_k xk+1=Fxk+Buk
其中,
F = [ 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 ] \mathbf{F} = \begin{bmatrix} 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 \end{bmatrix} F= 1000001000001000000000000
B = [ Δ t cos ( θ k ) 0 Δ t sin ( θ k ) 0 0 Δ t 1 0 0 1 ] \mathbf{B} = \begin{bmatrix} \Delta t \cos(\theta_k) & 0 \\ \Delta t \sin(\theta_k) & 0 \\ 0 & \Delta t \\ 1 & 0 \\ 0 & 1 \end{bmatrix} B= Δtcos(θk)Δtsin(θk)01000Δt01
具体展开各个状态变量的更新公式如下:
- x k + 1 = x k + Δ t ⋅ v d k cos ( θ k ) x_{k+1} = x_k + \Delta t \cdot v_{d_k} \cos(\theta_k) xk+1=xk+Δt⋅vdkcos(θk)
- y k + 1 = y k + Δ t ⋅ v d k sin ( θ k ) y_{k+1} = y_k + \Delta t \cdot v_{d_k} \sin(\theta_k) yk+1=yk+Δt⋅vdksin(θk)
- θ k + 1 = θ k + Δ t ⋅ w d k \theta_{k+1} = \theta_k + \Delta t \cdot w_{d_k} θk+1=θk+Δt⋅wdk
- v k + 1 = v d k v_{k+1} = v_{d_k} vk+1=vdk
- w k + 1 = w d k w_{k+1} = w_{d_k} wk+1=wdk
这些公式描述了在一个时间步长 Δ t \Delta t Δt 内,机器人的位置、朝向、线速度和角速度如何根据当前状态和控制输入进行更新。
8. 角度归一化 regularizeAngle
function theta = regularizeAngle(angle)theta = angle - 2*pi*floor((angle + pi)/(2*pi));
end
regularizeAngle
函数的主要作用是将输入的角度 angle
规范化到 ( − π , π ] (-\pi, \pi] (−π,π] 的范围内。在机器人运动控制、导航等领域中,角度的表示通常需要保持在一个标准的范围内,这样可以避免因角度值过大或过小而导致的计算误差和逻辑混乱。这个函数就实现了这样的角度规范化功能。
-
(angle + pi)
:首先将输入的角度angle
加上 π \pi π。这一步的目的是将角度的范围从原来的 ( − ∞ , + ∞ ) (-\infty, +\infty) (−∞,+∞) 平移到一个便于后续处理的区间,使得后续的取整操作能够正确地将角度映射到 ( − π , π ] (-\pi, \pi] (−π,π] 范围内。 -
(angle + pi)/(2*pi)
:将上一步得到的结果除以 2 π 2\pi 2π。因为 2 π 2\pi 2π 是一个完整的圆周角度,这样做的目的是计算输入角度相对于一个完整圆周的倍数。 -
floor((angle + pi)/(2*pi))
:使用floor
函数对上述结果进行向下取整。floor
函数会返回不大于输入值的最大整数,这样就得到了输入角度包含的完整圆周的数量。 -
2*pi*floor((angle + pi)/(2*pi))
:将取整后的结果乘以 2 π 2\pi 2π,得到输入角度中包含的完整圆周的总角度。 -
angle - 2*pi*floor((angle + pi)/(2*pi))
:最后,用输入角度angle
减去完整圆周的总角度,就得到了一个位于 ( − π , π ] (-\pi, \pi] (−π,π] 范围内的规范化角度。
示例说明
-
假设输入角度
angle = 3*pi
,则:(angle + pi) = 4*pi
(angle + pi)/(2*pi) = 2
floor((angle + pi)/(2*pi)) = 2
2*pi*floor((angle + pi)/(2*pi)) = 4*pi
theta = angle - 2*pi*floor((angle + pi)/(2*pi)) = 3*pi - 4*pi = -pi
-
假设输入角度
angle = -3*pi/2
,则:(angle + pi) = -pi/2
(angle + pi)/(2*pi) = -1/4
floor((angle + pi)/(2*pi)) = -1
2*pi*floor((angle + pi)/(2*pi)) = -2*pi
theta = angle - 2*pi*floor((angle + pi)/(2*pi)) = -3*pi/2 - (-2*pi) = pi/2
通过这样的计算,无论输入的角度是多少,最终都能将其规范化到 ( − π , π ] (-\pi, \pi] (−π,π] 的范围内。
9. 终止条件判断 shouldRotateToGoal
function flag = shouldRotateToGoal(cur, goal, param)flag = hypot(cur(1)-goal(1), cur(2)-goal(2)) < param.goal_dist_tol;
end
代码详细解释
function flag = shouldRotateToGoal(cur, goal, param)
- 此函数名为
shouldRotateToGoal
,接收三个输入参数:cur
:这是一个包含两个元素的向量,通常表示机器人当前的二维位置[x, y]
。goal
:同样是一个包含两个元素的向量,代表目标位置[x, y]
。param
:这是一个结构体,包含了各种参数,其中param.goal_dist_tol
表示目标距离容差,即当机器人与目标位置的距离小于该容差值时,认为机器人已经接近目标位置。
- 函数返回一个布尔值
flag
,用于指示是否需要执行旋转操作。
hypot(cur(1)-goal(1), cur(2)-goal(2))
hypot
是 MATLAB 中的一个函数,用于计算两个数的平方和的平方根,也就是欧几里得距离。cur(1)-goal(1)
计算的是机器人当前位置的x
坐标与目标位置的x
坐标之间的差值。cur(2)-goal(2)
计算的是机器人当前位置的y
坐标与目标位置的y
坐标之间的差值。hypot(cur(1)-goal(1), cur(2)-goal(2))
最终得到的是机器人当前位置与目标位置之间的欧几里得距离。
flag = hypot(cur(1)-goal(1), cur(2)-goal(2)) < param.goal_dist_tol;
- 这行代码将计算得到的欧几里得距离与目标距离容差
param.goal_dist_tol
进行比较。 - 如果欧几里得距离小于目标距离容差,说明机器人已经足够接近目标位置,此时
flag
被赋值为true
,表示需要执行旋转操作以调整机器人的朝向,使其达到目标位姿。 - 如果欧几里得距离大于或等于目标距离容差,说明机器人还没有接近目标位置,
flag
被赋值为false
,表示暂时不需要执行旋转操作,机器人可能需要继续向目标位置移动。
10. 角度偏差判断 shouldRotateToPath
function flag = shouldRotateToPath(angle_to_path, tol, param)if tol == 0.0flag = angle_to_path > param.rotate_tol;elseflag = angle_to_path > tol;end
end
shouldRotateToPath
函数的主要作用是判断机器人是否需要通过旋转操作来纠正其跟踪路径,即判断机器人当前的朝向与路径期望朝向之间的角度偏差是否超出了可容忍的范围。根据传入的容差参数 tol
的不同,使用不同的判断标准来确定是否需要进行旋转。
-
函数定义
function flag = shouldRotateToPath(angle_to_path, tol, param)
- 该函数名为
shouldRotateToPath
,接收三个输入参数:angle_to_path
:一个浮点数,表示机器人当前朝向与路径期望朝向之间的角度偏差(单位通常为弧度)。tol
:一个浮点数,是角度偏差的容差。它可以根据具体情况进行设置,如果传入0.0
,则会使用结构体param
中的rotate_tol
作为容差;如果传入其他值,则直接使用该值作为容差。param
:一个结构体,其中包含了各种控制参数,这里主要使用param.rotate_tol
,它表示默认的旋转容差。
- 函数返回一个布尔值
flag
,用于指示机器人是否应该执行旋转操作。
- 该函数名为
-
判断逻辑
if tol == 0.0flag = angle_to_path > param.rotate_tol; elseflag = angle_to_path > tol; end
if tol == 0.0
:检查传入的容差tol
是否为0.0
。- 如果
tol
等于0.0
:使用结构体param
中的rotate_tol
作为角度偏差的容差。将angle_to_path
与param.rotate_tol
进行比较,如果angle_to_path
大于param.rotate_tol
,则将flag
赋值为true
,表示机器人需要执行旋转操作来纠正朝向;否则,将flag
赋值为false
,表示不需要旋转。 - 如果
tol
不等于0.0
:直接使用传入的tol
作为角度偏差的容差。将angle_to_path
与tol
进行比较,如果angle_to_path
大于tol
,则将flag
赋值为true
;否则,将flag
赋值为false
。
- 如果
11. 角速度约束 angularRegularization
function w = angularRegularization(robot, w_d, param)w_inc = w_d - robot.w;if abs(w_inc) > param.max_w_incw_inc = param.max_w_inc * sign(w_inc);endw = robot.w + w_inc;if abs(w) > param.max_w, w = param.max_w * sign(w); endif abs(w) < param.min_w, w = param.min_w * sign(w); end
end
- 约束:
- 角速度变化量
Δw ≤ pi/2
。 - 角速度范围
w ∈ [-pi/2, pi/2]
。
函数的主要作用是对机器人的角速度进行正则化处理,确保角速度及其变化量在设定的允许范围内。在机器人运动控制中,为了保证机器人的安全和稳定运行,需要限制角速度的变化幅度以及角速度的取值范围,该函数就是实现这一功能的。
- 角速度变化量
12. 线速度约束 linearRegularization
function v = linearRegularization(robot, v_d, param)v_inc = v_d - robot.v;if abs(v_inc) > param.max_v_incv_inc = param.max_v_inc * sign(v_inc);endv = robot.v + v_inc;if abs(v) > param.max_v, v = param.max_v * sign(v); endif abs(v) < param.min_v, v = param.min_v * sign(v); end
end
- 约束:
- 线速度变化量
Δv ≤ 0.5
。 - 线速度范围
v ∈ [0, 1]
。
函数的主要作用是对机器人的线速度进行正则化处理,确保线速度及其变化量在设定的允许范围内。在机器人运动控制场景中,为了保障机器人运行的安全性和稳定性,需要对其线速度的变化幅度以及线速度的取值范围进行限制,此函数就实现了这样的功能。
- 线速度变化量
13. 前瞻距离计算 getLookaheadDistance
function d = getLookaheadDistance(robot, param)d = robot.v * param.lookahead_time;d = max(min(d, param.max_lookahead_dist), param.min_lookahead_dist);
end
- 逻辑:根据当前线速度和前瞻时间动态调整前瞻距离,确保在
[1, 2.5]
米范围内。
14. 路径前瞻点计算 getLookaheadPoint
function [pt, theta, kappa] = getLookaheadPoint(robot, path, param)
步骤:
- 搜索路径点:
- 计算所有路径点到机器人的距离,找到第一个≥前瞻距离的点。
- 线段与圆交点:
- 若路径点不足,取最后一个点;否则计算线段与圆的交点。
- 角度与曲率计算:
- 切线角度
theta
:由路径线段方向确定。 - 曲率
kappa
:通过三点法计算,符号由叉积决定(正负表示弯曲方向)。
- 切线角度
getLookaheadPoint
函数的主要作用是在给定的路径 path
上找到距离机器人当前位置恰好为前瞻距离 lookahead_dist
的点,同时计算该点所在路径线段的切线角度 theta
以及路径在该点附近的曲率 kappa
。这些信息对于机器人的路径跟踪和运动控制非常重要,例如可以帮助机器人确定下一步的行驶方向和速度。
-
函数定义与参数说明
function [pt, theta, kappa] = getLookaheadPoint(robot, path, param)
- 函数接收三个输入参数:
robot
:一个结构体,包含机器人的当前状态信息,如robot.x
和robot.y
表示机器人当前的位置坐标。path
:一个二维矩阵,每一行代表路径上一个点的坐标[x, y]
。param
:一个结构体,包含各种控制参数,其中getLookaheadDistance
函数会使用param
中的参数来计算前瞻距离。
- 函数返回三个输出:
pt
:一个二维向量,表示找到的前瞻点在全局坐标系下的坐标[x, y]
。theta
:一个浮点数,表示前瞻点所在路径线段的切线角度(单位为弧度)。kappa
:一个浮点数,表示路径在该点附近的曲率,正曲率表示曲线向左弯曲,负曲率表示曲线向右弯曲。
- 函数接收三个输入参数:
-
计算路径上各点到机器人的距离
dist_to_robot = []; [pts_num, ~] = size(path); for i=1:pts_numdist_to_robot(end + 1) = hypot(path(i, 1) - robot.x, path(i, 2) - robot.y); end
- 初始化一个空数组
dist_to_robot
,用于存储路径上每个点到机器人当前位置的欧几里得距离。 - 使用
size
函数获取路径path
的行数pts_num
,即路径上点的数量。 - 通过
for
循环遍历路径上的每个点,使用hypot
函数计算每个点到机器人当前位置的欧几里得距离,并将结果添加到dist_to_robot
数组中。
- 初始化一个空数组
-
找到距离机器人最近的点
[~, idx_closest] = min(dist_to_robot);
- 使用
min
函数找到dist_to_robot
数组中的最小值及其对应的索引idx_closest
,该索引表示路径上距离机器人最近的点的编号。
- 使用
-
初始化目标点和前一个点的索引
idx_goal = pts_num - 1; idx_prev = idx_goal - 1;
- 初始化目标点的索引
idx_goal
为路径上倒数第二个点的编号,前一个点的索引idx_prev
为倒数第三个点的编号。
- 初始化目标点的索引
-
计算前瞻距离
lookahead_dist = getLookaheadDistance(robot, param);
- 调用
getLookaheadDistance
函数,根据机器人的当前状态robot
和参数param
计算前瞻距离。
- 调用
-
找到第一个距离机器人大于等于前瞻距离的点
for i=idx_closest:pts_numif hypot(path(i, 1) - robot.x, path(i, 2) - robot.y) >= lookahead_distidx_goal = i;break;end end
- 从距离机器人最近的点开始,向后遍历路径上的点。
- 使用
hypot
函数计算当前点到机器人的距离,如果该距离大于等于前瞻距离,则将该点的索引赋值给idx_goal
,并跳出循环。
-
确定前瞻点的坐标
if idx_goal == pts_num - 1pt = [path(idx_goal, 1), path(idx_goal, 2)]; elseif idx_goal == 1idx_goal = idx_goal + 1;endidx_prev = idx_goal - 1;px = path(idx_prev, 1); py = path(idx_prev, 2);gx = path(idx_goal, 1); gy = path(idx_goal, 2);prev_p = [px - robot.x, py - robot.y];goal_p = [gx - robot.x, gy - robot.y];i_points = circleSegmentIntersection(prev_p, goal_p, lookahead_dist);pt = [i_points(1, 1) + robot.x, i_points(1, 2) + robot.y]; end
- 情况一:未找到距离大于等于前瞻距离的点:如果
idx_goal
仍然是路径上倒数第二个点的编号,说明路径上所有点距离机器人都不够远,此时将路径上倒数第二个点作为前瞻点。 - 情况二:找到距离大于等于前瞻距离的点:
- 确保
idx_goal
不小于 2,如果idx_goal
等于 1,则将其加 1。 - 获取目标点和前一个点的坐标
px, py
和gx, gy
。 - 将这两个点的坐标从全局坐标系转换到以机器人位置为原点的局部坐标系,得到
prev_p
和goal_p
。 - 调用
circleSegmentIntersection
函数,计算以机器人位置为圆心、前瞻距离为半径的圆与路径上目标点和前一个点所构成线段的交点。 - 将交点的坐标从局部坐标系转换回全局坐标系,得到前瞻点的坐标
pt
。
- 确保
- 情况一:未找到距离大于等于前瞻距离的点:如果
-
计算路径上该线段的切线角度
theta = atan2(path(idx_goal, 2) - path(idx_prev, 2), path(idx_goal, 1) - path(idx_prev, 1));
- 使用
atan2
函数计算路径上目标点和前一个点所构成线段的切线角度theta
,该角度表示路径在该线段处的方向。
- 计算路径在该点附近的曲率
if idx_goal == 2idx_goal = idx_goal + 1; end idx_prev = idx_goal - 1; idx_pprev = idx_prev - 1; a = hypot(path(idx_prev, 1) - path(idx_goal, 1), path(idx_prev, 2) - path(idx_goal, 2)); b = hypot(path(idx_pprev, 1) - path(idx_goal, 1), path(idx_pprev, 2) - path(idx_goal, 2)); c = hypot(path(idx_pprev, 1) - path(idx_prev, 1), path(idx_pprev, 2) - path(idx_prev, 2)); cosB = (a * a + c * c - b * b) / (2 * a * c); sinB = sin(acos(cosB)); cross = (path(idx_prev, 1) - path(idx_pprev, 1)) * ...(path(idx_goal, 2) - path(idx_pprev, 2)) - ...(path(idx_prev, 2) - path(idx_pprev, 2)) * ...(path(idx_goal, 1) - path(idx_pprev, 1)); kappa = 2 * sinB / b * sign(cross);
- 确保
idx_goal
不小于 3,如果idx_goal
等于 2,则将其加 1。 - 获取目标点、前一个点和前前一个点的索引
idx_goal
、idx_prev
和idx_pprev
。 - 使用
hypot
函数计算由这三个点构成的三角形的三条边长a
、b
和c
。 - 根据余弦定理计算角
B
的余弦值cosB
,再使用acos
函数计算其反余弦值得到角B
的弧度值,最后使用sin
函数计算其正弦值sinB
。 - 计算向量叉积
cross
,用于确定曲线的弯曲方向。 - 根据公式
kappa = 2 * sinB / b * sign(cross)
计算路径在该点附近的曲率kappa
,其中sign(cross)
用于给曲率加上方向信息,正曲率表示曲线向左弯曲,负曲率表示曲线向右弯曲。
- 确保
15. 线段与圆交点 circleSegmentIntersection
function i_points = circleSegmentIntersection(p1, p2, r)
数学原理:
- 线段方程:
(x-x1)(y2-y1) = (y-y1)(x2-x1)
。 - 圆方程:
x² + y² = r²
。 - 联立方程求解交点,考虑相切、相交和不相交三种情况。
circleSegmentIntersection
函数的主要功能是计算以原点 (0, 0)
为圆心、半径为 r
的圆与由点 p1
和 p2
所构成的线段的交点。函数返回一个矩阵 i_points
,其中包含了所有的交点坐标。如果线段与圆没有交点,i_points
为空矩阵。
-
函数定义与参数说明
function i_points = circleSegmentIntersection(p1, p2, r)
- 函数接收三个输入参数:
p1
:一个二维向量,表示线段的起点坐标[x1, y1]
。p2
:一个二维向量,表示线段的终点坐标[x2, y2]
。r
:一个浮点数,表示圆的半径。
- 函数返回一个矩阵
i_points
,每一行代表一个交点的坐标[x, y]
。
- 函数接收三个输入参数:
-
提取点的坐标并计算相关参数
x1 = p1(1); x2 = p2(1); y1 = p1(2); y2 = p2(2);dx = x2 - x1; dy = y2 - y1; dr2 = dx * dx + dy * dy; D = x1 * y2 - x2 * y1;
- 提取
p1
和p2
的x
和y
坐标。 - 计算线段在
x
和y
方向上的增量dx
和dy
。 - 计算线段长度的平方
dr2
,即 d x 2 + d y 2 dx^2 + dy^2 dx2+dy2。 - 计算
D
,它是一个中间变量,在后续计算交点坐标时会用到,其几何意义与线段和原点所构成的三角形面积有关(D
的绝对值的一半就是该三角形的面积)。
- 提取
-
计算其他中间变量
d1 = x1 * x1 + y1 * y1; d2 = x2 * x2 + y2 * y2; dd = d2 - d1;
- 计算点
p1
到原点的距离的平方d1
和点p2
到原点的距离的平方d2
。 - 计算
dd
,即d2 - d1
,用于后续判断交点的具体位置。
- 计算点
-
计算判别式
delta
delta = sqrt(r * r * dr2 - D * D);
delta
是一个判别式,用于判断线段与圆的相交情况。其数学原理基于圆和直线的方程联立求解。圆的方程为 x 2 + y 2 = r 2 x^2 + y^2 = r^2 x2+y2=r2,直线的方程可以表示为 y = ( d y / d x ) ∗ ( x − x 1 ) + y 1 y = (dy/dx) * (x - x1) + y1 y=(dy/dx)∗(x−x1)+y1(当dx != 0
时),将直线方程代入圆的方程,经过整理可以得到一个关于x
的二次方程 A x 2 + B x + C = 0 Ax^2 + Bx + C = 0 Ax2+Bx+C=0,其中判别式 D e l t a = B 2 − 4 A C Delta = B^2 - 4AC Delta=B2−4AC。这里的delta
是经过简化和变形后的判别式,其值决定了线段与圆的相交情况:- 当
delta < 0
时,二次方程无实数解,说明线段与圆没有交点。 - 当
delta = 0
时,二次方程有一个实数解,说明线段与圆相切。 - 当
delta > 0
时,二次方程有两个实数解,说明线段与圆有两个交点。
- 当
-
根据
delta
的值计算交点坐标if delta >= 0if delta == 0i_points = [D * dy / dr2, -D * dx / dr2];elsei_points = [(D * dy + sign(dd) * dx * delta) / dr2, ...(-D * dx + sign(dd) * dy * delta) / dr2; ...(D * dy - sign(dd) * dx * delta) / dr2, ...(-D * dx - sign(dd) * dy * delta) / dr2];end elsei_points = []; end
delta < 0
的情况:线段与圆没有交点,将i_points
赋值为空矩阵。delta = 0
的情况:线段与圆相切,根据推导得到的公式计算切点的坐标[D * dy / dr2, -D * dx / dr2]
。delta > 0
的情况:线段与圆有两个交点,根据推导得到的公式计算两个交点的坐标。sign(dd)
函数用于返回dd
的符号(正为 1,负为 -1),用于确定交点的具体位置。
16. MPC控制核心 mpcControl
function [u, u_p_new] = mpcControl(s, s_d, u_r, u_p, robot, param)%{Execute MPC control process.Parameters----------s: tuplecurrent states_d: tupledesired stateu_r: tuplerefered controlu_p: tupleprevious control errorReturn----------u: np.ndarraycontrol vector%}dim_u = 2; dim_x = 3;dt = param.dt;% state vector (5 x 1)x = [s - s_d, u_p]';% original state matrixA = eye(3);A(1, 3) = -u_r(1) * sin(s_d(3)) * dt;A(2, 3) = u_r(1) * cos(s_d(3)) * dt;% original control matrixB = zeros(3, 2);B(1, 1) = cos(s_d(3)) * dt;B(2, 1) = sin(s_d(3)) * dt;B(3, 2) = dt;% discrete iteration Ricatti equationP = param.Q;% state matrix (5 x 5)A = [A, B; zeros(dim_u, dim_x), eye(dim_u)];% control matrix (5 x 2)B = [B; eye(dim_u)];% output matrix (3 x 5)C = [eye(dim_x), zeros(dim_x, dim_u)];% mpc state matrix (3p x 5)S_x_cell = cell(param.p, 1);%预测时域长度。表示 MPC 算法向前预测的步数for i=1:param.pS_x_cell{i, 1} = C * A ^ i;endS_x = cell2mat(S_x_cell);% mpc control matrix (3p x 2m)S_u_cell = cell(param.p, param.m);for i = 1:param.pfor j = 1:param.mif j <= iS_u_cell{i, j} = C * A ^ (i - j) * B;elseS_u_cell{i, j} = zeros(dim_x, dim_u);endendendS_u = cell2mat(S_u_cell);% optimizationYr = zeros(3 * param.p, 1); % (3p x 1)Q = kron(eye(param.p), param.Q); % (3p x 3p)R = kron(eye(param.m), param.R); % (2m x 2m)H = S_u' * Q * S_u + R; % (2m x 2m)g = S_u' * Q * (S_x * x - Yr); % (2m x 1)% constriantsA_I = kron(tril(ones(param.m, param.m)), diag([1, 1]));U_min = kron(ones(param.m, 1), param.u_min);U_max = kron(ones(param.m, 1), param.u_max);U_k_1 = kron(ones(param.m, 1), u_p');% boundarydU_min = kron(ones(param.m, 1), param.du_min);dU_max = kron(ones(param.m, 1), param.du_max);% solveoptions = optimoptions('quadprog', 'MaxIterations', 100, 'TolFun', 1e-16, 'Display','off');dU_opt = quadprog(H, g, [-A_I; A_I], [-U_min + U_k_1; U_max - U_k_1], [], [], dU_min, dU_max, [], options);% first elementdu = [dU_opt(1), dU_opt(2)];% real controlu = du + u_p + u_r;u = [linearRegularization(robot, u(1), param), angularRegularization(robot, u(2), param)];u_p_new = u - u_r;
end
mpcControl
函数实现了模型预测控制(MPC)的核心算法,用于计算机器人的最优控制输入 u
,使其在约束条件下跟踪期望状态 s_d
。该函数通过构建状态增广模型、预测矩阵、优化目标和约束条件,求解二次规划问题得到控制增量,并最终生成实际控制输入。
数学推导与矩阵分析
1. 状态增广与误差动态
状态向量:
x = [s - s_d; u_p]'; % 增广状态向量(5×1)
- 状态误差:
s - s_d
(位置、角度误差)。 - 控制误差:
u_p = u_{k-1} - u_r
(前一次控制输入与参考输入的偏差)。
增广的目的:
- 将控制误差作为状态的一部分,引入积分作用,消除稳态误差。
- 将系统转换为线性时变(LTV)模型,便于MPC优化。
2. 状态转移矩阵 A
与控制矩阵 B
原状态矩阵 A
(3×3):
A = eye(3);
A(1, 3) = -u_r(1) * sin(s_d(3)) * dt;
A(2, 3) = u_r(1) * cos(s_d(3)) * dt;
- 物理意义:在参考输入
u_r
附近线性化,考虑参考线速度对角度误差的影响。 - 数学推导:
- 原连续时间模型:
e ˙ x = v r cos ( θ d ) − v r sin ( θ d ) e θ e ˙ y = v r sin ( θ d ) + v r cos ( θ d ) e θ e ˙ θ = ω r − ω \dot{e}_x = v_r \cos(\theta_d) - v_r \sin(\theta_d) e_{\theta} \\ \dot{e}_y = v_r \sin(\theta_d) + v_r \cos(\theta_d) e_{\theta} \\ \dot{e}_{\theta} = \omega_r - \omega e˙x=vrcos(θd)−vrsin(θd)eθe˙y=vrsin(θd)+vrcos(θd)eθe˙θ=ωr−ω - 离散化后得到
A
的元素(线性化忽略高阶项)。
- 原连续时间模型:
原控制矩阵 B
(3×2):
B(1, 1) = cos(s_d(3)) * dt;
B(2, 1) = sin(s_d(3)) * dt;
B(3, 2) = dt;
- 物理意义:控制输入
u = [v, omega]
对状态误差的影响。 - 数学推导:
e x [ k + 1 ] = e x [ k ] + v cos ( θ d ) Δ t e y [ k + 1 ] = e y [ k ] + v sin ( θ d ) Δ t e θ [ k + 1 ] = e θ [ k ] + ω Δ t e_x[k+1] = e_x[k] + v \cos(\theta_d) \Delta t \\ e_y[k+1] = e_y[k] + v \sin(\theta_d) \Delta t \\ e_{\theta}[k+1] = e_{\theta}[k] + \omega \Delta t ex[k+1]=ex[k]+vcos(θd)Δtey[k+1]=ey[k]+vsin(θd)Δteθ[k+1]=eθ[k]+ωΔt
增广后的矩阵:
A = [A, B; zeros(2,3), eye(2)]; % 5×5
B = [B; eye(2)]; % 5×2
- 状态转移方程:
x k + 1 = A x k + B Δ u k \mathbf{x}_{k+1} = A \mathbf{x}_k + B \Delta u_k xk+1=Axk+BΔuk
其中,Δu_k = u_k - u_r
为控制增量。
3. 预测模型矩阵 S_x
和 S_u
预测时域 p
:预测未来 p
步的状态响应。
控制时域 m
:优化前 m
步的控制增量 Δu
。
预测矩阵构造:
S_x = C * A^i % 3p×5
S_u = C * A^{i-j} * B % 3p×2m
- 物理意义:
S_x
:初始状态对未来p
步状态的影响。S_u
:控制增量对未来p
步状态的影响。
- 数学公式:
y = S x x 0 + S u Δ U \mathbf{y} = S_x \mathbf{x}_0 + S_u \Delta \mathbf{U} y=Sxx0+SuΔU
其中, y \mathbf{y} y 是预测状态误差向量。
4. 优化问题
目标函数:
m i n 1 2 Δ U T H Δ U + g T Δ U min \frac{1}{2} \Delta U^T H \Delta U + g^T \Delta U min21ΔUTHΔU+gTΔU
- Hessian矩阵
H
:
H = S u T Q S u + R H = S_u^T Q S_u + R H=SuTQSu+RQ = kron(eye(p), param.Q)
:状态误差权重矩阵(分块对角,每块为Q
)。R = kron(eye(m), param.R)
:控制增量权重矩阵(分块对角,每块为R
)。
- 梯度向量
g
:
g = S u T Q ( S x x 0 − Y r ) g = S_u^T Q (S_x \mathbf{x}_0 - Y_r) g=SuTQ(Sxx0−Yr)Y_r = 0
:期望的预测状态误差为零。
约束条件:
-
控制输入约束:
U min ≤ U k ≤ U max U_{\text{min}} \leq U_k \leq U_{\text{max}} Umin≤Uk≤Umax- 矩阵形式:
A I Δ U ≤ U max − U k − 1 ; A I Δ U ≥ U min − U k − 1 A_I \Delta U \leq U_{\text{max}} - U_{k-1} ; A_I \Delta U \geq U_{\text{min}} - U_{k-1} AIΔU≤Umax−Uk−1;AIΔU≥Umin−Uk−1
A I = k r o n ( t r i l ( o n e s ( m ) ) , d i a g ( [ 1 , 1 ] ) ) A_I = kron(tril(ones(m)), diag([1,1])) AI=kron(tril(ones(m)),diag([1,1])):下三角矩阵,确保控制增量的递推关系。
- 矩阵形式:
-
控制增量约束:
Δ U min ≤ Δ U ≤ Δ U max \Delta U_{\text{min}} \leq \Delta U \leq \Delta U_{\text{max}} ΔUmin≤ΔU≤ΔUmax
5. 二次规划求解
dU_opt = quadprog(H, g, [-A_I; A_I], [-U_min + U_k_1; U_max - U_k_1], [], [], dU_min, dU_max);
- 输入约束矩阵:
[ − A I A I ] Δ U ≤ [ − U min + U k − 1 U max − U k − 1 ] \begin{bmatrix} -A_I \\ A_I \end{bmatrix} \Delta U \leq \begin{bmatrix} -U_{\text{min}} + U_{k-1} \\ U_{\text{max}} - U_{k-1} \end{bmatrix} [−AIAI]ΔU≤[−Umin+Uk−1Umax−Uk−1] - 增量约束:
Δ U min ≤ Δ U ≤ Δ U max \Delta U_{\text{min}} \leq \Delta U \leq \Delta U_{\text{max}} ΔUmin≤ΔU≤ΔUmax
6. 控制输入计算
u = du + u_p + u_r;
u = [linearRegularization(...), angularRegularization(...)];
- 实际控制输入:
u k = Δ u 0 + u k − 1 + u r u_k = \Delta u_0 + u_{k-1} + u_r uk=Δu0+uk−1+ur- Δ u 0 \Delta u_0 Δu0:优化得到的第一个控制增量。
u_p_new = u - u_r
:更新后的控制误差。
关键矩阵与参数总结
矩阵/参数 | 物理意义 |
---|---|
A , B | 增广状态转移矩阵和控制矩阵 |
S_x , S_u | 预测模型矩阵,描述状态和控制对未来的影响 |
Q , R | 状态误差和控制增量的权重矩阵 |
A_I | 控制输入约束矩阵(下三角结构确保增量递推) |
U_min , U_max | 控制输入的上下限 |
dU_min , dU_max | 控制增量的变化范围 |
数学公式总结
-
增广状态方程:
x k + 1 = A x k + B Δ u k \mathbf{x}_{k+1} = A \mathbf{x}_k + B \Delta u_k xk+1=Axk+BΔuk -
预测模型:
y = S x x 0 + S u Δ U \mathbf{y} = S_x \mathbf{x}_0 + S_u \Delta \mathbf{U} y=Sxx0+SuΔU -
优化问题:
min Δ U 1 2 Δ U T H Δ U + g T Δ U s.t. A I Δ U ≤ U max − U k − 1 , Δ U min ≤ Δ U ≤ Δ U max \min_{\Delta \mathbf{U}} \frac{1}{2} \Delta \mathbf{U}^T H \Delta \mathbf{U} + g^T \Delta \mathbf{U} \\ \text{s.t.} \quad A_I \Delta \mathbf{U} \leq U_{\text{max}} - U_{k-1}, \quad \Delta U_{\text{min}} \leq \Delta \mathbf{U} \leq \Delta U_{\text{max}} ΔUmin21ΔUTHΔU+gTΔUs.t.AIΔU≤Umax−Uk−1,ΔUmin≤ΔU≤ΔUmax -
控制输入更新:
u k = Δ u 0 + u k − 1 + u r u_k = \Delta u_0 + u_{k-1} + u_r uk=Δu0+uk−1+ur
代码流程总结
- 状态增广:将状态误差和控制误差组合为增广状态。
- 模型线性化:在参考输入附近构建状态转移矩阵
A
和控制矩阵B
。 - 预测矩阵生成:计算预测时域内的状态和控制影响矩阵。
- 优化求解:通过二次规划计算最优控制增量。
- 约束处理:确保控制输入在物理限制范围内。
通过MPC框架,在每个控制周期内求解最优控制输入,实现机器人的动态路径跟踪与约束满足。
17. 算法流程总结
- 路径预处理:反转并插值路径。
- 主循环:
- 终止条件:到达目标点。
- 前瞻点计算:动态确定路径上的跟踪点。
- 控制决策:
- 若角度误差大,优先旋转。
- 否则,使用MPC计算最优控制输入。
- 状态更新:通过运动学模型更新机器人位姿。
- 输出结果:记录机器人位姿序列
pose
。
关键参数对性能的影响
Q
和R
:Q
增大:更注重状态跟踪精度。R
增大:更注重控制输入平滑性。
p
和m
:p
增大:预测更长远,但计算量增加。m
增大:优化更多控制输入,响应更灵活。
- 前瞻距离:
- 增大:稳定性提高,但响应速度降低。
- 减小:响应更快,但可能震荡。
相关文章:
MPC算法路径跟踪_Matlab实现
在机器人控制领域,模型预测控制(MPC)因其能够处理动态约束和多目标优化的特性,成为路径跟踪的热门方案。近期,我在 GitHub 上发现了 Mr.Winter 的MPC路径规划项目,其代码实现简洁且功能完整。本文将结合理论…...
QT Quick(C++)跨平台应用程序项目实战教程 2 — 环境搭建和项目创建
目录 引言 1. 安装Qt开发环境 1.1 下载Qt安装包 1.2 安装Qt 1.3 安装Visual Studio 2022 1.4 在Visual Studio 2022中安装Qt插件 1.5 在Visual Studio 2022中安装大模型编程助手 2. 创建Qt Quick项目 2.1 创建新项目 2.2 项目结构 2.3 运行项目 3. 理解项目代码 3…...
洛科威多功能岩棉板为环保助力,推动企业绿色可持续发展
在当今全球环保意识日益增强的背景下,企业工程项目在追求高效益的同时,也更加注重绿色可持续发展。作为建筑材料领域的佼佼者,洛科威公司推出的多功能岩棉板凭借其卓越的绿色环保特性,正逐渐成为企业工程项目领域的首选材料。 洛科…...
7.3《重力》
教会什么:重力及其三要素、重力加速度g、 培养什么:从力的三要素出发去研究一个力,用所学探究未知 课标: (二)运动和相互作用 2.2 机械运动和力 2.2.3 通过常见事例或实验,了解重力,认识力的作用效果。 (四)实验探究 4.1.6 用弹测力计测量力。 例6 测量一本物理教科书…...
虚幻基础:ue自定义类
文章目录 Gameplay Tag:ue标签类创建:其他-数据表格-gameplaytag安装:项目设置:gamePlayTag:gamePlay标签列表使用:变量类型:gamePlayTag primary data asset:ue数据类:通…...
88页手册上线 | 企业级本地私有化DeepSeek实战指南
DeepSeek为普通企业在低成本、高性能、安全可控的前提下私有化部署AI大模型提供了可行路径。 云轴科技ZStack全新推出《企业级本地私有化DeepSeek实战手册》(点击免费下载),直击企业痛点,从7B轻量化模型到671B超大规模部署&#…...
Godot读取json配置文件
概述 在Godot 4.3中读取JSON配置文件,可以通过以下步骤实现: 步骤说明 读取文件内容:使用FileAccess类打开并读取JSON文件。 解析JSON数据:使用JSON类解析读取到的文本内容。 错误处理:处理文件不存在或JSON格式错…...
时序分析笔记
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、周期约束 二、建立时间和保持时间 三、时序路径 四、时序模型 前言 约束文件笔记,傅里叶的猫的视频。 一、周期约束 时序约束就是告诉软件输…...
【笔记】深度学习模型训练的 GPU 内存优化之旅:重计算篇
开设此专题,目的一是梳理文献,目的二是分享知识。因为笔者读研期间的研究方向是单卡上的显存优化,所以最初思考的专题名称是“显存突围:深度学习模型训练的 GPU 内存优化之旅”,英文缩写是 “MLSys_GPU_Memory_Opt”。…...
Deepseek使用技巧大全
还有好多人不会用,一个链接让你们全部学完 https://m0739kfdebc.feishu.cn/docx/LIBddUcupoIBwVxp0yGcsT77nFd?fromfrom_copylink...
redis搭建一主一从+keepalived(虚拟IP)实现高可用
redis搭建一主一从keepalived(虚拟IP)实现高可用 前提 有两台机器:如 10.50.3.141 10.50.3.142,虚拟ip如:10.50.3.170 安装redis(两台机器执行): # 启用Remi仓库(CentOS 7) sudo yum install…...
6、说一下索引失效的场景?【中高频】
索引失效意味着 查询操作 不能利用索引进行数据检索,而是使用 全表扫描(也就是 数据库需要从磁盘上读取表的所有数据行),从而导致性能下降,下面一些场景会发生索引失效 对索引使用左或者左右模糊匹配(where…...
前端调试实战指南:从入门到高阶的完整解决方案
引言:调试的本质与价值 调试是程序员将理想代码映射到现实运行环境的关键过程。据统计,开发者平均将30%的工作时间用于调试。本指南将系统梳理现代前端调试技术体系,帮助开发者构建高效的调试工作流。 一、基础调试工具箱 1.1 浏览器开发者工具核心功能 元素调试(Elemen…...
电商多包裹与子母单发货区别
在电商发货中,多包裹发货和子母单是两种常见的发货方式,具体含义如下: 1. 多包裹发货 定义: 指一个订单中的商品因库存、尺寸或重量等原因,无法装入一个包裹,需分成多个包裹发出。 原因: 商品…...
程序化广告行业(28/89):基于用户旅程的广告策略解析
程序化广告行业(28/89):基于用户旅程的广告策略解析 大家好!一直以来,我都希望能和大家在技术学习的道路上携手前行、共同进步。在之前的文章里,我们探讨了程序化广告行业的诸多关键环节,这次让…...
Hugging Face模型国内镜像HF Mirror下载
直接下载 Hugging Face 开启梯子,一看好几个g... 我们寻找国内镜像。 访问HF-Mirror 继续上面搜索。 继续点击跟踪路径。 拼出路径。 https://hf-mirror.com/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors 如果网…...
2025-03-19 学习记录--C/C++-C语言-单链表的结构体定义 + LNode * 和 LinkList 的区别
C语言-单链表的结构体定义 ⭐️ 一、单链表的结构体定义 🍭 typedef struct LNode { // 定义结构体 LNode,表示链表中的一个结点int data; // 数据域,存储结点的值struct LNode *next; // 指针域,指向下一个结点 } LN…...
【操作系统安全】任务7:服务与进程
目录 一、引言 二、服务与进程介绍 2.1 服务的概念 2.2 进程的概念 2.3 服务与进程的关系 2.4 服务与进程在网络安全中的重要性 三、LAMP 网站环境部署 3.1 LAMP 简介 3.2 LAMP 环境部署步骤 3.2.1 安装 Linux 操作系统 3.2.2 安装 Apache HTTP 服务器 3.2.3 安装 …...
AI里的RAG到底是什么?
AI大模型如deepseek本地部署的成本相对较低,如果要训练,微调大模型,则需要非常多的显卡,与很多时间,那一般企业无法投入那么多钱去买显卡,怎么办? 通过RAG与本地部署来提升大模型的专业知识 R…...
数据库从安装到劝退
友好的安装是数据库使用的第一步 MySQL被称为5分钟数据库。是形容安装简单。事实也是如此。RPM一下可以就把几个包安装完毕了。一个单机情况下,5分钟是足够的。 其他数据库PostgreSQL也差不多是这样。 而Redis这种就更快了。所以这些才能流行。 曾经数据库中安装相…...
《基于Spring Boot+Vue的智慧养老系统的设计与实现》开题报告
个人主页:@大数据蟒行探索者 一、研究背景及国内外研究现状 1.研究背景 根据1982年老龄问题世界大会联合国制定的标准,如果一个国家中超过65岁的老人占全国总人口的7%以上,或者超过60岁的老人占全国总人口的10%以上,那么这个国家将被定义为“老龄化社会”[1]。 随着国…...
PHP转GO Go语言环境搭建(Day1) 常见问题及解决方案指南
Go语言环境搭建(Day1)整理的 常见问题及解决方案指南: Go环境搭建问题排查手册 一、安装阶段问题 问题现象原因分析解决方案安装包下载失败网络问题或官网访问慢使用国内镜像下载:- Go中文网提示"Access Denied"Windows系统权限不足1. 右键安装包选择"以管理…...
VLLM专题(三十九)—自动前缀缓存(二)
前缀缓存(Prefix Caching)是一种在LLM推理中广泛使用的优化技术,旨在避免冗余的提示词(prompt)计算。其核心思想很简单——我们缓存已处理请求的键值缓存(kv-cache)块,并在新请求的前缀与之前请求相同时重用这些块。由于前缀缓存几乎是一种“免费的午餐”,并且不会改变…...
C语言每日一练——day_12(最后一天)
引言 针对初学者,每日练习几个题,快速上手C语言。第十二天。(最后一天,完结散花啦) 采用在线OJ的形式 什么是在线OJ? 在线判题系统(英语:Online Judge,缩写OJ࿰…...
HAL库编程知识点---Can.c和Driver_can.c分层开发
在一个工程中,通常会把对CAN外设的操作分成底层和上层两个部分,从而提高代码的模块化和可维护性。一般来说: can.c 通常由硬件抽象层(HAL)或者自动生成工具(如 CubeMX)提供或生成。主要负责CAN硬…...
L2TP实验 作业
拓扑图 实验需求 让FW1(PPPoE Client)模拟拨号用户,向内部服务器发送建立拨号连接的请求,并保证连通 实验步骤 安全区域 firewall zone trust add int g1/0/0 策略 security-policy default action permit NAS int g1/…...
算法模型从入门到起飞系列——递归(探索自我重复的奇妙之旅)
文章目录 前言一、递归本质1.1 递归的要素1.2 递归特点 二、递归&迭代2.1 递归&迭代比较2.2 递归&迭代如何实现相同功能2.2.1 递归实现2.2.2 迭代实现2.2.3 性能对比 三、优雅的递归理解3.1 阶乘计算分解3.2 [DFS](https://blog.csdn.net/qq_38315952/article/deta…...
Netty源码—1.服务端启动流程二
大纲 1.服务端启动整体流程及关键方法 2.服务端启动的核心步骤 3.创建服务端Channel的源码 4.初始化服务端Channel的源码 5.注册服务端Channel的源码 6.绑定服务端端口的源码 7.服务端启动流程源码总结 5.注册服务端Channel的源码 (1)注册服务端Channel的入口 (2)注册…...
Python OCR文本识别详细步骤及代码示例
光学字符识别(OCR)是将图像中的文字转换为可编辑文本的技术。在Python中,我们可以利用多种库实现OCR功能。本文将详细介绍使用Tesseract和EasyOCR进行文本识别的步骤,并提供完整的代码示例。 一、OCR简介 OCR(Optical…...
springmvc 框架学习
什么是 SpringMVC 框架 Spring MVC 是 Spring 框架的核心模块之一,基于 Java Servlet API 构建的 Web 层解决方案。它实现了 MVC 设计模式(Model-View-Controller),专为开发灵活、松耦合的 Web 应用程序而设计。 在控制层框架历…...
学习threejs,构建THREE.ParametricGeometry参数化函数生成几何体
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.ParametricGeometry1…...
【华为OD-E卷 - 单词接龙 100分(python、java、c++、js、c)】
【华为OD-E卷 - 单词接龙 100分(python、java、c、js、c)】 题目 单词接龙的规则是: 可用于接龙的单词首字母必须要前一个单词的尾字母相同; 当存在多个首字母相同的单词时,取长度最长的单词,如果长度也相…...
美团Leaf分布式ID生成器使用教程:号段模式与Snowflake模式详解
引言 在分布式系统中,生成全局唯一ID是核心需求之一。美团开源的Leaf提供了两种分布式ID生成方案:号段模式(高可用、依赖数据库)和Snowflake模式(高性能、去中心化)。本文将手把手教你如何配置和使用这两种…...
性能测试过程实时监控分析
性能监控 前言一、查看性能测试结果的3大方式1、GUI界面报告插件2、命令行运行 html报告3、后端监听器接入仪表盘 二、influxDB grafana jmeter测试监控大屏1、原理:2、linux环境中influxDB 安装和配置3、jmerer后端监听器连接influxDB4、linux环境总grafana环境搭…...
【工具类】Java的 LocalDate 获取本月第一天和最后一天
博主介绍:✌全网粉丝22W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
Eclipse 创建 Java 类
Eclipse 创建 Java 类 引言 Eclipse 是一款功能强大的集成开发环境(IDE),被广泛用于 Java 开发。本文将详细介绍如何在 Eclipse 中创建 Java 类,包括配置开发环境、创建新项目、添加类以及编写类代码等步骤。 配置 Eclipse 开发环境 1. 安装 Eclipse 首先,您需要在您…...
Centos编译升级libcurl
Centos编译升级libcurl 下载最新版源码包安装编译依赖配置编译选项如果报错:通过 EPEL 仓库安装手动源码编译安装 如果报错:安装Brotli 开发库 如果报错:方法一:安装 libpsl-devel 依赖通过 EPEL 仓库安装重新运行 configure 方…...
蓝桥杯第九天 2022 省赛 第 4 题 最少刷题数
太多坑了,考虑不全只能过50%,有两种特殊情况 public static void main(String[]args) {Scanner scan new Scanner(System.in);int n scan.nextInt();int a[] new int [100005];int b[] new int [100005];for(int i 0;i<n;i)a[i] scan.nextInt()…...
3D点云数据处理中的聚类算法总结
1.欧式聚类: 基于点的空间距离(欧几里得距离)来分割点云,将距离较近的点归为同一簇。 欧式聚类需要的参数:邻域半径R,簇的最小点阈值minPts,最大点数阈值maxPts。 实现效率: O(n * log n) 实现…...
配置本机监控
配置本机监控 1、安装zabbix-agent 2、编辑zabbix-agent配置文件 zabbix-agent工作模式: 主动模式 被动模式 这两行配置都是指定监控服务器的地址 被动模式下,zabbix server的地址 主动模式下,zabbix server的地址 指定被监控端的名称&…...
基于python的Flask模块化设计与蓝图的妙用——打造轻量化Web应用
基于python的Flask模块化设计与蓝图的妙用——打造轻量化Web应用 前言 如果你刚开始学习Flask,可能会遇到这样的困惑:当项目功能越来越多,代码都堆在一个.py文件里,不仅难维护,还容易冲突。别担心!本文将用…...
历年云南大学计算机复试上机真题
历年云南大学计算机复试机试真题 在线评测:传送门:pgcode.cn 喝饮料 题目描述 商店里有 n 中饮料,第 i 种饮料有 mi 毫升,价格为 wi。 小明现在手里有 x 元,他想吃尽量多的饮料,于是向你寻求帮助&#x…...
Python 线程池
Python 线程池 flyfish 线程池的概念 线程池是一种多线程处理形式,它预先创建了一定数量的线程,这些线程会被保存在一个线程池中。当有新的任务提交时,线程池会从池中取出一个空闲的线程来执行该任务;若池中没有空闲线程&#…...
【Linux】Bash是什么?怎么使用?
李升伟 整理 什么是 Bash? Bash(Bourne Again Shell)是一种 命令行解释器(Shell),广泛用于 Unix 和 Linux 操作系统。它是 Bourne Shell(sh) 的增强版,提供了更多的功能…...
蓝桥杯day2:解码异或 后的数组
一、题意 未知 整数数组 arr 由 n 个非负整数组成。 经编码后变为长度为 n - 1 的另一个整数数组 encoded ,其中 encoded[i] arr[i] XOR arr[i 1] 。例如,arr [1,0,2,1] 经编码后得到 encoded [1,2,3] 。 给你编码后的数组 encoded 和原数组 arr …...
R语言软件配置(自用)
①输入R: The R Project for Statistical Computing ②点击进入Cran镜像网页,选择清华大学镜像,选择自己合适的版本下载即可(以我电脑windows为例)。 ③点击base或者install R for the first time,然后选择Download R-4.4.3 for windows&…...
基于deepseek的智能语音客服【第二讲】后端异步接口调用封装
本篇内容主要讲前端请求(不包含)访问后端服务接口,接口通过检索知识库,封装提示词,调用deepseek的,并返回给前端的全过程,非完整代码,不可直接运行。 1.基于servlet封装异步请求 为…...
LEDNet总结
LEDNet:联合低光增强和暗光去模糊 1、暗光增强和去模糊可以单独处理,但是合并效果不理想。 研究问题的背景:光线不足 可见度颜色失真 最小快门速度有限 长时间曝光引起运动模糊 低光运动模糊同时存在 存在问题:暗光增强后运动模…...
线性规划的标准形式
标准形式的定义 目标函数:最大化线性目标函数 其中,x 是决策变量向量,c 是目标系数向量。 约束条件:等式形式约束 A x b, 其中,A 是约束系数矩阵,b 是常数项向量。 变量非负约束: 。 因此…...
xxl-job 执行器端服务器的简单搭建
xxl-job 执行器端服务器的简单搭建 先讲一下我们平时怎么使用 xxl-job 的,再引出背后是如何实现的。 我觉得对于一款成功的框架来说,好用,是非常重要的一个特性。 框架要便于接入,便于使用。对于用户来说,不要有太多…...