简单记录在 Conda 虚拟环境中编译安装 OpenCV 的方法
写这篇文章是因为我想在实验室的服务器上跑 CUDA 加速版本的 OpenCV,但是服务器上安装的 OpenCV 是没有 CUDA 加速的,找管理员重装 OpenCV 又很麻烦,所以我最终研究出了编译安装 OpenCV,同时不需要 sudo 权限,而且可以一并安装 conda 环境中的 opencv-python 的方法。其实之前打机器人比赛的时候我们团队也经常遇到类似的问题,那时主要的困难是编译安装的 OpenCV 要么只能在全局的 python 里使用,要么只能在虚拟环境中使用,在另一侧import cv2
会报错,如果还有其它库(例如 ROS)基于 OpenCV 和 Python 的话会把环境搞的非常非常乱。
废话不多说直接上方法,其实就是在编译的时候直接指定安装路径和 Python 的位置为虚拟环境,道理很好想明白只是不一定知道怎么操作,以下是我cmake
时提供的参数:
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=$CONDA_PREFIX \
-D INSTALL_PYTHON_EXAMPLES=OFF \
-D INSTALL_C_EXAMPLES=OFF \
-D OPENCV_ENABLE_NONFREE=ON \
-D WITH_CUDA=ON \
-D WITH_CUDNN=ON \
-D OPENCV_DNN_CUDA=ON \
-D ENABLE_FAST_MATH=ON \
-D CUDA_FAST_MATH=ON \
-D CUDA_ARCH_BIN=8.6 \
-D WITH_CUBLAS=ON \
-D OPENCV_EXTRA_MODULES_PATH=../../contrib/modules \
-D HAVE_opencv_python3=ON \
-D PYTHON3_EXECUTABLE=$(which python) \
-D PYTHON3_INCLUDE_DIR=$(python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())") \
-D PYTHON3_NUMPY_INCLUDE_DIRS=$(python -c "import numpy; print(numpy.get_include())") \
-D PYTHON3_PACKAGES_PATH=$(python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") \
-D BUILD_EXAMPLES=OFF \
-D WITH_CUDEV=ON \
-D WITH_NVCUVID=ON \
-D WITH_GTK=ON \
-D WITH_QT=ON \
-D WITH_OPENGL=ON \
-D WITH_VULKAN=ON \
-D CMAKE_C_COMPILER=/usr/bin/gcc-14 \
-D CMAKE_CXX_COMPILER=/usr/bin/g++-14 ..
其中大部分参数都不是重点,主要是CMAKE_INSTALL_PREFIX
、PYTHON3_EXECUTABLE
、PYTHON3_INCLUDE_DIR
、PYTHON3_PACKAGES_PATH
这四个,其中CMAKE_INSTALL_PREFIX
是make install
时二进制文件被复制到的位置,CONDA_PREFIX
是当前虚拟环境的根路径,另外三个参数有 CMake 基础都明白是什么意思,主要是参数的值,在对应的虚拟环境下照抄即可。
需要注意安装步骤应该直接使用make install
而不是sudo make install
,因为~/miniconda3
(或者~/anaconda3
)对于当前用户而言当然是有写权限的。
通过这种方法安装以后,import cv2
时可能还会出现以下问题:
>>> import cv2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/***/miniconda3/envs/***/lib/python3.11/site-packages/cv2/__init__.py", line 181, in <module>
bootstrap()
File "/home/***/miniconda3/envs/***/lib/python3.11/site-packages/cv2/__init__.py", line 153, in bootstrap
native_module = importlib.import_module("cv2")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/***/miniconda3/envs/***/lib/python3.11/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ImportError: /home/***/miniconda3/envs/***/bin/../lib/libstdc++.so.6: version `CXXABI_1.3.15' not found (required by /home/***/miniconda3/envs/***/lib/python3.11/site-packages/cv2/python-3.11/cv2.cpython-311-x86_64-linux-gnu.so)
请注意这里出错的动态链接库不一定是我这里的libstdc++.so.6
,但出错的原因应该都是一样的。这因为编译流程中的符号决议(Symbol Resolution)过程中,编译器找到了位于全局的动态链接库/usr/lib64/libstdc++.so.6
(我是 Fedora 发型版,如果你是 Ubuntu,那应该是位于/usr/lib/x86_64-linux-gnu/libstdc++.so.6
)并采用了较新版本的符号CXXABI_1.3.15
,而在运行阶段,加载的动态链接库是虚拟环境中的旧版本动态链接库,因没有找到对应的符号而发生了运行时链接失败。
最根本解决这个问题的方法是在cmake
时提供CMAKE_PREFIX_PATH
参数指向虚拟环境的根目录,这么做会让 CMake 从虚拟环境中搜索编译所需的全部依赖,这种解决方法最彻底但也有非常明显的弊端——在虚拟环境中安装库的全部依赖是一件既消耗精力又占用空间的行为,所以这里我提供另一种解决方法。
答案就是在启动 Python 前强制指定具体加载哪一个动态链接库,方法就是使用LD_PRELOAD
环境变量:
export LD_PRELOAD=/lib64/libstdc++.so.6
python -c "import cv2"
可以发现已经不报错了。
但是每次启动 Python 前都要手动指定LD_PRELOAD
会很麻烦,直接将其写在.bashrc
里又显得破坏了虚拟环境的隔离性,很不优雅,有没有办法让 Conda 来管理环境变量的值?其实也是有的,虚拟环境在被 activate 和 deactivate 时会分别执行$CONDA_PREFIX/etc/conda/activate.d
和$CONDA_PREFIX/etc/conda/deactivate.d
下的脚本,可以用它们来配置环境变量的隔离。
我的$CONDA_PREFIX/etc/conda/activate.d/env_vars.sh
:
#!/bin/bash
export LD_PRELOAD_OLD="$LD_PRELOAD"
export LD_PRELOAD="/lib64/libstdc++.so.6:$LD_PRELOAD"
我的$CONDA_PREFIX/etc/conda/deactivate.d/env_vars.sh
:
#!/bin/bash
export LD_PRELOAD="$LD_PRELOAD_OLD"
unset LD_PRELOAD_OLD
这样就可以让 Conda 来帮我们管理环境变量了。