【ROS】—— ROS常用组件_TF坐标变换_多态坐标变换与TF坐标变换实操(十一)

news2024/11/28 3:48:28

文章目录

  • 前言
  • 1. 多态坐标变换
    • 1.1 发布方
    • 1.2 订阅方(C++)
    • 1.3 订阅方(python)
  • 2. 坐标系关系查看
  • 3. TF坐标变换实操(C++)
    • 3.1准备
    • 3.2 生成新的乌龟
    • 3.3 增加键盘控制
    • 3.4 发布方(发布两只乌龟的坐标信息)
    • 3.5 订阅方(解析坐标信息并生成速度信息)

前言

📢本系列将依托赵虚左老师的ROS课程,写下自己的一些心得与笔记。
📢课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ
📢讲义链接:http://www.autolabor.com.cn/book/ROSTutorials/index.html
📢 文章可能存在疏漏的地方,恳请大家指出。

1. 多态坐标变换

需求描述:

现有坐标系统,父级坐标系统 world,下有两子级系统 son1,son2,son1 相对于 world,以及 son2 相对于 world 的关系是已知的,求 son1原点在 son2中的坐标,又已知在 son1中一点的坐标,要求求出该点在 son2 中的坐标

实现分析:

  • 首先,需要发布 son1 相对于 world,以及 son2 相对于 world 的坐标消息
  • 然后,需要订阅坐标发布消息,并取出订阅的消息,借助于 tf2 实现 son1 和 son2 的转换
  • 最后,还要实现坐标点的转换

实现流程: C++ 与 Python 实现流程一致

  • 新建功能包,添加依赖

  • 创建坐标相对关系发布方(需要发布两个坐标相对关系)

  • 创建坐标相对关系订阅方

  • 执行

1.1 发布方

为了方便,使用静态坐标变换发布

<launch>
    <!-- 发布son1相对于world以及son2相对于world的关系-->
    <node pkg="tf2_ros" type="static_transform_publisher" name="son1" args="5 0 0 0 0 0 /world /son1" output="screen"/>
    <node pkg="tf2_ros" type="static_transform_publisher" name="son2" args="3 0 0 0 0 0 /world /son2" output="screen"/>
</launch>

启动

roslaunch tf2_test tf2_test_two.launch 

在rviz中查看
在这里插入图片描述

1.2 订阅方(C++)

#include "ros/ros.h"
#include"tf2_ros/transform_listener.h"
#include "tf2_ros/buffer.h"
#include "geometry_msgs/PointStamped.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.h"
#include "geometry_msgs/TransformStamped.h"
/*

需求:
    现有坐标系统,父级坐标系统 world,下有两子级系统 son1,son2,
    son1 相对于 world,以及 son2 相对于 world 的关系是已知的,
    求 son1 与 son2中的坐标关系,又已知在 son1中一点的坐标,要求求出该点在 son2 中的坐标
实现流程:
    1.包含头文件
    2.初始化 ros 节点
    3.创建 ros 句柄
    4.创建 TF 订阅对象
    5.解析订阅信息中获取 son1 坐标系原点在 son2 中的坐标
      解析 son1 中的点相对于 son2 的坐标
    6.spin

*/
int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ros 节点
    ros::init(argc,argv,"tfs");
    // 3.创建 ros 句柄
    ros::NodeHandle nh;
    // 4.创建 TF 订阅对象
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener sub(buffer);
    // 5.解析订阅信息中获取 son1 坐标系原点在 son2 中的坐标
    //   解析 son1 中的点相对于 son2 的坐标

    //创建坐标点
    geometry_msgs::PointStamped pson1;
    pson1.header.frame_id = "son1";
    pson1.header.stamp = ros::Time::now();
    pson1.point.x  = 1.0;
    pson1.point.y  = 2.0;
    pson1.point.z  = 3.0;
    ros::Rate rate(10);
    while (ros::ok())
    {
        try
        {
          //1.计算son1与son2之间的相对关系
        geometry_msgs::TransformStamped son1toson2= buffer.lookupTransform("son1","son2",ros::Time(0));
        ROS_INFO("son1相对于son2的相对关系:父级:%s,子级:%s,偏移量:%0.2f,%0.2f,%0.2f",
            son1toson2.header.frame_id.c_str(),
            son1toson2.child_frame_id.c_str(),
            son1toson2.transform.translation.x,
            son1toson2.transform.translation.y,
            son1toson2.transform.translation.z);
          //2.计算son1中的某个目标点在son2中的坐标值
         geometry_msgs::PointStamped pson2;
         pson2 = buffer.transform(pson1,"son2");
         ROS_INFO("坐标点在son2中的值%0.2f,%0.2f,%0.2f",
                                                                            pson2.point.x,
                                                                            pson2.point.y,
                                                                            pson2.point.z);
        }
        catch(const std::exception& e)
        {
            ROS_INFO("错误提示:%s",e.what());
        }
        
        rate.sleep();
        ros::spinOnce();
    }
    
    // 6.spin
    return 0;
}

