Linux下編譯、鏈接、加載運行C++ OpenCV的兩種方式及常見問題的解決
在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
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
,整個過程沒有報錯的話,則測試成功。
方法二:通過g++直接命令行編譯
筆者在使用這種方法時遇到了不少問題,會記錄在后面的常見問題及解決一節,讀者在編譯過程中遇到問題可以去后面看一下,如果后面也沒有你遇到的問題,歡迎留言討論。
我們回到最初的測試目錄的內容,只有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
問題二:pkg-config的安裝
下載、解壓、安裝、驗證一氣呵成:
# 下載
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 4.5.4 可行的opencv.pc文件
這個是筆者自己寫腳本提取的opencv.pc
文件,可能與官網安裝時生成的不同,但是親測使用正常,放到/usr/local/lib/pkgconfig
目錄下即可。
prefix=/usr
exec_prefix=${prefix}/local
includedir=${prefix}/include
libdir=${exec_prefix}/libName: 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 demolinux-vdso.so.1 (0x00007ffdd25b5000)libopencv_core.so.4.5 => not foundlibopencv_imgcodecs.so.4.5 => not foundlibopencv_imgproc.so.4.5 => not foundlibstdc++.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 demolinux-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庫的源代碼時遇到的一些問題及解決方案了,若有錯誤遺漏,或讀者如果有其他問題不能解決,歡迎留言討論。
Ref
https://blog.csdn.net/Charliewolf/article/details/101273248