/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d

参考:https://zhuanlan.zhihu.com/p/400316912

0.前提描述

0.1.环境

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

0.2.使用的工程

  • uav-get-get:ROS功能包,使用OpenCV4
  • yn:ROS功能包,使用OpenCV3,并且用到 uav-get-get 这个功能包。因此为了方便,一开始把 uav-get-get 这个功能包的source环境变量语句加到了 ~/.zshrc 中
  • r2live:港大Mars实验室的SLAM项目,也是ROS工程,使用OpenCV3

1.问题描述

编译港大r2live工程,编译的时候出现这个问题,虽然只是警告,但是运行的时候直接报错。
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d因为链接库有问题,使用gdb调试发现如下结果,有一部分库链接到了OpenCV4.5的库上,而我cmake指定的是3.4.5的版本。/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
这个确实是一个很隐晦的问题。问题就在于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上,如下图所示。
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
根据参考博客中的讲解,我指定查找OpenCV 的路径是ros安装的Opencv路径,即3.2.0版本,编译之后报警告如下图。
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
然后我查看了feature_tracker这个可执行文件所依赖的库,发现有一部分链接到了OpenCV 4.5.1的库,一部分链接到了OpenCV 3.2.0
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d

最大的问题:为什么我在cmakelists.txt中制定了OpenCV的版本是3.4.5,还是会错误的连接到OpenCV4.5.1的库上呢?

2.问题分析过程

  1. 查看自己安装的OpencV 4.5.1的 OpenCVConfig.cmake 文件,它的位置也比较奇怪,竟然在lib文件夹下。
    /usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
  2. 在实验室电脑上编译feature_tracker包,其中在cmakelists.txt中明确指明了要使用的OpenCV版本是3.2.0:
find_package(OpenCV 3.2.0 REQUIRED)

但是结果却没有优先找到系统的OpenCV版本上,而是找到了自己安装的3.4.5版本上(说明这台电脑上的ROS自带的OpenCV版本可能出了点问题):
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d

  1. 用EXACT关键字强制指定确定版本,而不是兼容版本:
find_package(OpenCV 3.2.0 EXACT  REQUIRED)

但是这么写结果还是找到了OpenCV3.4.5的版本,所以我感觉写法应该是有点问题,所以把REQUIRED参数去掉了,写成:

find_package(OpenCV 3.2.0 EXACT)

这次确实找到了正确的版本,但是链接的问题依然存在:
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d

  1. 把实验室电脑上的OpenCV4.5.1删除,这下一切正常了。feature_tracker正常链接到了3.2.0的库上,如下图。并且最后编译整个r2live成功,最后也成功运行起来没有任何错误。/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
  2. 在宿舍电脑上再次实验,结果也是也可正常编译整个r2live成功,最后也成功运行起来没有任何错误。但是一个比较奇怪的问题是,如果到编译到feature_tracker为止的话,按理说已经编译出来了feature_tracker这个功能包,但是单独运行它报错,按理说ros的分布式的,编译出来这个功能包应该就可以运行了啊?。可能这个功能包有传入的参数?需要回头看看源码。初步猜测和launch文件中有参数有关:
    /usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
  3. 再次测试,宿舍的电脑上删除了OpenCV4.5.1版本,保留了自己安装的opencv3.4.5,然后在cmakelists.txt中指定链接的版本是3.4.5。
find_package(OpenCV 3.4.5 REQUIRED)

然后它编译并带有警告。

/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
有点明白了,感觉像是库会自动链接到比他高版本的库上。但是为什么上面指定OpenCV3.2.0的时候,没有链接到高版本上呢?

新思路:不是会自动链接到比他版本高的库!

  1. 在6后面,继续编译r2live包,没有警告,而且程序可以正常跑,没有feature_tracker process has died的错误。
  2. 到实验室电脑上也改成3.4.5版本,结果和宿舍电脑上一样如下图所示。
    /usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
  3. 再次安装opencv4.5.1,目录仍然是/usr/local/opencv4.5.1,但是此时再次编译r2live链接不会链接到4.5.1的库上。
  4. 编译另一个使用opencv4工程uav-target-get。然后再来编译r2live,这个时候就链接到4.5.1了,好像是使用了一次之后系统知道了这个目录。

3. 破案了!破案了!破案了!