PS:
lookupTransform的参数解释

参数解释
参数1目标坐标系(son2)
参数2源坐标系(son1)
参数3ros::Time(0) 取间隔最短的两个坐标关系帧计算相对关系
返回值geometry_msgs::TransformStamped源相对于目标坐标系的相对关系

计算son1中的某个目标点在son2中的坐标值(3,2,3)

在这里插入图片描述
计算son1与son2之间的相对关系

[ INFO] [1673275545.749496126]: son1相对于son2的相对关系:父级:son1,子级:son2,偏移量:-2.00,0.00,0.00
[ INFO] [1673275545.749576950]: 坐标点在son2中的值3.00,2.00,3.00
[ INFO] [1673275545.849524384]: son1相对于son2的相对关系:父级:son1,子级:son2,偏移量:-2.00,0.00,0.00
[ INFO] [1673275545.849619936]: 坐标点在son2中的值3.00,2.00,3.00
[ INFO] [1673275545.949552460]: son1相对于son2的相对关系:父级:son1,子级:son2,偏移量:-2.00,0.00,0.00
[ INFO] [1673275545.949686138]: 坐标点在son2中的值3.00,2.00,3.00
[ INFO] [1673275546.049589769]: son1相对于son2的相对关系:父级:son1,子级:son2,偏移量:-2.00,0.00,0.00
[ INFO] [1673275546.049734242]: 坐标点在son2中的值3.00,2.00,3.00
[ INFO] [1673275546.149552228]: son1相对于son2的相对关系:父级:son1,子级:son2,偏移量:-2.00,0.00,0.00
[ INFO] [1673275546.149697011]: 坐标点在son2中的值3.00,2.00,3.00
[ INFO] [1673275546.249640250]: son1相对于son2的相对关系:父级:son1,子级:son2,偏移量:-2.00,0.00,0.00
[ INFO] [1673275546.249781671]: 坐标点在son2中的值3.00,2.00,3.00
[ INFO] [1673275546.349580365]: son1相对于son2的相对关系:父级:son1,子级:son2,偏移量:-2.00,0.00,0.00
[ INFO] [1673275546.349704334]: 坐标点在son2中的值3.00,2.00,3.00

1.3 订阅方(python)

发布方都是用launch文件进行,所以不再重复.python版本的订阅方实现如下

#!/usr/bin/env python
"""  
    需求:
        现有坐标系统,父级坐标系统 world,下有两子级系统 son1,son2,
        son1 相对于 world,以及 son2 相对于 world 的关系是已知的,
        求 son1 与 son2中的坐标关系,又已知在 son1中一点的坐标,要求求出该点在 son2 中的坐标
    实现流程:   
        1.导包
        2.初始化 ROS 节点
        3.创建 TF 订阅对象
        4.调用 API 求出 son1 相对于 son2 的坐标关系
        5.创建一依赖于 son1 的坐标点,调用 API 求出该点在 son2 中的坐标
        6.spin

"""
# 1.导包
import rospy
import tf2_ros
from geometry_msgs.msg import TransformStamped
from tf2_geometry_msgs import PointStamped

