参考:https://zhuanlan.zhihu.com/p/400316912
Ubuntu18.04 + ros-melodic
ROS安装的OpenCV3.2.0,安装在根目录,比较分散,比如头文件在/usr/include
,库文件在/usr/lib/x86_64-gnu
下。
自己安装的OpenCV3.4.5, 路径是/usr/local/OpenCV3.4.5
自己安装的OpenCV4.5.1,路径是/usr/local/OpenCV4.5.1
uav-get-get
:ROS功能包,使用OpenCV4yn
:ROS功能包,使用OpenCV3,并且用到 uav-get-get
这个功能包。因此为了方便,一开始把uav-get-get
这个功能包的source环境变量语句加到了~/.zshrc
中r2live
:港大Mars实验室的SLAM项目,也是ROS工程,使用OpenCV3编译港大r2live工程,编译的时候出现这个问题,虽然只是警告,但是运行的时候直接报错。
因为链接库有问题,使用gdb调试发现如下结果,有一部分库链接到了OpenCV4.5的库上,而我cmake指定的是3.4.5的版本。
这个确实是一个很隐晦的问题。问题就在于ROS工程中用到OpenCV的话,基本上都会用到ROS中的cv_bridge
用来转换ros和opencv的图片格式。ros安装的opencv是3.2.0版本的,而如果我在cmakelist中指定使用其他版本的OpenCV,很可能就会造成cv_bridge依赖的库出错。从上面的警告来看,都是警告某些OpenCV3的库和Opencv4的库冲突。并且从最后一张图片来看我确实是链接到了OpenCV4的库上,导致程序编译的时候只是给了一个警告,在运行的时候就出错了。后来我把安装的opencv4删除了,然后就没有警告了。实际我的电脑上还有OpenCV3.4.5,和ros版本不同。我觉得此时程序可能仍然错误地连接到了opencv3.4.5的库上,只不过他和cv_bridge用的3.2.0的库主版本号一样,相差不大,所以没有报警告,应该运行也没问题。
不是!实际测试是全部连接到了OpenCV的3.2.0上,如下图所示。
根据参考博客中的讲解,我指定查找OpenCV 的路径是ros安装的Opencv路径,即3.2.0版本,编译之后报警告如下图。
然后对feature_tracker
这个可执行文件依赖的库进行了查看,发现确实一部分链接到了OpenCV 4.5.1
的库上,一部分链接到了OpenCV 3.2.0
上。
最大的问题:为什么我在cmakelists.txt中制定了OpenCV的版本是3.4.5,还是会错误的连接到OpenCV4.5.1的库上呢?
OpenCVConfig.cmake
文件,它的位置也比较奇怪,竟然在lib文件夹下。find_package(OpenCV 3.2.0 REQUIRED)
但是结果却没有优先找到系统的OpenCV版本上,而是找到了自己安装的3.4.5版本上(说明这台电脑上的ROS自带的OpenCV版本可能出了点问题):
find_package(OpenCV 3.2.0 EXACT REQUIRED)
但是这么写结果还是找到了OpenCV3.4.5的版本,所以我感觉写法应该是有点问题,所以把REQUIRED参数去掉了,写成:
find_package(OpenCV 3.2.0 EXACT)
find_package(OpenCV 3.4.5 REQUIRED)
然后编译还是有警告。
有点明白了,感觉像是库会自动链接到比他高版本的库上。但是为什么上面指定OpenCV3.2.0的时候,没有链接到高版本上呢?
新的想法:并不是会自动链接到比他高版本的库上!
在6后面,继续编译r2live包,没有警告,而且程序可以正常跑,没有feature_tracker process has died的错误。
再次安装opencv4.5.1,目录仍然是/usr/local/opencv4.5.1,但是此时再次编译r2live链接不会链接到4.5.1的库上。
编译另一个使用opencv4工程uav-target-get。然后再来编译r2live,这个时候就链接到4.5.1了,好像是使用了一次之后系统知道了这个目录。
问题恰恰出在我的另一个使用opencv4.5.1的工程uav-target-get
上!因为这个ros包被其他包使用,为了方便使用,就把这个ros包的source环境变量语句加到了~/.zshrc中。也就是每次打开一个shell窗口,都会有这个包的环境变量,比如看echo $ROS_PACKAGE_PATH
环境变量:
由于ros是用catkin_make编译的,底层也是cmake,所以ROS_PACKAGE_PATH
的位置也会在cmake编译查找的变量CMAKE_PREFIX_PATH
中。
如果我把source使用opencv4的工程uav-target-get
环境变量语句在~/.zshrc
中删掉之后,在重开窗口,这时候查看环境变量中就没有使用opencv4这个工程的路径了。然后再次编译r2live,就不会再链接到OpenCV4.5.1上了!如果把这个路径再加到环境变量中,再编译r2live,那么又会链接到OPenCV4.5.1上。 所以问题应该就出现在CMAKE_PREFIX_PATH
环境变量这里!
cmake简明使用指南 :这里面就讲了CMake的语法,好像也没有说上面的原因是什么。
加入uav-target-get
的环境变量路径,在链接到opencv4.5.1的工程中打开catkin_make编译生成的build 和 devel两个文件夹,用vscode搜索其中的opencv版本4.5.1,结果发现果真有很多和4.5.1有关的内容:
把上面uav-target-get包的环境变量删掉,再来编译看看,这里结果显示链接不到OpenCV4.5.1了。
这个时候再用vscode看,只有一个opencv4.5.1了,而且这个只是OpenCV4.5.1版本config.cake路径,说明此时确实没有链接到OpenCV4.5.1上。
到底这样是为什么不清楚,因为cmake生成的文件太多太复杂了。
但是猜测一下应该是这样:
由于在CMAKE_PREFIX_PATH
有uav-target-get
的路径,而这个工程使用了OpenCV4.5.1。那么这个工程的build文件夹下的文件中就会存在很多和OpenCV4.5.1有关的内容。而CMAKE_PREFIX_PATH
是cmake查找的路径,这样cmake在查找库的时候就会把opencv4.5.1的库也链接到,从而造成了有些库的版本被错误链接的情况!
我现在删掉uav-target-get
工程的环境变量路径,此时电脑上存在自己安装的OpenCV3.4.5和OpenCV4.5.1,CMakeLists.txt制定使用OpenCV3.4.5,然后此时编译结果:
看链接的库:
根据上面的解释,很容易想到这个很有可能是因为用到了cv_bridge
这个库,因为为了在ros中为了方便处理经常要把图像在ros格式和cv格式互转。查看cv_bridge
这个库到底依赖了那些库:
cd /opt/ros/melodic/lib/
ldd libcv_bridge.so | grep opencv
结果如下,猜测正确!这三个库文件恰好就是上面的feature_tracker警告所链接到的OpenCV3.2.0版本的库文件!
这里虽然同样链接到库出问题了,但是最后编译完整个r2live运行并没有出错。原因应该是3.4.5版本和3.2.0版本的这几个库差别不大,毕竟他们版本号非常相近。但是如果后面使用的OpenCV版本很高,可能这几个库和3.2.0版本的库差别很大呢?此时就只能卸载原来的ros安装的cv_bridge,然后下载github下载源码编译自己匹配的版本的cv_bridge。具体再搜索即可。(但感觉应该不会有人写程序这么搞吧?这不是给自己找麻烦吗?)
因为uav-target-get
是一个使用OpenCV 4.5.1
的ROS工程包,然后另一个使用OpenCV 3
的ROS功能包yn
会使用uav-target-get
这个功能包。那么为了在编译yn
的时候能够找到uav-target-get
功能包,就必须source它的环境变量。
这样就带来了上面的问题:现在的yn工程使用OpenCV3,同时还用了uav-target-get
功能包又必须source它的功能包,由于source了环境变量之后会链接到OpenCV4.5的版本上,这样就会导致库链接冲突的问题。
之前看了编译后uav-target-get
工程在build
devel
文件夹下有很多和OpenCV4.5.1
版本相关的内容,这里想把build
删掉,是不是尽管source了环境变量,也不会链接到OpenCV4.5.1
上了?结果没用,还是会链接到上面。因为uav-target-get
工程的devel
文件夹下还是有和OpenCV4.5.1
版本相关的内容。如下图
看CMakeCache.txt,结果像是大部分链接到了OpenCV4.5.1上,而少部分链接到OpenCV3.2.0上。迷惑?
现在想想如果要彻底解决这个链接库的问题,可以怎么做?
现在唯一可以想到的就是自己在CMakeLists.txt中用find_library
手动把OpenCV库写成一个自己的变量,比如MY_OpenCV_LIB
,然后target_link_libraries
的时候链接${MY_OpenCV_LIB
,而不链接${OpenCV_LIBS}
。但是面对别人的程序很难知道链接了哪些OpenCV库(或许什么都不改先链接,然后ldd看可执行文件就知道链接了哪些库了,但还是比较麻烦),这样就只能在Cmakellist中链接所有的库。OpenCV库又很多,这样导致每个cmakelists文件都要加一大堆内容,太麻烦。
所以想到另一个解决办法就是写一个FindOpenCV-3.4.5.cmake
这种文件放到ModulePath
文件夹下,因为find_package会优先查找Module模式的库,即Findxxx.cmake
这种。然后在FindOpenCV-3.4.5.cmake
中定义头文件和库文件路径,同理这样我们的库名就变成了OpenCV-3.4.5
而非OpenCV
,链接库的时候也就成了${OpenCV-3.4.5_LIBS}
,甚至这个库的名称我们可以在FindOpenCV-3.4.5.cmake
文件中任意定义,保证一定不会和${OpenCV_LIBS}
重复。