问题恰恰出在我的另一个使用opencv4.5.1的工程uav-target-get上!因为这个ros包被其他包使用,为了方便使用,就把这个ros包的source环境变量语句加到了~/.zshrc中。也就是每次打开一个shell窗口,都会有这个包的环境变量,比如看echo $ROS_PACKAGE_PATH环境变量:

/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
由于ros是用catkin_make编译的,底层也是cmake,所以ROS_PACKAGE_PATH的位置也会在cmake编译查找的变量CMAKE_PREFIX_PATH中。
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d

如果我把source使用opencv4的工程uav-target-get环境变量语句在~/.zshrc中删掉之后,在重开窗口,这时候查看环境变量中就没有使用opencv4这个工程的路径了。然后再次编译r2live,就不会再链接到OpenCV4.5.1上了!如果把这个路径再加到环境变量中,再编译r2live,那么又会链接到OPenCV4.5.1上。所以问题应该就出现在CMAKE_PREFIX_PATH环境变量这里!

4.再分析原因?

cmake简明使用指南:这里面就讲了CMake的语法,好像也没有说上面的原因是什么。

加入uav-target-get的环境变量路径,在链接到opencv4.5.1的工程中打开catkin_make编译生成的build 和 devel两个文件夹,用vscode搜索其中的opencv版本4.5.1,结果发现果真有很多和4.5.1有关的内容:
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d把上面uav-target-get包的环境变量删掉,再来编译看看,这里结果显示链接不到OpenCV4.5.1了。
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d这个时候再用vscode看,只有一个opencv4.5.1了,而且这个只是OpenCV4.5.1版本config.cake路径,说明此时确实没有链接到OpenCV4.5.1上。
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
到底这样是为什么不清楚,因为cmake生成的文件太多太复杂了。

但猜测应该是这样的:

由于在CMAKE_PREFIX_PATHuav-target-get的路径,而这个工程使用了OpenCV4.5.1。那么这个工程的build文件夹下的文件中就会存在很多和OpenCV4.5.1有关的内容。而CMAKE_PREFIX_PATH是cmake查找的路径,这样cmake在查找库的时候就会把opencv4.5.1的库也链接到,从而造成了有些库的版本被错误链接的情况!

5.使用OpenCV3.4.5还会链接到OpenCV3.2.0上的问题

我现在删掉uav-target-get工程的环境变量路径,此时电脑上存在自己安装的OpenCV3.4.5和OpenCV4.5.1,CMakeLists.txt制定使用OpenCV3.4.5,然后此时编译结果:
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d看链接库:
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
根据上面的解释,很容易想到这个很有可能是因为用到了cv_bridge这个库,因为为了在ros中为了方便处理经常要把图像在ros格式和cv格式互转。查看cv_bridge这个库到底依赖了那些库:

cd /opt/ros/melodic/lib/
ldd libcv_bridge.so | grep opencv

结果如下,猜测正确!这三个库文件恰好就是上面的feature_tracker警告所链接到的OpenCV3.2.0版本的库文件!
/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
这里虽然同样链接到库出问题了,但是最后编译完整个r2live运行并没有出错。原因应该是3.4.5版本和3.2.0版本的这几个库差别不大,毕竟他们版本号非常相近。但是如果后面使用的OpenCV版本很高,可能这几个库和3.2.0版本的库差别很大呢?此时就只能卸载原来的ros安装的cv_bridge,然后下载github下载源码编译自己匹配的版本的cv_bridge。具体再搜索即可。(但感觉应该不会有人写程序这么搞吧?这不是给自己找麻烦吗?)

6.有使用OpenCV3的工程用到uav-target-get怎么办?

因为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工程在builddevel文件夹下有很多和OpenCV4.5.1版本相关的内容,这里想把build删掉,是不是尽管source了环境变量,也不会链接到OpenCV4.5.1上了?结果没用,还是会链接到上面。因为uav-target-get工程的devel文件夹下还是有和OpenCV4.5.1版本相关的内容。如下图

/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
看CMakeCache.txt,结果像是大部分链接到了OpenCV4.5.1上,而少部分链接到OpenCV3.2.0上。迷惑?

/usr/bin/ld: warning libopencvxxx.so.a.b,needed by xxx, may conflict with libopencvxxx.so.c.d
但是程序运行起来似乎没有任何问题。

现在想想如果要彻底解决这个链接库的问题,可以做些什么呢?

  • 现在唯一可以想到的就是自己在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} 重复。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2022年4月6日 下午6:52
下一篇 2022年4月8日 上午10:47

相关推荐