if __name__ == "__main__":

    # 2.初始化 ROS 节点
    rospy.init_node("frames_sub_p")
    # 3.创建 TF 订阅对象
    buffer = tf2_ros.Buffer()
    listener = tf2_ros.TransformListener(buffer)

    rate = rospy.Rate(1)
    while not rospy.is_shutdown():

        try:
        # 4.调用 API 求出 son1 相对于 son2 的坐标关系
            #lookup_transform(self, target_frame, source_frame, time, timeout=rospy.Duration(0.0)):
            tfs = buffer.lookup_transform("son2","son1",rospy.Time(0))
            rospy.loginfo("son1 与 son2 相对关系:")
            rospy.loginfo("父级坐标系:%s",tfs.header.frame_id)
            rospy.loginfo("子级坐标系:%s",tfs.child_frame_id)
            rospy.loginfo("相对坐标:x=%.2f, y=%.2f, z=%.2f",
                        tfs.transform.translation.x,
                        tfs.transform.translation.y,
                        tfs.transform.translation.z,
            )
        # 5.创建一依赖于 son1 的坐标点,调用 API 求出该点在 son2 中的坐标
            point_source = PointStamped()
            point_source.header.frame_id = "son1"
            point_source.header.stamp = rospy.Time.now()
            point_source.point.x = 1
            point_source.point.y = 1
            point_source.point.z = 1

            point_target = buffer.transform(point_source,"son2",rospy.Duration(0.5))

            rospy.loginfo("point_target 所属的坐标系:%s",point_target.header.frame_id)
            rospy.loginfo("坐标点相对于 son2 的坐标:(%.2f,%.2f,%.2f)",
                        point_target.point.x,
                        point_target.point.y,
                        point_target.point.z
            )

        except Exception as e:
            rospy.logerr("错误提示:%s",e)


        rate.sleep()
    # 6.spin    
    # rospy.spin()

2. 坐标系关系查看

在机器人系统中,涉及的坐标系有多个,为了方便查看,ros 提供了专门的工具,可以用于生成显示坐标系关系的 pdf 文件,该文件包含树形结构的坐标系图谱。

首先调用rospack find tf2_tools查看是否包含该功能包,如果没有,请使用如下命令安装:

sudo apt install ros-<ROS版本号>-tf2-tools

以多坐标变换的launch文件为例,启动launch文件

roslaunch tf2_test tf2_test_two.launch 

启动坐标系广播程序之后,运行如下命令:

rosrun tf2_tools view_frames.py

会产生类似于下面的日志信息:

[INFO] [1673336121.642848]: Listening to tf data during 5 seconds...
[INFO] [1673336126.655691]: Generating graph in frames.pdf file...

查看当前目录会生成一个 frames.pdf 文件
可以直接进入目录打开文件,或者调用命令查看文件:evince frames.pdf

在这里插入图片描述

3. TF坐标变换实操(C++)

实现分析:

乌龟跟随实现的核心,是乌龟A和B都要发布相对世界坐标系的坐标信息,然后,订阅到该信息需要转换获取A相对于B坐标系的信息,最后,再生成速度信息,并控制B运动。

  • 启动乌龟显示节点
  • 在乌龟显示窗体中生成一只新的乌龟(需要使用服务)
  • 编写两只乌龟发布坐标信息的节点
  • 编写订阅节点订阅坐标信息并生成新的相对关系生成速度信息

实现流程: C++ 与 Python 实现流程一致

  • 新建功能包,添加依赖

  • 编写服务客户端,用于生成一只新的乌龟

  • 编写发布方,发布两只乌龟的坐标信息

  • 编写订阅方,订阅两只乌龟信息,生成速度信息并发布

  • 运行

3.1准备

1.新建一个功能包(tf2_turtle),包含以下依赖 tf2tf2_rostf2_geometry_msgsroscpp rospy std_msgs geometry_msgsturtlesim

2.创建一个launch文件(start_tf_turtle.launch)

3.2 生成新的乌龟

实现生成乌龟龟的功能(spawn),此项任务在之前【ROS】—— ROS通信机制——实践与练习(六)已经实现,直接将代码复制,稍作修改即可.

spawn_new_turtle.cpp

/*
    生成一只小乌龟
    准备工作:
        1.服务话题 /spawn
        2.服务消息类型 turtlesim/Spawn
        3.运行前先启动 turtlesim_node 节点

    实现流程:
        1.包含头文件
          需要包含 turtlesim 包下资源,注意在 package.xml 配置
        2.初始化 ros 节点
        3.创建 ros 句柄
        4.创建 service 客户端
        5.等待服务启动
        6.发送请求
        7.处理响应

*/

