在Linux下安装完OpenCV C++之后(还没有安装的读者请参考Ubuntu 18.04 安装OpenCV C++),本文将探索Linux下编译、链接C++ OpenCV的两种方式,并且给出笔者在初次尝试时遇到的一些问题的解决方法。一般来看,不只是OpenCV,其实本文已经囊括了大部分Linux下C++链接库文件、运行时链接动态库文件的常见问题。各位读者如果链接时遇到其他问题,也可留言讨论。
注意,我们这里并不采用简单的包含进opencv的头文件,然后再main函数中打印个hello world的测试方式。这种测试方式只能测试头文件的正常包含,即只能保证编译通过,我们需要实际地调用一些opencv中的图像处理接口,来保证链接和运行时的正确性。这里,我们调用Canny算子来提取图像的边缘信息。如果这个测试用例能够正常地编译、链接、运行的话,那整个opencv的运行测试才能算是完整通过了。
首先我们准备一个测试源码demo.cpp
,和测试图像demo.jpg
,把它们放到测试文件夹demo/
下。即一开始,我们有目录树如下:
├── demo
│ ├── demo.cpp
│ └── demo.jpg
CMake链接的使用方法笔者已经在安装OpenCV的文章中介绍过,这里再复述一遍,这种方法基本不会遇到什么问题。我们先准备一个CMakeLists.txt
:
cmake_minimum_required(VERSION 2.8)
project( demo )
find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( demo demo.cpp )
target_link_libraries( demo ${OpenCV_LIBS} )
具体的含义笔者就不在这里赘述了,这需要一些cmake的相关知识,总之按照这个CMakeLists.txt
是可以正常编译链接运行包含opencv库的源代码的。我们来试一下:
cmake .
make
过程输出:
$ cmake .
-- The C compiler identification is GNU 7.5.0
-- The CXX compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenCV: /usr/local (found version "4.5.4")
-- Configuring done
-- Generating done
-- Build files have been written to: /home/song/hello/demo/demo
$ make
Scanning dependencies of target demo
[ 50%] Building CXX object CMakeFiles/demo.dir/demo.cpp.o
[100%] Linking CXX executable demo
[100%] Built target dem
经过这两步后,我们直接运行生成的可执行文件demo
:
./demo
会得到一张Canny算子提取的图像边缘信息Canny.jpg
,整个过程没有报错的话,则测试成功。
笔者在使用这种方法时遇到了不少问题,会记录在后面的常见问题及解决一节,读者在编译过程中遇到问题可以去后面看一下,如果后面也没有你遇到的问题,欢迎留言讨论。
我们回到最初的测试目录的内容,只有demo.cpp
和demo.jpg
。
我们先尝试直接编译链接:
g++ demo.cpp -o demo
这样是肯定不行的,会报一大堆undefined reference,因为这样是没有链接我们的opencv库的。但是只这样编译其实是可以的,即:
g++ -c demo.cpp
除非你的头文件都找不到,否则上述-c
选项的仅编译的命令是会通过的,会得到一个demo.o
可重定向文件。头文件找不到保存请看下面的问题一。
下面我们来解决链接时undefined reference的问题,未定义引用,究其原因无非就是opencv的库文件没有链接进来。对于动态链接库,我们通常使用-l[lib]
的选项来链接,但是opencv中要包含的库文件太多了,我们难道要一个一个写吗?
当然不用!我们通常会借助pkg-config
工具来生成链接时的库文件选项。如果还没有安装请参考问题二。我们要在pkgconfig的目录下添加opencv.pc
来让它帮助生成我们的opencv链接选项。opencv.pc
应该是在opencv安装时直接生成的。
但默认是不生成的,笔者当时安装的时候不知道要用到这个,就没有配置,笔者就自己从Stack Overflow抄了个opencv.pc
文件,但是弄了大半天都不行,最后才发现是版本太老了,不适合现在的opencv版本了。最终无奈自己写了个脚本把lib
目录下opencv的动态链接库名称都提取出来,放到opencv.pc
里才成功。(不然这篇博客应该会早几小时出来,本段小吐槽勿怪)
笔者把自己提取到的opencv.pc
放到问题三了,可能与官网安装时生成的不同,但是亲测使用正常,有需要的小伙伴自取,版本是4.5.4。
将适合自己版本的opencv.pc
放到/usr/local/lib/pkgconfig
目录下。然后命令行执行pkg-config opencv --libs --cflags
,应当会得到如下链接选项:
-I/usr/include/opencv -I/usr/include/opencv2 -L/usr/local/lib -lopencv_aruco -lopencv_barcode -lopencv_bgsegm -lopencv_bioinspired -lopencv_calib3d -lopencv_ccalib -lopencv_core -lopencv_datasets -lopencv_dnn_objdetect -lopencv_dnn -lopencv_dnn_superres -lopencv_dpm -lopencv_face -lopencv_features2d -lopencv_flann -lopencv_freetype -lopencv_fuzzy -lopencv_gapi -lopencv_hfs -lopencv_highgui -lopencv_imgcodecs -lopencv_img_hash -lopencv_imgproc -lopencv_intensity_transform -lopencv_line_descriptor -lopencv_mcc -lopencv_ml -lopencv_objdetect -lopencv_optflow -lopencv_phase_unwrapping -lopencv_photo -lopencv_plot -lopencv_quality -lopencv_rapid -lopencv_reg -lopencv_rgbd -lopencv_saliency -lopencv_shape -lopencv_stereo -lopencv_stitching -lopencv_structured_light -lopencv_superres -lopencv_surface_matching -lopencv_text -lopencv_tracking -lopencv_videoio -lopencv_video -lopencv_videostab -lopencv_wechat_qrcode -lopencv_xfeatures2d -lopencv_ximgproc -lopencv_xobjdetect -lopencv_xphoto
这时我们就可以正常地链接测试源码demo.cpp
了:
g++ demo.cpp `pkg-config opencv --libs --cflags` -o demo
注意是反引号:`。
关于以上链接选项的稍微详细一些的介绍,可以参考:gcc参数 -i, -L, -l, -include。
这时我们就应该得到可执行文件demo
了,运行它,我们可以得到一张Canny算子提取的图像边缘信息Canny.jpg
,整个过程没有报错的话,则测试成功。
如果已经得到了可执行文件demo
,但是运行时报错找不到共享库,请参考问题四。
找不到头文件报错,通常是因为默认安装的头文件路径与搜索的头文件路径不匹配。opencv默认安装的头文件路径是/usr/local/include
,而我们搜索的默认路径在/usr/include
。
遇到这种找不到头文件的报错,请先到/usr/local/include/opencv4/opencv2
路径下确认有自己的头文件,然后直接软链接就行了:
sudo ln -s /usr/local/include/opencv4/opencv2 /usr/include/
关于软链接,用法:Linux软链接的使用,详解:Linux中的硬链接和软链接。
或者通过gcc的-I
参数来指定头文件的搜索路径:
g++ -c demo.cpp -I/usr/local/include
下载、解压、安装、验证一气呵成:
# 下载
wget https://pkg-config.freedesktop.org/releases/pkg-config-0.29.2.tar.gz
# 解压
tar -zxvf pkg-config-0.29.2.tar.gz
# 安装
cd pkg-config-0.29.2/
./configure
make
make check
sudo make install
# 验证
pkg-config --version
# 输出:0.29.2 安装成功
这个是笔者自己写脚本提取的opencv.pc
文件,可能与官网安装时生成的不同,但是亲测使用正常,放到/usr/local/lib/pkgconfig
目录下即可。
prefix=/usr
exec_prefix=${prefix}/local
includedir=${prefix}/include
libdir=${exec_prefix}/lib
Name: opencv
Description: The opencv library
Version: 2.x.x
Cflags: -I${includedir}/opencv -I${includedir}/opencv2
Libs: -L${libdir} -lopencv_aruco -lopencv_barcode -lopencv_bgsegm -lopencv_bioinspired -lopencv_calib3d -lopencv_ccalib -lopencv_core -lopencv_datasets -lopencv_dnn_objdetect -lopencv_dnn -lopencv_dnn_superres -lopencv_dpm -lopencv_face -lopencv_features2d -lopencv_flann -lopencv_freetype -lopencv_fuzzy -lopencv_gapi -lopencv_hfs -lopencv_highgui -lopencv_imgcodecs -lopencv_img_hash -lopencv_imgproc -lopencv_intensity_transform -lopencv_line_descriptor -lopencv_mcc -lopencv_ml -lopencv_objdetect -lopencv_optflow -lopencv_phase_unwrapping -lopencv_photo -lopencv_plot -lopencv_quality -lopencv_rapid -lopencv_reg -lopencv_rgbd -lopencv_saliency -lopencv_shape -lopencv_stereo -lopencv_stitching -lopencv_structured_light -lopencv_superres -lopencv_surface_matching -lopencv_text -lopencv_tracking -lopencv_videoio -lopencv_video -lopencv_videostab -lopencv_wechat_qrcode -lopencv_xfeatures2d -lopencv_ximgproc -lopencv_xobjdetect -lopencv_xphoto
由于我们Linux默认是动态链接的,即有些共享库是在可执行文件运行时才链接进来的(关于链接、装载与库,可参考:Linux下的ELF文件、链接、加载与库(含大量图文解析及例程)。因此我们正确地链接生成可执行文件demo
之后并不能保证正确地运行,可能会找不到共享库报错:
./demo: error while loading shared libraries: libopencv_core.so.4.5: cannot open shared object file: No such file or directory
我们还可以通过ldd
命令来查看可执行文件所需要的共享库:
$ ldd demo
linux-vdso.so.1 (0x00007ffdd25b5000)
libopencv_core.so.4.5 => not found
libopencv_imgcodecs.so.4.5 => not found
libopencv_imgproc.so.4.5 => not found
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fdf87e92000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fdf87c7a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdf87889000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdf874eb000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdf8841f000
确实opencv相关的库都找不到,但是这时我们到共享库目录/usr/local/lib/
下去查看,其实是有这个共享库的:
$ ls -l /usr/local/lib/ | grep "core"
lrwxrwxrwx 1 root root 21 10月 7 19:55 libopencv_core.so -> libopencv_core.so.4.5
lrwxrwxrwx 1 root root 23 10月 7 19:55 libopencv_core.so.4.5 -> libopencv_core.so.4.5.4
-rw-r--r-- 1 root root 5247960 10月 7 11:42 libopencv_core.so.4.5.4
既然有这个库还报找不到文件的错误,那这就提醒我们,是不是系统不知道要到这个目录下去找共享库。这时,我们就应该通过指定环境变量LD_LIBRARY_PATH
来告诉系统我们想要搜索的共享库目录。即通过以下命令将/usr/local/lib
添加到共享库搜索目录的环境变量中:
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
添加之后,我们可以直接先ldd
来查看一下是否已经能够找到共享库:
$ ldd demo
linux-vdso.so.1 (0x00007ffc15975000)
libopencv_core.so.4.5 => /usr/local/lib/libopencv_core.so.4.5 (0x00007fb271fde000)
libopencv_imgcodecs.so.4.5 => /usr/local/lib/libopencv_imgcodecs.so.4.5 (0x00007fb271b1f000)
libopencv_imgproc.so.4.5 => /usr/local/lib/libopencv_imgproc.so.4.5 (0x00007fb271283000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb270efa000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb270ce2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb2708f1000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb2706ed000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb2704ce000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fb2702c6000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fb2700a9000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb26fd0b000)
libjpeg.so.8 => /usr/lib/x86_64-linux-gnu/libjpeg.so.8 (0x00007fb26faa3000)
libpng16.so.16 => /usr/lib/x86_64-linux-gnu/libpng16.so.16 (0x00007fb26f871000)
libtiff.so.5 => /usr/lib/x86_64-linux-gnu/libtiff.so.5 (0x00007fb26f5fa000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb27282d000)
liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007fb26f3d4000)
libjbig.so.0 => /usr/lib/x86_64-linux-gnu/libjbig.so.0 (0x00007fb26f1c6000)
我们看到,所需的共享库已经全部能够找到了。这时我们再运行,即可得到正确结果。
其实总的来说,这里的问题一对应的是编译时期头文件包含的问题,问题二三对应的是链接时期使用pkg-conifg
生成对应库文件的链接选项的问题,而问题四对应的则是运行时期共享库搜索的环境变量配置的问题。所以说本文的这一节将一个动态链接的C++库在Linux下编译、链接、加载运行各个阶段会出现的常见问题都涵盖到了。
总之以上就是笔者在编译、链接、加载运行含有OpenCV库的源代码时遇到的一些问题及解决方案了,若有错误遗漏,或读者如果有其他问题不能解决,欢迎留言讨论。
https://blog.csdn.net/Charliewolf/article/details/101273248