
第 26 篇 | LINSHIYI
这是Ubuntu下安装OpenCV系列的第二篇:CMake编译(上)。本文以OpenCV3.4.10和Ubuntu18.04为例,使用Debug模式对OpenCV核心算法和额外算法进行CMake编译,并生成适用于Unix/Linux系统的Makefile等编译文件。
CMake编译这一步共有2篇文,分为3个部分。第一部分介绍CMake命令行中的各个参数;第二部分分析某些文件/压缩包下载失败的原因,并给出多种解决方法;第三部分展示编译结果。
这是CMake编译的上篇,包含第一部分和第二部分(1)-(2)小节的内容,以概念解释为主,下篇会以实践为主,包括第二部分的(3)-(4)小节和第三部分。

目录--2.CMake编译





CMake编译
第四步:CMake编译,生成适用于Ubuntu系统的Makefile及相关文件
命令执行位置:构建目录下
命令行:
$ sudo cmake -D CMAKE_BUILD_TYPE=<compile mode> \
-D CMAKE_INSTALL_PREFIX=<install_directory> \
-D OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules \
<opencv_source_directory>
注意:
1. CMAKE_BUILD_TYPE的参数大小写敏感,如 Debug/Release/RelWithDebInfo/MinSizeRel 等
2. 自行选择库文件的安装路径<install_directory>,默认为/usr/local
3. <opencv_contrib>指额外算法文件夹的路径
4. <opencv_source_directory>指核心算法文件夹的路径
5. 自行增加或减少cmake命令后的变量
6. ippicv压缩包、vgg_*.i系列文件、boostdesc_*.i系列文件和face_landmark_model.dat数据集可能会下载失败复制