#include "ros/ros.h"
#include "turtlesim/Spawn.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ros 节点
    ros::init(argc,argv,"set_turtle");
    // 3.创建 ros 句柄
    ros::NodeHandle nh;
    // 4.创建 service 客户端
    ros::ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn");
    // 5.等待服务启动
    // client.waitForExistence();
    ros::service::waitForService("/spawn");
    // 6.发送请求
    turtlesim::Spawn spawn;
    spawn.request.x = 1.0;
    spawn.request.y = 4.0;
    spawn.request.theta = 1.57;
    spawn.request.name = "turtle2";
    bool flag = client.call(spawn);
    // 7.处理响应结果
    if (flag)
    {
        ROS_INFO("新的乌龟生成,名字:%s",spawn.response.name.c_str());
    } else {
        ROS_INFO("乌龟生成失败!!!");
    }


    return 0;
}

将launch文件更改如下:

<launch>
    <!-- 启动乌龟GUI-->
    <node pkg="turtlesim" type = "turtlesim_node" name ="turtle1" output = "screen"/>
    <!-- 生成新的一只乌龟-->
    <node pkg="tf2_turtle" type = "spawn_new_turtle" name ="turtle2" output = "screen"/>
</launch>

启动launch文件,出现如下则运行成功.
在这里插入图片描述

3.3 增加键盘控制

若相对键盘控制进行修改(控制另一只乌龟),可以参考【ROS】—— ROS运行管理 ——元功能包与launch文件(八)或者使用rostopic pub参考【ROS】——常用命令(五)

<launch>
    <!-- 启动乌龟GUI-->
    <node pkg="turtlesim" type = "turtlesim_node" name ="turtle1" output = "screen"/>
    <!-- 键盘控制-->
    <node pkg="turtlesim" type = "turtle_teleop_key" name ="teleop_key" output = "screen"/>
    <!-- 生成新的一只乌龟-->
    <node pkg="tf2_turtle" type = "spawn_new_turtle" name ="turtle2" output = "screen"/>
</launch>

3.4 发布方(发布两只乌龟的坐标信息)

订阅乌龟的位姿信息,然后再转换成坐标信息并发布
类似于【ROS】—— ROS常用组件_TF坐标变换_静态坐标变换与动态坐标变换(十)中的坐标变换,只不过此时需要发布两只乌龟的坐标信息.复制代码的方法可行,但却降低了代码的复用率.对此,我们有以下实现思路:

1.节点只编写一个
2.此节点需要启动两次
3.节点启动时动态传参:第一次启动后传递turtle1,第二次启动传递turtle2.

tf2_pub_turtle.cpp

/*  
    该文件实现:需要订阅 turtle1 和 turtle2 的 pose,然后广播相对 world 的坐标系信息

    注意: 订阅的两只 turtle,除了命名空间(turtle1 和 turtle2)不同外,
          其他的话题名称和实现逻辑都是一样的,
          所以我们可以将所需的命名空间通过 args 动态传入

    实现流程:
        1.包含头文件
        2.初始化 ros 节点
        3.解析传入的命名空间
        4.创建 ros 句柄
        5.创建订阅对象
        6.回调函数处理订阅的 pose 信息
            6-1.创建 TF 广播器
            6-2.将 pose 信息转换成 TransFormStamped
            6-3.发布
        7.spin

*/
//1.包含头文件
#include "ros/ros.h"
#include "turtlesim/Pose.h"
#include "tf2_ros/transform_broadcaster.h"
#include "tf2/LinearMath/Quaternion.h"
#include "geometry_msgs/TransformStamped.h"
//保存乌龟名称
std::string turtle_name;


void doPose(const turtlesim::Pose::ConstPtr& pose){
    //  6-1.创建 TF 广播器 ---------------------------------------- 注意 static
    static tf2_ros::TransformBroadcaster broadcaster;
    //  6-2.将 pose 信息转换成 TransFormStamped
    geometry_msgs::TransformStamped tfs;
    tfs.header.frame_id = "world";
    tfs.header.stamp = ros::Time::now();
    //传参关键点
    tfs.child_frame_id = turtle_name;
    //偏移量
    tfs.transform.translation.x = pose->x;
    tfs.transform.translation.y = pose->y;
    tfs.transform.translation.z = 0.0;
    //四元数
    tf2::Quaternion qtn;
    qtn.setRPY(0,0,pose->theta);
    tfs.transform.rotation.x = qtn.getX();
    tfs.transform.rotation.y = qtn.getY();
    tfs.transform.rotation.z = qtn.getZ();
    tfs.transform.rotation.w = qtn.getW();
    //  6-3.发布
    broadcaster.sendTransform(tfs);

} 

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ros 节点
    ros::init(argc,argv,"pub_tf");
    // 3.解析传入的命名空间
    if (argc != 2)
    {
        ROS_ERROR("请传入正确的参数");
    } else {
        turtle_name = argv[1];
        ROS_INFO("乌龟 %s 坐标发送启动",turtle_name.c_str());
    }

    // 4.创建 ros 句柄
    ros::NodeHandle nh;
    // 5.创建订阅对象
    ros::Subscriber sub = nh.subscribe<turtlesim::Pose>(turtle_name + "/pose",1000,doPose);
    //     6.回调函数处理订阅的 pose 信息
    //         6-1.创建 TF 广播器
    //         6-2.将 pose 信息转换成 TransFormStamped
    //         6-3.发布
    // 7.spin
    ros::spin();
    return 0;
}

