一、Anaconda与Virtualenv
虚拟环境Anaconda与Virtualenv可以二选一
1.1 Anaconda
Tip:ros 和Anaconda 一起使用的时候,如果先安装了Anaconda,再安装ros,会报错,因此正确的联合使用方式为:先安装ros相关,后安装anaconda即可。
Anaconda安装完成后会在~/.bashrc中写入如下命令:
# >>> conda initialize >>>
# !! Contents within this block are managed by ‘conda init‘ !!
__conda_setup="$(‘/home/“user”/anaconda3/bin/conda‘ ‘shell.bash‘ ‘hook‘ 2> /dev/null)"
if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "/home/"user"/anaconda3/etc/profile.d/conda.sh" ]; then
. "/home/"user"/anaconda3/etc/profile.d/conda.sh"
else
export PATH="/home/"user"/anaconda3/bin:$PATH"
fi
fi
unset __conda_setup
# <<< conda initialize <<<
脚本命令解析:
$ if [ $? -eq 0 ]; then echo "true"; else echo "false"; fi;
#这条命令的意思是:如果上一条命令执行成功则打印true,否则打印false
#对于上述~/.bashrc中的命令,通常的结果是执行eval "$__conda_setup"
#除非__conda_setup="$('/home/jay/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"这一句出现了错误
可以通过在终端运行$ echo "$('/home/jay/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
命令来查看eval "$__conda_setup"
都干了哪些事。你也可以运行$ cat /home/jay/anaconda3/etc/profile.d/conda.sh
看一下这个脚本里的内容,没错,二者做了相同的工作,只不过前者比后者多执行了一步conda activate base
。if [ -f "/home/jay/anaconda3/etc/profile.d/conda.sh" ]
;的意思是判断这个脚本文件是否存在且没有被损坏,通常这个条件也是满足的。
博主想要表达的意思是,只有当前两个条件都不满足时才会执行export PATH="/home/jay/anaconda3/bin:$PATH"
,可见这个操作并不是上上之选。然而很多人在装完Anaconda后会直接将anaconda的bin目录加入环境变量,鄙人认为这是不科学的,且事实证明这样做会经常带来一些问题,因为它干扰了系统原生的环境。
按照Anaconda默认的配置,打开一个新的终端会自动进入Anaconda的环境:
如果不想直接进入Anaconda的环境,博主的做法是在~/.bashrc中添加一句conda deactivate
,这样打开一个新的终端就是系统原始的Python环境:
要再进入Anaconda环境就运行$ conda activate
。
上图对比了进入anaconda环境与退出anaconda环境后,系统环境变量发生的变化。显然,在进入anaconda环境后,PATH中多了/home/jay/anaconda3/bin,而退出anaconda环境后/home/jay/anaconda3/bin又消失了,博主认为这才是使用Anaconda的正确姿势。
利用Anaconda创建自己的虚拟环境:
$ conda create -n py3.7 python=3.7
$ conda activate py3.7
1.2 Virtualenv
安装Virtualenv:
$ sudo pip install virtualenv
创建虚拟Python环境:
$ whereis python2
$ whereis python3
$ mkdir ~/virtualenv
$ virtualenv -p /usr/bin/python3.5 ~/virtualenv/py3.5
$ source ~/virtualenv/py3.5/bin/activate
可以在虚拟环境中装个TensorFlow玩一下:
$ pip install tensorflow -i https://pypi.tuna.tsinghua.edu.cn/simple
二、在Python3环境下编译ROS包
为了说明问题,我们使用OpenCV在两个虚拟环境中折腾一下:用Anaconda创建的py3.7编写一个OpenCV的Python节点来发布图像话题,然后编写一个C++节点用来订阅图像话题并显示,在Virtualenv创建的py3.5环境下进行编译。具体步骤如下:
首先创建一个ROS工作空间并新建一个ROS包:
$ mkdir -p ~/ros_ws/src
$ cd ~/ros_ws/src
$ catkin_init_workspace
$ catkin_create_pkg cv_package roscpp cv_bridge OpenCV
$ cd cv_package/src
$ touch pub_image.py sub_image.cpp
然后在pub_image.py中粘贴以下内容:
#!/home/jay/anaconda3/envs/py3.7/bin/python
#-*-coding:utf-8-*-
#注意这里import的顺序
import numpy
import rospy
from sensor_msgs.msg import Image
import sys
sys.path.remove('/opt/ros/kinetic/lib/python2.7/dist-packages')
import cv2
def shutdown():
print("shut down!")
def publisher():
rospy.init_node("image_publisher", anonymous=True)
if len(sys.argv)<2:
rospy.loginfo("There should be a parameter follow the %s, such as 0 or 1.", sys.argv[0])
rospy.on_shutdown(shutdown)
return
capture=cv2.VideoCapture(int(sys.argv[1]))
imgPub=rospy.Publisher('/camera/image_raw',Image,queue_size=1)
rospy.loginfo("I am publishing an image...")
rate = rospy.Rate(30)
while not rospy.is_shutdown():
ref,frame=capture.read()
image=Image()
image.encoding='bgr8'
image.height=frame.shape[0]
image.width=frame.shape[1]
image.step=frame.shape[1]*frame.shape[2]
image.data=numpy.array(frame).tostring()
image.header.stamp=rospy.Time.now()
imgPub.publish(image)
rate.sleep()
if __name__ == '__main__':
try:
publisher()
except rospy.ROSInterruptException:
pass
在sub_image.cpp中粘贴以下内容:
#include <ros/ros.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/highgui/highgui.hpp>
void subscriber(const sensor_msgs::ImageConstPtr& msg)
{
cv::Mat img;
cv_bridge::CvImageConstPtr cv_ptr;
try
{
cv_ptr = cv_bridge::toCvShare(msg);
}
catch (cv_bridge::Exception& e)
{
ROS_ERROR("cv_bridge exception: %s", e.what());
return;
}
cv_ptr->image.copyTo(img);
cv::imshow("image",img);
if(27==cv::waitKey(10))ros::shutdown();
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "image_subscriber");
ros::NodeHandle nh;
if(!nh.ok())return 0;
ros::Subscriber imgSub = nh.subscribe("camera/image_raw", 10, subscriber);
ROS_INFO("I am subscribing an image...");
while (ros::ok())ros::spin();
cv::destroyAllWindows();
return 0;
}
再修改一下CMakeLists.txt:
cmake_minimum_required(VERSION 2.8.3)
project(cv_package)
## Compile as C++11, supported in ROS Kinetic and newer
add_compile_options(-std=c++11)
set(OpenCV_DIR /opt/ros/kinetic/share/OpenCV-3.3.1-dev/)
find_package(catkin REQUIRED COMPONENTS
OpenCV
cv_bridge
roscpp
)
catkin_package(
)
include_directories(
${catkin_INCLUDE_DIRS}
)
add_executable(sub_image src/sub_image.cpp)
target_link_libraries(sub_image
${catkin_LIBRARIES}
)
最后在py3.5环境中进行编译(编译本身和Python3.5并没有关系,这里只是用到了它的环境)
$ source ~/virtualenv/py3.5/bin/activate
$ cd ~/ros_ws
$ catkin_make #不出意外的话这一步会报错,然后按照以下步骤进行编译
$ pip install catkin_pkg pyyaml empy rospkg numpy
$ rm -rf build
$ catkin_make
$ chmod +x src/cv_package/src/pub_image.py
$ echo "source ~/ros_ws/devel/setup.bash" >> ~/.bashrc
先运行$ roscore,再运行pub_image和sub_image这两个节点:
# 运行pub_image.py之前,要先在py3.7环境下安装一些依赖
$ conda activate py3.7
$ pip install numpy pyyaml rospkg opencv_python -i https://pypi.tuna.tsinghua.edu.cn/simple
$ conda deactivate #这一步也可以不执行
$ rosrun cv_package pub_image.py 0
# 打开一个新的终端
$ rosrun cv_package sub_image
三、解决相关的疑难杂症
opencv常见症状如下:
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFReadRGBAStrip@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFReadDirectory@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFWriteEncodedStrip@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFIsTiled@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFWriteScanline@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFGetField@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFNumberOfStrips@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFScanlineSize@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFReadEncodedTile@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFReadRGBATile@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFClose@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFClientOpen@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFRGBAImageOK@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFOpen@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFReadEncodedStrip@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFSetField@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFSetWarningHandler@LIBTIFF_4.0’未定义的引用
/opt/ros/kinetic/lib/x86_64-linux-gnu/libopencv_imgcodecs3.so.3.3.1:对‘TIFFSetErrorHandler@LIBTIFF_4.0’未定义的引用
解决办法:修改CMakeLists.txt,在target_link_libraries中添加/usr/lib/x86_64-linux-gnu/libtiff.so
,如下:
add_executable(${PROJECT_NAME}_node src/main.cpp)
target_link_libraries(${PROJECT_NAME}_node
${catkin_LIBRARIES}
/usr/lib/x86_64-linux-gnu/libtiff.so
)
安装python3环境中的问题参考链接https://blog.csdn.net/weixin_44088559/article/details/105116697
tip:如果要使用cv_bridge,则需要参考另一篇文章的配置