4.1 命令行介绍
(1) cmake -D
观察上文输入的命令,可以发现命令行的格式为sudo cmake -D <变量>=<值> ..,cmake是CMake的命令语句,-D是cmake命令中的一个选项,它用于创建或更新CMake的缓存。
The cache is best thought of as a configuration file. The first time CMake is run, it produces a CMakeCache.txt file. This file contains things like the existence and location of native JPEG library.
上图中变量OPENCV_EXTRA_MODULES_PATH负责设置OpenCV额外算法模块的路径,该变量默认为空值,由于我们在命令行中使用-D为变量设置了新的值,所以CMake的缓存也被更改。这个变量在第(4)小节会有详细介绍。
命令行中为变量赋值
每当我们对项目有了新的配置,都需要使用-D <variable>=<value>来更新CMake缓存。另外,-D和变量名之间的空格可以去掉,比如-D CMAKE_BUILD_TYPE=Debug也可以写成-DCMAKE_BUILD_TYPE=Debug。
更多有关-D和其他选项(如-C、-S等)的介绍可以在官方文档中查找到(https://cmake.org/cmake/help/v3.21/manual/cmake.1.html)
(2) CMAKE_BUILD_TYPE
CMAKE_BUILD_TYPE变量
我们在上一篇中介绍过编译模式的概念,项目的编译模式就是通过参数CMAKE_BUILD_TYPE进行设置的(https://cmake.org/cmake/help/v3.21/variable/CMAKE_BUILD_TYPE.html)
我们在Ubuntu系统上使用的是Makefile生成器,也就是说,CMake编译时生成的Makefile等构建文件,只能包含Debug、Release、MinSizeRel、RelWithDebInfo等编译模式中的一种。如果既想要Debug模式的构建文件,又想要Release模式的构建文件,就需要使用CMake编译两次,每次为CMAKE_BUILD_TYPE设置不同的参数。
除了单一配置生成器(Single-Configuration Generators),还有一种生成器叫多元配置生成器(Multi-Configuration Generators),即一次可以生成多种编译模式的项目文件的生成器。
对于多元配置生成器来说,变量CMAKE_BUILD_TYPE是无效的,需要使用另一个变量CMAKE_CONFIGURATION_TYPES来设置(https://cmake.org/cmake/help/latest/variable/CMAKE_CONFIGURATION_TYPES.html)
下图是我在Windows下使用Visual Studio生成器进行CMake编译的过程截图,Visual Studio生成器属于多元配置生成器,从图中可以看出变量CMAKE_CONFIGURATION_TYPES默认写入了Debug和Release两种编译模式,我们可以在“Value”中在追加其他的编译模式,并以分号分隔:
CMAKE_CONFIGURATION_TYPES变量

单一/多元配置生成器示意图
另外,还有一点需要注意,无论是参数CMAKE_BUILD_TYPE还是CMAKE_CONFIGURATION_TYPES,它们的值都是大小写敏感的,例如正确的参数是Debug/Release,写成debug/release是无效的。
(3) CMAKE_INSTALL_PREFIX
参数CMAKE_INSTALL_PREFIX用来设置OpenCV编译生成的头文件、库文件、可执行程序等文件的存放路径。Unix/Linux系统默认将这些文件保存在/usr/local/下的bin、lib、include和share四个目录下(https://cmake.org/cmake/help/v3.21/variable/CMAKE_INSTALL_PREFIX.html)
使用默认的安装路径会有一个问题,不同版本、不同编译模式的头文件、库文件多有重名,如果将这些文件都安装在同一目录下,重名文件会相互覆盖,导致链接库文件失败、程序无法成功运行等问题。
所以,如果我们想安装多个版本的OpenCV,或者生成多个编译模式的库文件,不建议使用默认的安装路径,应该为每个版本、每种编译模式生成的文件,设置单独的存放目录。
我将OpenCV3.4.10(debug版)生成的文件存放在了/usr/local/opencv-3-4-10-debug/下,其它版本的文件路径也都遵循/usr/local/opencv-<version>-<mode>的格式,如/usr/local/opencv-3-4-10-release,/usr/local/opencv-4-5-3-release等,用户可以根据自己的喜好来设置文件夹名称,只要起到区分版本/编译模式的作用即可。
更多有关变量CMAKE_INSTALL_PREFIX的解释见https://cmake.org/cmake/help/v3.21/variable/CMAKE_INSTALL_PREFIX.html
(4)OPENCV_EXTRA_MODULES_PATH

OPENCV_EXTRA_MODULES_PATH变量
OPENCV_EXTRA_MODULES_PATH是配置opencv_contrib中的额外算法模块所必须的参数,如果我们不使用额外算法,这个参数就无需设置
(https://docs.opencv.org/master/db/d05/tutorial_config_reference.html)
定义中的a semicolon-separated list of directories(以分号分割的目录列表)需要做一下解释。如果我们想要使用所有的额外模块,就可以设置OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules,因为modules文件夹下包含了所有算法的源代码文件:

opencv_contrib中的modules文件夹
但如果我们只需要其中的一个模块,例如face模块,那么参数就可以设置为OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules/face
如果想编译两个或多个模块,就需要用分号将不同模块的路径分隔开,例如我们想要编译face和plot模块,参数就是OPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules/face\; <opencv_contrib>/modules/plot。分号前的"\"是转义符,不可以省略。(另外,这里的命令应该是一行,因为排版问题可能看上去像是两行)
(5) <opencv_source_directory>

<opencv_source_directory>
这是cmake命令中需要设置的最后一个变量,位置在命令行的结尾,不可以调整变量顺序。<opencv_source_directory>指的是OpenCV核心算法源代码目录下CMakeLists.txt所在的路径。

CMakeLists.txt和构建目录的位置
如上图所示,我们进行CMake编译时所在的目录是构建目录debug,也就是核心算法文件夹下的子目录,而CMakeLists.txt位于根目录下,换句话说,CMakeLists.txt位于构建目录debug的父目录下。
在Unix/Linux系统中,父目录可以用两个英文句号来表示,当前目录可以用一个英文句号来表示(https://discourse.ubuntu.com/t/the-linux-command-line-for-beginners)
所以对于<opencv_source_directory>参数来说,用户可以直接输入两个点,而无需输入完整的OpenCV源代码文件夹路径。
(6)其他参数
除去上文提到的参数,用户在编译时还可以自己添加相关的参数,比如设置BUILD_EXAMPLES=ON来生成所有样例,设置BUILD_JAVA=OFF不生成OpenCV的Java支持文件,设置BUILD_opencv_<modules_name>=OFF不编译指定的模块等等。
在构建目录下输入cmake-L可以输出项目可用的所有变量和默认参数,输入cmake -LH还可以输出变量注释:

项目可用的CMake变量及其说明
这里的CMake报错可以忽略掉,因为我们并不是要运行CMakeLists.txt中的命令,只是查看可用的参数而已。我们也可以通过查看官方文档来寻找有用的变量(https://cmake.org/cmake/help/v3.21/manual/cmake-variables.7.html)
4.2 有关下载失败的问题
在CMake编译这一步中,除去报错,大概率还会遇到文件/压缩包下载失败的问题,这主要是由于网速不佳或者连接不上GitHub造成的文件下载超时错误。一般会有4种文件/压缩包下载失败:ippicv压缩包、vgg_*.i系列文件、boostdesc_*.i系列文件和face_landmark_model.dat数据集。

CMake下载失败警告--ippicv压缩包
这一节分为4个部分,第一部分会对这4种可能下载失败的文件/压缩包做简要介绍,第二部分通过下载日志来解析CMake的下载逻辑,第三部分给出手动下载文件的两种方法,第四部分会详细介绍3种解决办法。本文包括前两部分,下一篇包括后两部分。
(1)包的简介
先来说说ippicv压缩包,对于OpenCV3.0以及更高的版本,英特尔集成性能原语库(Integrated Performance Primitives, IPP)提供了一个名为IPPICV的子集,它可以作为第三方库链接在OpenCV中,提升程序的运行速度。
如果我们不想使用这个优化器的话,可以在cmake命令中加入-D WITH_IPP=OFF语句来关闭IPPICV加速,这样在CMake编译的过程中就不会自动下载IPPICV压缩包,我们也就不会遇到下载ippicv失败的问题了。
更多有关IPPICV的内容见Intel官网https://software.intel.com/content/www/us/en/develop/articles/intel-integrated-performance-primitives-intel-ipp-open-source-computer-vision-library-opencv-faq.html
vgg_*.i系列文件、boostdesc_*.i系列文件和face_landmark_model.dat数据集都是额外算法模块用到的文件。
vgg_*.i和boostdesc_*.i属于额外的二维特征算法模块(Extra 2D Features Framework,简称xfeatures2d),face_landmark_model.dat属于人脸识别模块(Face Recognition,简称face)
xfeatures2d包括了实验性的特征提取算法(Experimental 2D feature algorithms)和一些受到专利保护的算法(Non-free 2D feature algorithms)。CMake默认不编译受专利保护的算法,用户需要使用-D OPENCV_ENABLE_NONFREE=ON来生成该算法所需的文件(上文定义来自https://github.com/opencv/opencv_contrib/tree/master/modules)
face_landmark_model.dat是人脸识别模块中有关人脸特征探测(Face Landmark Detection)的训练集(https://github.com/opencv/opencv_3rdparty/tree/contrib_face_alignment_20170818)
使用语句-D BUILD_opencv_<extra modules name>=OFF可以不编译某个额外模块,例如对于刚刚的xfeatures2d和face模块,用户可以使用-D BUILD_opencv_xfeatures2d=OFF和-D BUILD_opencv_face=OFF语句来跳过编译,当然,跳过编译之后用户也就无法使用这些算法了。
一些教程中,针对vgg_*.i和boostdesc_*.i系列文件下载失败的问题所给出的解决方案就是在cmake语句中加入-D BUILD_opencv_xfeatures2d=OFF,这对于不打算使用xfeatures2d模块的用户来说是一个方法,但是对于需要使用此模块的用户来说,这种方法并不能解决问题。
(2)CMake的下载逻辑
接下来分析一下CMake在执行下载任务时遵循的逻辑,本小节会涉及两个文件,一个是构建目录下的下载日志;一个是负责OpenCV下载和解压缩任务的文件OpenCVDownload.cmake,它位于源代码目录中的cmake文件夹下。
下载日志名为CMakeDownloadLog.txt,它记录了CMake在执行下载任务时所经历的所有步骤,CMake每编译一次,日志就会被重写一次。通过查看日志中的内容,我们可以了解每个文件的具体下载/解压缩信息:

CMakeDownloadLog.txt
OpenCVDownload.cmake定义了OpenCV下载文件/压缩包的函数ocv_download()、还负责输出下载日志。通过解读这个文件,我们可以了解CMake在下载文件/压缩包时执行的任务逻辑:

OpenCVDownload.cmake
CMake下载逻辑
紫色逻辑共有2个部分,上面的部分是初始任务:判断下载目录是否存在->判断CMake是否成功执行过此任务->初始化下载日志->判断任务模式。下面的部分包括了文件/压缩包的下载和签名校验任务。
文件任务的主逻辑是:检查目标目录下是否存在该文件->检查下载目录下是否存在该文件->下载文件->复制文件至目标目录下。
压缩包任务的主逻辑是:检查下载目录下是否存在该文件->下载文件->检查目标目录下是否存在该文件->目标目录下解压缩。
这里的下载目录指的是CMake在编译过程中新建的一个名为.cache/的文件夹,用来存放所有的下载文件,该文件夹位于源代码文件夹下。目标目录指的是文件/压缩包下载成功之后被复制/解压缩的文件夹。
最后详细介绍一下文件/压缩包下载失败时CMake的逻辑线走向:
CMake下载失败时的逻辑
我们需要对照下载日志CMakeDownloadLog.txt来看一看CMake具体执行了哪些操作,下文以ippicv压缩包和vgg_generated_64.i文件为例。
日志中第一行#use_cache "${OPENCV_DOWNLOAD_PATH}"是程序初始化下载日志时输出的内容。OPENCV_DOWNLOAD_PATH是下载目录的路径。从下图可以看到在我的计算机中OpenCV3.4.10的下载目录为/home/用户名/downloads/opencv-3.4.10/.cache。

ippicv压缩包下载失败日志
下载ippicv压缩包时,程序执行压缩包任务(#do_unpack)-->在下载目录.cache/下查找压缩包是否已存在-->未找到-->下载压缩包(#cmake_download,#try)-->下载失败(编译信息输出Download failed)

ippicv压缩包下载失败警告
下图是文件vgg_generated_64.i的下载日志:程序执行文件任务(#do_copy)-->先在目标目录下寻找-->未发现文件(#missing)-->之后在下载目录下寻找-->未找到文件-->下载文件(#cmake_download,#try)-->下载失败(编译信息输出Download failed)

vgg_generated_64.i下载失败日志

vgg_generated_64.i下载失败警告
最后的最后,解释一下刚刚出现的3种语句#do_unpack/copy、#missing和#cmake_download,并搭配下载日志进行展示,
1. 执行压缩包/文件任务时输出的语句
#do_${mode} "${DL_FILENAME}" "${DL_HASH}" "${DL_URL}" "${DL_DESTINATION_DIR}"
参数:
(1)${mode} 有unpack和copy两种,下载压缩包是unpack模式,下载文件是copy模式
(2)${DL_FILENAME} 文件名称
(3)${DL_HASH} MD5校验值,可以将它理解为文件的一种数字签名,只有数字签名正确的文件才会被下载
(4)${DL_URL} 文件下载地址,一般是以https开头的一串网址
(5)${DL_DESTINATION_DIR} 目标目录,即文件最终存放的位置/压缩包最终被解压的位置复制
下面分别标注出了下载日志中ippicv和vgg_generated_64.i的#do_${mode}语句:


2. 执行文件任务,在目标目录下未找到文件时输出的语句
#missing "${COPY_DESTINATION}"
参数:
(1)${COPY_DESTINATION} 目标目录下文件的路径复制
由于这条语句只会在CMake执行文件任务时输出,所以下面只展示vgg_generated_64.i的#missing语句:

vgg_generated_64.i #missing
3. 下载文件/压缩包时输出的语句
#cmake_download "${CACHE_CANDIDATE}" "${DL_URL}"
#try
参数:
(1)${CACHE_CANDIDATE} 缓存路径,即存放在.cache/下的文件路径
(2)${DL_URL} 下载地址,这里的下载地址与#do_${mode}中的下载地址相同复制
下面两张图标注了下载日志中ippicv和vgg_generated_64.i的#cmake_download和#try语句:


vgg_generated_64.i #cmake_download





~END
~