launch文件如下

<launch>
    <!-- 启动乌龟GUI-->
    <node pkg="turtlesim" type = "turtlesim_node" name ="turtle1" output = "screen"/>
    <!-- 键盘控制-->
    <node pkg="turtlesim" type = "turtle_teleop_key" name ="teleop_key" output = "screen"/>
    <!-- 生成新的一只乌龟-->
    <node pkg="tf2_turtle" type = "spawn_new_turtle" name ="turtle2" output = "screen"/>
    <!-- 启动两个乌龟相对世界的坐标关系的发布-->
    <node pkg="tf2_turtle" type = "tf2_pub_turtle" name ="pub1" args="turtle1" output = "screen"/>
    <node pkg="tf2_turtle" type = "tf2_pub_turtle" name ="pub2" args="turtle2" output = "screen"/>
</launch>

启动launch文件后,再启动rviz,得到以下结果,则运行成功(三个坐标系)
在这里插入图片描述

3.5 订阅方(解析坐标信息并生成速度信息)

  1. 订阅 turtle1 和 turtle2 的 TF 广播信息,查找并转换时间最近的 TF 信息
  2. 将 turtle1 转换成相对 turtle2 的坐标,在计算线速度和角速度并发布
/*  
    订阅 turtle1 和 turtle2 的 TF 广播信息,查找并转换时间最近的 TF 信息
    将 turtle1 转换成相对 turtle2 的坐标,在计算线速度和角速度并发布

    实现流程:
        1.包含头文件
        2.初始化 ros 节点
        3.创建 ros 句柄
        4.创建 TF 订阅对象
        5.处理订阅到的 TF
        6.spin

*/
//1.包含头文件
#include "ros/ros.h"
#include "tf2_ros/transform_listener.h"
#include "geometry_msgs/TransformStamped.h"
#include "geometry_msgs/Twist.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化 ros 节点
    ros::init(argc,argv,"sub_TF");
    // 3.创建 ros 句柄
    ros::NodeHandle nh;
    // 4.创建 TF 订阅对象
    tf2_ros::Buffer buffer;
    tf2_ros::TransformListener listener(buffer);
    // 5.处理订阅到的 TF

    // 需要创建发布 /turtle2/cmd_vel 的 publisher 对象

    ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle2/cmd_vel",1000);

    ros::Rate rate(10);
    while (ros::ok())
    {
        try
        {
            //5-1.先获取 turtle1 相对 turtle2 的坐标信息
            geometry_msgs::TransformStamped tfs = buffer.lookupTransform("turtle2","turtle1",ros::Time(0));

            //5-2.根据坐标信息生成速度信息 -- geometry_msgs/Twist.h
            geometry_msgs::Twist twist;
            twist.linear.x = 0.5 * sqrt(pow(tfs.transform.translation.x,2) + pow(tfs.transform.translation.y,2));
            twist.angular.z = 4 * atan2(tfs.transform.translation.y,tfs.transform.translation.x);

            //5-3.发布速度信息 -- 需要提前创建 publish 对象
            pub.publish(twist);
        }
        catch(const std::exception& e)
        {
            // std::cerr << e.what() << '\n';
            ROS_INFO("错误提示:%s",e.what());
        }



        rate.sleep();
        // 6.spin
        ros::spinOnce();
    }

    return 0;
}

最后的launch文件

