目录
- 学习opencv过程中一些问题记录
- 学习ROS遇到的问题记录
- 头文件和库文件区别
- ROS中CMakeList文件中catkin_package()和find_package()区别和作用
- ROS中CMakeList文件中add_dependencies()和TARGET_LINK_LIBRARIES()区别和作用
- ROS中的CMakeList文件中add_dependencies()中 ¥{¥{PROJECT_NAME}_EXPORTED_TARGETS} 和${catkin_EXPORTED_TARGETS}是什么意思
- catkin中的package.xml和cmakelist文件中依赖项参数含义的解释
- ROS中命名空间的混淆理解
- 二维旋转矩阵的简单通俗理解和记忆
- ROS系统中的TF工具的坐标变换、坐标系变化区别和小乌龟自身坐标系说明
- Cmakelist文件中Install部分的路径变量解释和catkin make install命令说明
- Quaternion(四元数)的tf和msg两种不同的表示方式主要区别和转化方式
学习opencv过程中一些问题记录
为什么opencv分解全彩图像为三个独立的b,g,r通道,但是三个通道的显示都是灰度图像
打开单一通道显示的是灰色的图像,其实不是灰色,是黑白灰三种颜色,打开单一通道时,如果白色越多,证明你打开这个通道颜色越多,比如你打开红色通道,白色代表红色,黑色是红色的相反色。(灰度图像方便人眼观察所以这样设置)
opencv所使用的颜色空间是BGR而不是RGB有什么影响
BGR和RGB区别在于前者的通道数依次是蓝色,绿色和红色,而后者的通道数依次是红绿蓝。使用opencv想可视化出来正常的RGB(也就是真实的图像色彩样式)格式,那么给opencv使用的图像应该是BGR格式的图像,这样颜色的通道数才能一一对应上。详细解释参考链接
opencv中cv::at处理像素时像素坐标的表示应该是(y,x)
因为OpenCV采用了行优先的存储方式,在OpenCV中cv::at表示像素坐标时,应该先指定行数(y 坐标),然后指定列数(x 坐标),也就是处理图像像素坐标为(x,y)时应该用Mat::at(y,x)。具体可参考:OpenCV – 图像遍历的四种方式(at、指针、isCountinuous、迭代器)、在Vector尾部加数据函数push_back() 中的at函数中文解释和这里的实例结果描述。
opencv中cv2.findContours函数解析(补充内外轮廓含义)
参考
内外轮廓含义补充说明:轮廓是闭合的,因此下图所示中1和2都只有一个轮廓(这根粗线的外包围线就是轮廓,包围这个白色部分只需要一个闭合的部分),而0则分内轮廓和外轮廓(这个首尾相接的粗线外部线闭合算作外轮廓,内环线也是闭合的算作内轮廓,包围这个白色部分需要两部分)。
同理分析下图,0和1表示的是这个环的外轮廓和内轮廓,同理2和3也是如此,而7,6,5,4都只有一个外轮廓(5和4可以想象成更粗的一段线因此只包含最外围的环)
学习ROS遇到的问题记录
头文件和库文件区别
头文件本身并不直接指定库函数的位置(也就是头文件中不包含库文件的位置)。头文件只包含了函数的声明,即函数的名称、参数列表和返回类型等信息,以便在编译时进行语法检查和类型检查。
头文件通常用于告知编译器某个函数的存在和接口,但并不提供函数的实际定义和实现。函数的定义和实现通常位于库文件或源代码文件中。
在编译源代码时,编译器需要知道函数的声明,以便验证代码的正确性。但它并不需要知道函数的实现细节或库函数的位置(也就是没有指明头文件位置,编译器会警告,但实际编译过程还是会链接到库文件生成的二进制文件)。
链接器负责在编译后的目标文件与库文件进行链接,将函数的引用与函数的定义进行匹配,以生成最终的可执行文件。链接器可以根据函数的名称和其他信息,从库文件中找到对应的函数定义,并将其与代码中的函数引用进行关联。
因此,头文件的作用是在编译时提供函数的声明,而链接器会处理函数的定义和实现的匹配和链接。头文件本身并不直接指定库函数的位置,而是提供了函数的接口信息,以便编译器和链接器能够正确处理函数的引用和链接。
具体细节参考此处
ROS中CMakeList文件中catkin_package()和find_package()区别和作用
catkin_packge()官方说明文档:详细文档
上图官方文档部分内容则是了下文黄色字体含义。
这部分内容说明很多catkin类的初始化变量,例如¥{catkin_INCLUDE_DIRS}和${catkin_EXPORTED_TARGETS}等,是catkin_package()运行才定义的,所以Cmakelist文件中catkin_package()一定是非注释的即使其内部参数设置都是注释状态。
cmake_minimum_required(VERSION 3.0.2)
project(your_package_name)
find_package(catkin REQUIRED)
catkin_package()
//上述命令在cmakelist文件夹中一般是不能够缺少的!
当然,如果catkin_package()中指定了catkindepence项,则记得修改package.xml文件中的 <build_export_depend>。。。</build_export_depend>部分内容添加上这些项!参考下图部分内容
find_package()可以帮助我们实现该功能包找头文件和库的目的,而catkin_package()是catkin提供的CMake宏,用于为catkin提供构建、生成pkg-config和CMake文件所需要的信息,其主要用途如果其他功能包使用本功能包(有catkin_package()设置的包)的话,其他功能包会包含我们在本功能包catkin_package中声明的include路径和库,对于DEPENDS依赖项,会将DEPENDS的头文件路径和库添加到本功能包下的include路径和库,也能让其他功能包使用。(对强调部分内容的简单理解可以看这里)但是,如果其他功能包明确需要使用某些依赖,还是需要再find_package()中标注避免一些报错。
catkin_package()参数:
INCLUDE_DIRS:声明给其它package的include路径
LIBRARIES: 声明给其它package的库
CATKIN_DEPENDS: 该功能包被使用时且需要导出提供给其他功能包调用的catkin package
DEPENDS: 该功能包被使用时且需要导出给其他功能包调用的非catkin package
详细讲解请看该参考文献,下面对其内容一些笔记如下:
笔记:这句话是说这两个选项是告诉catkin需要将该程序包的哪些依赖项传递给其他调用你该程序包的程序包来使用。
笔记:这段文字主要解释了catkin_package中INCLUDE_DIRS和LIBRARIES选项的意义。
笔记:这段文字强调了如果其他功能包明确需要使用某些依赖,还是需要再find_package()中标注避免一些报错。
笔记:这段文字将catkin依赖单独设置一个CATKIN_DEPENDS选项是为了让catkin执行一些额外的检查,确定依赖项是catkin依赖项而不是其他库中的依赖项。
简单理解请看这个参考文献
ROS中CMakeList文件中add_dependencies()和TARGET_LINK_LIBRARIES()区别和作用
add_dependencies()是确保TARGET_LINK_LIBRARIES()进行链接过程中依赖性都已经提前编译生成可执行程序。add_dependencies()一般用于对自定义的Messages, Services, and Action Targets提前编译生成可执行程序。
具体解释参考这里
ROS中的CMakeList文件中add_dependencies()中 ¥{¥{PROJECT_NAME}_EXPORTED_TARGETS} 和${catkin_EXPORTED_TARGETS}是什么意思
首先CMakeList文件中的project()定义了功能包名称,可以使用变量${PROJECT_NAME}来指代该功能包名称。
${catkin_EXPORTED_TARGETS}是一个全局变量而¥{¥{PROJECT_NAME}_EXPORTED_TARGETS}是一个局部变量,它们存储了相同的导出目标列表,但¥{¥{PROJECT_NAME}_EXPORTED_TARGETS}受¥{PROJECT_NAME}变量的值影响,即只能是该范围内的构建的消息、服务和动作,所以前者变量比后者变量涵盖的内容更多。
¥{PROJECT_NAME}_EXPORTED_TARGETS}一般是可以不需要的,保留¥{catkin_EXPORTED_TARGETS}就可以了。但是留着也无妨,操作起来也省事,更保险。
意思就是前者指的是全局下的自定义编写的消息,服务和行动消息文件,而后者只能针对的是指定包下编写的自定义的消息,服务和行动文件。
参考这篇文章下图部分内容,这部分内容也介绍了创建一个自定义消息,服务和行动消息需要对cmakelist文件和package.xml文件进行的改动。
catkin中的package.xml和cmakelist文件中依赖项参数含义的解释
官方参考文献,局部内容如下图所示
笔记:额外补充说明:Build Export Dependencies 指出你的当前包编译导出的库 (依赖),作用: b依赖a的头文件,c只依赖b,但是b对a进行了导出,则c建立过程中就能够使用a的头文件,从而避免缺乏引用依赖而导致依赖的功能包没法使用。
specify which packages are needed to build libraries against this package. This is the case when you
transitively include their headers in public headers in this package (especially when these packages
are declared as (CATKIN_)DEPENDS in catkin_package() in CMake).
如果catkin_package()中指定了catkindepence项,则记得修改package.xml文件中的 <build_export_depend>。。。</build_export_depend>部分内容添加上这些项!
ROS中命名空间的混淆理解
<launch>
<node pkg="ssr_pkg" type="yao_node" name="yao" ns="new_name"/>
<node pkg="ssr_pkg" type="li_node" name="li_node"/>
<node pkg="atr_pkg" type="keji_node" name="keji_node" output="screen" />
</launch>
type指的是节点可执行程序名称,name指的是实际使用节点的名称(注意:name不能包含命名空间即不能是"new_name/yao",name标签使用的是重映射原理,虽然程序里面可能节点名称与name不一样,但是实际使用过程中用的是name重映射的名称)。例如:节点名称代码设置的是yao_node,但是launch文件用name修改了节点名称为yao,那么程序运行之后使用rosTopic list功能列出来的yao节点,即实际使用是按照name设定的节点名称。
注:rosrun的终端重映射命令(__name)也不能包含命名空间,$ rosrun turtle_tf2 turtle_tf2_broadcaster __name:=turtle1_tf2/turtle1
就是错误的,这样设置只能设置节点的重映射名称,所以不能包含“/”符号。
如果终端又想指定命名空间又想重映射节点名称,则应该用类似这样的命令rosrun turtlesim turtlesim_node __ns:=/xxx __name:=tn
,语法: rosrun 包名 节点名 ns:=新名称 name:=新名称。
ns会设定一个命名空间放置节点,比如上述代码中yao节点就被放在new_name命名空间,实际节点名称是new_name/yao,也就是/前面的内容指的是命名空间。如下图所示
话题处于哪个命名空间呢?当两个节点不属于一个命名空间下能不能建立通信连接呢?
ros::Publisher pub1 = n.avdertise<std_msgs::String>(“/chatter”,10);
ros::Publisher pub2 = n.avdertise<std_msgs::String>(“chatter”,10);
默认全局名称就是“/”,所有节点以及默认的话题名都是全局命名空间下,当指定命名空间后才会到其他命名空间下。上述代码中加上“/”的话题名会屏蔽所有其他命名空间,只会设置当前自定义的命名(也就是“/”全局空间)作为话题。而没有“/”的话题名会跟随有关系节点设置的命名空间来设置自己发布的话题名称(所在空间)。例如:下图所示,两个节点分别位于两个命名空间,但是把话题名称设置为"/yt_home"就能建立两个节点之间的联系。
如果设置的话题名称为“yt_home”,则两个节点会分别创建一个/new_name/yt_home话题名称和一个/new_nam/yt_home话题名称(也就是上文所说的“话题名称取决于节点所在命名空间”),因为二个节点话题名称不一致,所以两个节点就无法建立连接。(话题的创建取决于与之有关的节点是否存在!)实际运行情况如下:
话题情况如下图所示:
节点连接情况如下图所示,两个节点未建立连接通信
二维旋转矩阵的简单通俗理解和记忆
核心思想解释:
处于某二维空间中的任意向量,可以通过标准正交基来表示。通俗来讲,就是用坐标系来表示。不过表示这个向量的不是x轴和y轴坐标,而是二维的基向量。我们可以联想一下物理中的静止参考系和动参考系。动静参考系在这里对应于动静坐标系。向量旋转的同时,动坐标系是相对于这个向量不动的,相对于静止坐标系则旋转同样的角度。因此只要知道旋转后动坐标系中的标准正交基在静止坐标系中的表达,就能知道旋转后的向量在静止坐标系中的表达。
具体解释参考
ROS系统中的TF工具的坐标变换、坐标系变化区别和小乌龟自身坐标系说明
ROS中的小乌龟自身坐标系
乌龟头指向为x轴方向,垂直于乌龟所在平面的方向是z轴方向,满足右手坐标系。
TF工具的概念讲解
坐标变换概念
TF(TransForm),就是坐标转换,包括了位置和姿态两个方面的变换。注意区分坐标转换和坐标系转换。坐标转换是一个坐标在不同坐标系下的表示,而坐标系转换不同坐标系的相对位姿关系。
source frame、target frame是在进行坐标变换时的概念,source是坐标变换的源坐标系,target是目标坐标系。这个时候,这个变换代表的是坐标变换,即
T
s
o
u
r
c
e
t
r
a
g
e
t
T_{source}^{traget}
Tsourcetraget,对于坐标系位置关系,这个则表示的是在target的坐标系下,source_frame的坐标位置。(理论依据:(a frame到b frame的坐标系变换(frame transform),也表示了b frame在a frame的描述,也代表了把一个点在b frame里坐标变换成在a frame里坐标的坐标变换。))
例如:rosrun tf tf_echo <source_frame> <target_frame> (官方文件以及很多书籍都是这样写的,但是根据source、target frame定义以及实际使用所显示的结果,应该是rosrun tf tf_echo <target_frame> <source_frame>,否则坐标变化不满足
T
s
o
u
r
c
e
t
r
a
g
e
t
T_{source}^{traget}
Tsourcetraget)具体原因解释参考这里,具体位置参考下图。
注:上述文章的定义如图所示,这样定义tf_echo是满足定义要求与实验结果相同的目标的。
在TF监听器中的
void tf::TransformListener::lookupTransform (const std::string &target_frame, const std::string &source_frame, const ros::Time &time, StampedTransform &transform)
命令中,对于target_frame和source_frame定义位置就很正确,实验结果与概念定义符合。实验结果显示的坐标变化满足
T
s
o
u
r
c
e
t
r
a
g
e
t
T_{source}^{traget}
Tsourcetraget,即是source坐标系下的坐标点变化为traget坐标系下坐标点的数据。利用这个命令我们可以进行
详细证明具体过程参考
parent frame、child frame是在描述坐标系变换时的概念,parent是原坐标系,child是变换后的坐标系,这个时候这个变换描述的是坐标系变换,也是child坐标系原点坐标在parent坐标系下的描述,(这个常用构建TF广播器时,设置两个坐标系的父子关系中,此时指的就是坐标系之间的关系)
注:下图会根据这种关系生成TF树来显示各个坐标系之间的转换关系。
注:上图是TF广播器中的回调函数,这里第14行设置的就是parent坐标系,第15行设置的就是child坐标系。
Cmakelist文件中Install部分的路径变量解释和catkin make install命令说明
通常项目编译完成后,目标被放入catkin工作空间下的devel目录。然而,cmakelist文件中的Install部分设置则可以将整个项目进行打包到某个指定位置,通过合适设置能够生成一个程序包而屏蔽源代码相关内容供用户直接使用。当需要将目标直接供其他用户使用,或者安装到本地目录来测试系统级别的安装需要时使用到此功能。
#############
## Install ##
#############
# all install targets should use catkin DESTINATION variables
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
##上面两句话意思就是应该利用这个网页里的打包路径变量进行指定打包位置,打开网页之后又如下部分内容
##Install destinations
##All destination variables are meant to be used with the CMake install() macro as an DESTINATION argument.
##They only contain relative paths and are supposed to be relative to the ${CMAKE_INSTALL_PREFIX} (or ${CATKIN_DEVEL_PREFIX}).
##意思就是说路径变量都是相对路径,路径的前缀是${CMAKE_INSTALL_PREFIX} (or ${CATKIN_DEVEL_PREFIX}),也就是设置这些路径变量之后就会将对应文件放在“前缀+/路径变量”路径下
##在通俗解释就是把对应文件打包”复制”放在工作空间下的install文件夹下,devel文件夹还是有相应文件,能够在删除install文件夹后正常运行。
## Mark executable scripts (Python etc.) for installation
## 添加python程序.in contrast to setup.py, you can choose the destination
install(PROGRAMS
scripts/talker.py
scripts/listener.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
## 添加可执行文件或者库文件,Mark executables and/or libraries for installation
install(TARGETS talker_node listener_node
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
## 添加头文件.Mark cpp header files for installation
install(DIRECTORY include/${PROJECT_NAME}/
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
FILES_MATCHING PATTERN "*.h"
PATTERN ".svn" EXCLUDE
)
## 添加资源文件的目录,例如文件夹:urdf mesh rviz,其下的所有子目录的文件也会安装到相应的目录下.
install(DIRECTORY model
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
install(DIRECTORY urdf
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
install(DIRECTORY mesh
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
install(DIRECTORY rviz
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
## 添加资源文件.Mark other files for installation (e.g. launch and bag files, etc.)
install(FILES
launch/bringup.launch
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
)
上述代码的解释可以参考这篇文章,但是这里补充一下代码里面的DESTINATION变量的通俗解释:
我在/home/yuhs/novatel_ws建立了一个novatel功能包的工作空间,然后对项目catkin_make install之后
在我的电脑上有如下目录地址:
/home/yuhs/novatel_ws/devel
/home/yuhs/novatel_ws/build
/home/yuhs/novatel_ws/src
/home/yuhs/novatel_ws/install/bin/novatel
/home/yuhs/novatel_ws/install/lib/novatel
/home/yuhs/novatel_ws/install/include/novatel
打印这些catkin系统变量信息如下:
CATKIN_PACKAGE_LIB_DESTINATION:lib/novatel
CATKIN_PACKAGE_BIN_DESTINATION:bin/novatel
CATKIN_PACKAGE_INCLUDE_DESTINATION:include/novatel
打印出来的是相对路径,那么路径的前缀是什么,参考上述代码install部分说明中的给的网址,下面这些图片给出了这个网页的内容具体解释。
上面指明了路径前缀也就是工作空间下的install文件夹路径。
(##意思就是说路径变量都是相对路径,路径的前缀是${CMAKE_INSTALL_PREFIX} (默认是指工作空间下的install文件夹)(or ${CATKIN_DEVEL_PREFIX}),也就是设置这些路径变量之后就会将对应文件放在“前缀+/路径变量”路径下。
##在通俗解释就是把对应文件打包”复制”放在工作空间下的install文件夹下,devel文件夹还是有相应文件,能够在删除install文件夹后正常运行。)
上面两个图片解释了变量的含义,第一张图片解释了CATKIN_PACKAGE_BIN_DESTINATION变量的实际路径,第二章图片则说明CATKIN_GLOBAL_BIN_DESTINATION变量是在前缀路径下创建bin文件夹。
补充说明catkin_make install
只有运行catkin_make install命令,cmakelist文件中的install部分才会执行。具体操作过程可以参考这篇文章。
笔记:
第一步是具体看install文件夹的setup.bash在哪,有可能就在install文件夹下,那么直接source setup.bah就可以了。
第二步如果是测试仅靠install文件夹就能运行这个项目,那最好把项目源代码所在工作空间的source setup.bash注释,避免影响检验。(对应文件打包复制放在工作空间下的install文件夹下,devel文件夹还是有相应文件,能够在删除install文件夹后正常运行。)
例如:~/catkin_ws是工作空间,我们将.bashrc文件中的source ~/catkin_ws/devel/setup.bash注释掉,这样就能确保执行roslaunch package_install bringup.launch是install文件夹下的,而不是catkin_ws工作空间的。
自定义install目标位置(camkelist文件内容不变)
这样只需要将默认的install位置前缀变量进行修改就可以了,例如终端执行
$ catkin_make -DCMAKE_INSTALL_PREFIX=/opt/ros/<distro> install
命令,则会修改${CMAKE_INSTALL_PREFIX}为/opt/ros/<distro>
,不在是默认的工作空间路径/install
路径了(其中的取决于ROS安装的版本类型,例如noetic)。但是这样设置在系统文件夹目录下往往会需要root权限,具体解决方式这篇文章中的下图部分内容进行解决。
Quaternion(四元数)的tf和msg两种不同的表示方式主要区别和转化方式
tf2::Quaternion是tf库中用于表示方向和旋转的数据类型。它具有成员函数和方法,用于执行旋转、转换和运算等操作。tf::Quaternion通常用于处理坐标系之间的旋转关系,例如从一个坐标系到另一个坐标系的旋转变换。(需要声明#include <tf2/LinearMath/Quaternion.h>
)
geometry_msgs/Quaternion是其中一种消息类型,用于表示姿态信息的旋转部分。它是一个简单的数据结构,包含四个浮点数字段,分别代表四元数的x、y、z和w分量。它只是一种数据储存类型,不具有别的操作功能(需要声明#include <geometry_msgs/TransformStamped.h>
)
综上所述,二者主要区别体现在,当我们需要对数据进行一些处理变换时,将其转化为tf2::quaternion就可以使用其成员函数方便我们进行操作,不需要我们再去编写程序进行对应操作。例如将欧拉数转化为四元数,我们就可以使用tf2::Quaternion::setRPY()函数进行转化。
很重要的补充说明:
1.tf2Scalar是tf2::Quaternion类型的中double数据类型的别称。官方文档,下图部分内容
typedef double tf2Scalar的含义就是:tf2Scalar是都变了的别名。
2.
geometry_msgs中的Quaternion中的数据类型也是double类型(官方文档链接),此时就会有一个很重要的应用,如下图所示
主要是上图的如下部分内容
transformStamped.transform.rotation.x = q.x();
transformStamped.transform.rotation.y = q.y();
transformStamped.transform.rotation.z = q.getZ();
transformStamped.transform.rotation.w = q.getW();
明明q是tf2::Quaternion类型而 transformStamped.transform.rotation是geometry_msgs类型,二者不一样的但是为啥能够直接赋值?
原因:因为q.x()这个成员函数返回值是一个tf2Scalar(double)类型数值,而 transformStamped.transform.rotation.x代表的也是float64(double)类型数值,二者当然可以进行赋值操作。
ps:q.x()和q.getX()都是q的成员函数而不是数值,所以需要加(),否则就报错,区别成员函数和数值。
3.geometry_msgs::Quaternion和tf2::Quaternion是可以相互转化的,什么时候需要转化?当需要对Quaternion进行一些坐标数据变化的处理时候,一般需要将geometry_msgs::Quaternion类型转化为tf2::Quaternion类型,从而能够调用后者的成员函数进行数据处理。二者之间的转化方式参考,下图部分内容中三个相关转换函数,也可以自行百度,但是别忘记引用#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
。