<launch>
    <!-- 启动乌龟GUI-->
    <node pkg="turtlesim" type = "turtlesim_node" name ="turtle1" output = "screen"/>
    <!-- 键盘控制-->
    <node pkg="turtlesim" type = "turtle_teleop_key" name ="teleop_key" output = "screen"/>
    <!-- 生成新的一只乌龟-->
    <node pkg="tf2_turtle" type = "spawn_new_turtle" name ="turtle2" output = "screen"/>
    <!-- 启动两个乌龟相对世界的坐标关系的发布-->
    <node pkg="tf2_turtle" type = "tf2_pub_turtle" name ="pub1" args="turtle1" output = "screen"/>
    <node pkg="tf2_turtle" type = "tf2_pub_turtle" name ="pub2" args="turtle2" output = "screen"/>
    <!-- 订阅turtle1和turtle2相对于世界坐标系的坐标消息,并转换成turtle1相对于turtle2的坐标系,再生成速度消息-->
    <node pkg="tf2_turtle" type = "tf2_sub_turtle" name ="sub"  output = "screen"/>
</launch>

在这里插入图片描述
python版本的乌龟跟随实验与C++基本一致,具体可见TF坐标变换实操

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

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

相关文章

将git仓库瘦身

一个小工具的仓库居然有7个g了&#xff0c;每次clone都要等好久&#xff0c;在网上找的方法&#xff0c;实际了几个小时才成功瘦身&#xff0c;做一次记录 一、排查是哪些历史文件占用了内存&#xff0c;下面是查询最大的5个文件 git rev-list --objects --all | grep "$(…

使用nvm管理node版本,实现高版本与低版本node之间的转换

第一步&#xff1a;先清空本地安装的node.js版本 1.按健winR弹出窗口&#xff0c;键盘输入cmd,然后敲回车&#xff08;或者鼠标直接点击电脑桌面最左下角的win窗口图标弹出&#xff0c;输入cmd再点击回车键&#xff09; 然后进入命令控制行窗口&#xff0c;并输入where node查…

Linux环境配置

一、安装ubuntu16的步骤 1、新建Ubuntu 右击文件---------新建虚拟机 典型----------------下一步 稍后安装操作系统-------------下一步 Linux&#xff08;L&#xff09;------------------Ubuntu64位-----------------下一步 虚拟机名称&#xff08;随意起&#xff09;----…

OSS简单介绍

OSS 阿里云对象存储OSS&#xff08;Object Storage Service&#xff09;是一款海量、安全、低成本、高可靠的云存储服务&#xff0c;可提供99.9999999999%&#xff08;12个9&#xff09;的数据持久性&#xff0c;99.995%的数据可用性。多种存储类型供选择&#xff0c;全面优化…

197: vue+openlayers 预加载preload瓦片地图,减少过渡期间的空白区域

第197个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers项目中演示瓦片预加载和没有预加载的不同状态。 没有采用预加载 当我们平移和缩放地图时,经常会遇到过渡期间的空白区域(因为内容正在加载),过一会儿,切片图像才出现了。 采用预加载,将预载的值设置为…

深入ReentrantLock锁

1. 前言 今天我们来探讨下另一个核心锁ReentrantLock. 从具体的实现到JVM层面是如何实现的。 我们都会一一进行讨论的&#xff0c;好了&#xff0c;废话不多说了&#xff0c;我们就开始吧 2. ReentrantLock 以及synchronized 核心区别&#xff1a; ReentrantLock 是一个抽象的…

MVC框架知识详解

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

机器学习课程学习随笔

文章目录本文来源机器学习简介机器学习流程机器学习可以完成如下功能&#xff1a;机器学习应用场景金融领域零售领域机器学习分类机器学习实现基于python等代码自己实现本文来源 本博客 是通过学习亚马逊的官方机器学习课程的随笔。 课程参考链接https://edu.csdn.net/course/…

爬虫与反爬虫 - 道高一尺魔高一丈 - 2013最新 - JS逆向 - Python Scrapy实现 - 爬取某天气网站历史数据

目录 背景介绍 网站分析 第1步&#xff1a;找到网页源代码 第2步&#xff1a;分析网页源代码 Python 实现 成果展示 后续 Todo 背景介绍 今天这篇文章&#xff0c;3个目的&#xff0c;1个是自己记录&#xff0c;1个是给大家分享&#xff0c;还有1个是向这个被爬网站的前…

synchronized锁膨胀(附代码分析)

synchronized锁膨胀 1. 基本概念 Java对象头 Java对象的对象头信息中的 Mark Word 主要是用来记录对象的锁信息的。 现在看一下 Mark Word 的对象头信息。如下&#xff1a; 其实可以根据 mark word 的后3位就可以判断出当前的锁是属于哪一种锁。注意&#xff1a;表格中的…

shell脚本练习2023年下岗版

shell脚本练习 1.判断指定进程的运行情况 #!/bin/bash NAMEhttpd #这里输入进程的名称 NUM$(ps -ef |grep $NAME |grep -vc grep) if [ $NUM -eq 1 ]; thenecho "$NAME running." elseecho "$NAME is not running!" fi2.判断用户是否存在 #!/bin/bash r…

【RabbitMQ】安装、启动、配置、测试一条龙

一、基本环境安装配置 1.英文RabbitMQ是基于erlang开发的所以需要erlang环境,点击以下链接下载安装 Downloads - Erlang/OTP 2.官网下载RabbitMQ安装包并安装 Installing on Windows — RabbitMQ 3.配置erlang本地环境变量(和JAVAHOME类似) 4.cmd查看erlang版本 5.点击以下…

自己看的操作系统

计算机网络冯诺依曼体系进程线程内核和虚拟内存os管理线程冯诺依曼体系 计算机五大组成&#xff1a;输入设备、输出设备、控制器、运算器、存储器 进程线程 这些应用都是进程 进程相当于一个菜谱&#xff0c;读取到内存中去使用。 电脑一时间能运行很多进程。 进程中为什么要…

excel函数技巧:MAX在数字查找中的应用妙招

大家都知道VLOOKUP可以按给定的内容去匹配到我们所需的数据&#xff0c;正因为如此&#xff0c;它在函数界有了很大的名气。但是今天要分享的这三个示例&#xff0c;如果使用VLOOKUP去匹配数据的话&#xff0c;就有些麻烦了。就在VLOOKUP头疼不已的时候&#xff0c;MAX函数二话…

2022 年度总结

1、CSDN 年度总结 2022年的粉丝涨幅比较明显竟然超过了之前几年的总和&#xff0c;这是比较意外的。应该是因为今年研究了一些云原生、元宇宙的原因&#xff0c;方向比努力真的重要的多。 1500的阅读确实没想到~~~说明低头一族还是没白当 涨粉稍微明细&#xff0c;不过还需…

English Learning - L1-11 时态 + 情态动词 2023.1.9 周一

English Learning - L1-11 时态 情态动词 2023.1.9 周一8 时态8.4 完成进行时&#xff08;一&#xff09;现在完成进行时核心思维&#xff1a;动作开始于现在之前&#xff0c;并有限地持续下去&#xff0c;动作到目前为止尚未完成1. 动作从过去某时开始一直持续到现在并可能继…

【Python】如何使用python将一个py文件变成一个软件?

系列文章目录 这个系列文章将写一些python中好玩的小技巧。 第一章 使用Python 做一个软件 目录 系列文章目录 前言 一、第一步&#xff1a;写好文件 二、第二步&#xff1a;生成程序 1.安装库 2.使用安装的库进行转化 总结 前言 本文重点说如何将py文件转化为exe文件…

回溯法--符号三角形(杂记)

回溯法说来简单&#xff0c;写起来难&#xff0c;真的是要愁死。回溯法有两种模板--子集树、排列树5.4符号三角形--dfs计算多少个满足条件的符号三角形&#xff0c;同号下面为“”&#xff0c;异号下面为“-”。根据异或的规则我们令“”0&#xff0c;“-”1&#xff0c;(异或的…

postgresql 启用ssl安全连接方式

SSL的验证流程 利用openssl环境自制证书 CA 证书签发 创建私钥ca.key,使用des3算法,有效期2048天 openssl genrsa -des3 -out ca.key 2048生成根CA证书请求&#xff08;.csr&#xff09; openssl req -new -key ca.key -out ca.csr -subj "/CCN/STGuangDong/LGuangZhou…

Cloudflare免费版不支持cname解析解决办法

最近调整CDN&#xff0c;使用云盾CDN的话基本上节点都在国内&#xff0c;国外访问就比较难了&#xff0c;虽然我们的站国外用户基本没有&#xff0c;但作为一个有大抱负的站长&#xff0c;眼界必须得宽&#xff0c;必须得支持国外访问才行&#xff01;说起国外免费CDN&#xff…