提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
CMake基础
提示:以下是本篇文章正文内容,下面案例可供参考
一、准备知识
1.1 C++的编译过程
使用g++等编译工具,从源码生成最终的可执行文件一般有这几步∶预处理(Preprocess)、编译(Compile)、汇编(assemble)、链接(link)
输入g++ --help可以看到对应命令:
-E Preprocess only; do not compile,assemble or link.
-S compile only ; do not assemble or link .
-c compilc and assemble, but do not link.
-o <file> Place the output into <file> .
以下面程序为例:
#include <iostream>
int main( )
{
std::cout<<”Hello world:" << std::endl;
return 0;
}
第一步:预处理
C++中预处理指令以#开头。在预处理阶段,会对#define进行宏展开,处理#if,#else等条件编译指令,递归处理#include。这—步需要我们添加所有头文件的引用路径。
#将xx.cpp源文件预处理成xx.i文件(文本文件)
g++ -E main.cpp -o main.i
第二步:编译
检查代码的规范性和语法错误等,检查完毕后把代码翻译成汇编语言文件。
#将xx.i文件编译为xx.s的汇编文件(文本文件)
g++ -s main.i -o main.s
第三步:汇编
基于汇编语言文件生成二进制格式的目标文件。
#手将xx.s文件汇编成xx.o的二进制目标文件
g++ -c main.s -o main.o
第四步:链接
将目标代码与所依赖的库文件进行关联或者组装,合成一个可执行文件
#将xx .o二进制文件进行链接,最终生成可执行程序
g++ main.o -o main
1.2 静态链接库和动态链接库
所谓静态和动态;其区别是链接的阶段不一样。
1、静态链摄库名称一般是lib库名称.a (.a代表archive library),其链接发生在编译环节。一个工程如果依赖一个静态链接库,其输出的库或可执行文件会将静态链接库+.a打包到该工程的输出文件中(可执行文件或库),因此生成的文件比较大,但在运行时也就不再需要库文件了。
2、而动态链接库的链接发生在程序的执行过程中,其在编译环节仅执行链接检查,而不进行真正的链接,这样可以节省系统的开销。动态库一般后缀名为*.so (.so代表shared object,Linux: lib库名称.so,macOS: lib库名称.dylib)。动态链接库加载后,在内存中仅保存一份拷贝,多个程序依赖它时,不会重复加载和拷贝,这样也节省了内存的空间。
3、以下图为例
工程A和B依赖静态链接库 static library,A和s在运行时,内存中会有多份static library ;
工程A和B依赖动态链接库 shared library,A和s在运行时,内存中只有一份shared library (shared: 共享)。
以上只是非常简单的一个解释以区分动态链接库和静态链接库。更多底层的知识需要单独进行深入讲解。
1.3 为什么需要CMake
1.3.1 g++命令行编译
当我们编译附件中1.hello_world时,我们可以运行
g++ main-cpp -o main
当我们需要引入外部库时,如附件中的2.external_libs,需要引入gflags (Google开源的命令行参数处理库),我们则需要运行:
//安装gflags
sudo apt-get install libgflags-dev libgflags2.2
// -lgflags表示链接gflags库,-o main表示输出文件名为main
g++ main.cpp -lgflags -o main
//或者:
//安装pkg-config
sudo apt-get install pkg-config
//pkg-config是一个工具,用于查找和管理安装在系统上的库文件,--cflags --libs gflags表示查找gflags库的头文件和库文件的路径,-o main表示输出文件名为main
g++ main.cpp "pkg-config --cflags --libs gflags" -o main
//测试输出
./main --age 31 --name alice
程序代码main.cpp:
#include <iostream>
#include <gflaqs/gflags.h> //引入gflags头文件
DEFINE_string(name,"","姓名");//定义name参数,类型为string,缺省值为空,描述为“姓名”
DEFINE_int32(age,0,"年龄");//定义age参数,类型为int32,缺省值为0,描述为"年龄”
int main(int argc,char **argv)
{
gflags::ParseCommandLineFlags(&argc,&argv,true);//解析命令行参数
std::cout << "name: " << FLAGS_name << std::endl;//输出name参数
std::cout << "age: " << FLAGS_age << std::endl;//输出age参数
return 0;
}
有些时候有一些常用库我们也不用手动添加头文件或链接库路径,通常g++能在默认查询路径中找到他们。当我们的项目文件变得多起来,引入的外部库也多起来时,命令行编译这种方式就会变得十分臃肿,也不方便调试和编辑。通常在测试单个文件时会使用命令行进行编译,但不推荐在一个实际项目中使用命令行编译。
1.3.2 CMake简介
在实际工作中推荐使用CMake构建G +项目,CMake是用于构建、测试和软件打包的开源跨平台工具;特性:
1·自动搜索可能需要的程序、库和头文件的能力;
2.独立的构建目录(如build) ,可以安全清理
3·支持复杂的自定义命令(下载、生成各种文件)
4·自定义配置可选组件
5·从简单的文本文件( csakeLists.txt)自动生成工作区和项目的能力
6·在主流平台上自动生成文件依赖项并支持并行构建
7· 几乎支持所有的IDE
二、CMake基础知识
2.1 安装
ubuntu上请执行
cd CMake
sudo wget https://cmake.org/files/v3.25/cmake-3.25.1.tar.gz
sudo tar -zxvf cmake-3.25.1.tar.gz
cd cmake-3.25.1
sudo apt-get -y install libssl-dev
sudo ./configure
sudo make -j8
sudo make install
cmake --version
//如果cmake --version没有显示的话
sudo cp ./bin/cmake /usr/bin/
编译安装:
//以v3.25.1版本为例
cd ~
git clone -b v3.25.1 https://github.com/Kitware/CMake.git
cd CMake
chmod 755 ./CMake-3.25.1/ -R
sudo apt install make
//你使用"--prefix~来指定安装路径,或者去掉~ --prefix”,安装在默认路径。
./ bootstrap --prefix=<安装路径>&& make && sudo make install
//验证
cmake --version
2.2 第一个CMake例子
附件位置:first_cmake
//第一步:-S 指定源码目录,-B指定构建目录
cmake -S . -B build
//第二步:生成,--build指定构建目录
cmake --build build
//运行
./build/firse_cmake
利用vscode 连接Ubuntu,新建文件夹新建文件,写好代码后cmake --build build一直报错:在函数‘_start’中:(.text+0x20):对‘main’未定义的引用collect2: error: ld returned 1 exit status。上网查了一下说是可能是写的代码里没有包含main函数,但是程序里面明明就有main函数啊,搞半天还是百思不得解。结果发现我的程序写完没保存,于是我在vscode里按了保存按钮,保存后再cmake --build build就成功了。
vs code插件:
安装twxs.cmake做代码提示;
安装ms-vscode.cmake-tools界面操作。
启用ms-vscode.cmake-tools后,如下图依次操作,即可生成可执行文件
2.3 基础语法
2.3.1 指定版本
以first_cmake/ CMakeLists.txt为例:
#CMake 最低版本号要求
cmake_minimum_required(VERSION 3.10)
#first_cmake是项目名称,VERSION是版本号,DESCRIPTION是项目描述,LANGUAGES是项目语言
#关键字和参数后面都要加一个空格
project(first_cmake VERSION 1.0.0
DESCRIPTION "项目描述"
LANGUAGES CXX)
#添加一个可执行程序,first__cmake是可执行程序名称,main.cpp是源文件
add_executable(first_cmake main.cpp)
命令cmake_minimum_required来指定当前工程所使用的CMake版本,不区分大小写的,通常用小写。VERSTON是这个函数的一个特殊关键字,版本的值在关键词之后。CMake中的命令大多和cmake_minimum_required相似,不区分大小写,并有很多关键字来引导命令的参数输入(类似函数传参} 。
2.3.2 设置项目
以附件:first_cmake/ cNakeLists.txt为例:
project(ProjectName
VERSION 1.0.0
DESCRIPTION "项目描述"
LANGUAGES cxX)
在CMakeLists.txt的开头,都会使用project来指定本项目的名称、版本、介绍、与使用的语言。在project中,第一个ProjectName(例子中用的是first_cmake )不需要参数。其他关键字都有参数。
2.3.3 添加可执行文件目标
add _executable( first_cmake main.cpP)
这里我们用到add_executable,其中第一个参数是最终生成的可执行文件名以及在CMake中定义的Target名。我们可以在CMake中继续使用Target的名字为Target的编译设置新的属性和行为。命令中第一个参数后面的参数都是编译目标所使用到的源文件。
2.3.4 生成静态库并链接
A.生成静态库
文件位置3.static_lib_test/account_dir/CMakeLists.txt
#最低版本要求
cmsake_minimum_required(VERSION 3.10)
#项目信息
project(Account)
#添加静态库,Linux下会生成1ibAccount.a
add_library(Account STATIC Account.cpp Account.h)
#编译静态库后,会在build下生成build/libAccoant.a静态库文件
#然后我们可以删掉./build下除了.a文件外的多余文件
account_air/
├── Account.cpp
├── Account.h
├── build
│ └── libAccount.a
└── CMakeLists.txt
这里我们用到add_library ,和add_executable一样,Account为最终生成的库文件名(lib库名称.a),第二个参数是用于指定链接库为动态链接库(SHARED)还是静态链接库(STATIC),后面的参数是需要用到的源文件。
再到目录3.static_lib_test/test_account/CMakeLists.txt
#test_account/CMakeLists.txt
# 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 添加项目信息
project(test_account)
# 添加可执行文件目标
add_executable(test_account main.cpp)
# 为目标添加头文件目录
target_include_directories(test_account PUBLIC "../account_dir")
# 为目标添加库文件目录
target_link_directories(test_account PUBLIC "../account_dir/build")
# 链接,为目标添加库libAccount.a,这里不需要写全称
target_link_libraries(test_account Account)
编译后,会在build下生成test_account可执行文件
然后我们可以删掉./build下除了可执行文件外的多余文件
最后整个项目目录如下
├── account_dir
│ ├── Account.cpp
│ ├── Account.h
│ ├── build
│ │ └── libAccount.a
│ └── CMakeLists.txt
└── test_account
├── build
│ └── test_account
├── CMakeLists.txt
└── main.cpp
2.3.5 生成动态库并链接
文件位置4.dynanic_lib_test/account_dir/CHakeLists.txt
#account_dir/CMakeLists.txt
# 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 项目信息
project(Account)
# 指定生成目标:静态库
# 三个参数分别为:库名称、库类型、源文件
# Linux下库类型为: libAccount.a
add_library(Account SHARED Account.cpp)
编译动态库后,会在build下生成build/libAccoant.so动态库文件
再到目录4.dynanic_lib_test/test_account/CMakeLists.txt
#test_account/CMakeLists.txt
# 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 添加项目信息
project(test_account)
# 添加可执行文件目标
add_executable(test_account main.cpp)
# 为目标添加头文件目录
target_include_directories(test_account PUBLIC "../account_dir")
# 为目标添加库文件目录
target_link_directories(test_account PUBLIC "../account_dir/build")
# 链接,为目标添加库libAccount.a,这里不需要写全称
target_link_libraries(test_account Account)
编译后,会在build下生成test_account可执行文件。
然后我们可以删掉./build下的多余文件。
最后整个项目目录如下
├── account_dir
│ ├── Account.cpp
│ ├── Account.h
│ ├── build
│ │ └── libAccount.so
│ └── CMakeLists.txt
└── test_account
├── build
│ └── test_account
├── CMakeLists.txt
└── main.cpp
2.3.5.1 用一个CMakeLists.txt构建项目
文件位置5.build_together/CHakeLists.txt
#5.build_together/CMakeLists.txt
# 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 添加项目信息
project(test_account)
# 添加目标:动态库
add_library(Account SHARED "account_dir/Account.cpp" "account_dir/Account.h")
# 添加可执行文件目标
add_executable(test_account "test_account/main.cpp")
# 添加头文件目录
target_include_directories(test_account PUBLIC "account_dir")
# 链接库libAccount.so,这里不用写全称
target_link_libraries(test_account Account)
编译后,会在build下生成test_account可执行文件和libAccount.so动态库文件。
最后整个项目目录如下:
├── account_dir
│ ├── Account.cpp
│ └── Account.h
├── build
│ ├── libAccount.so
│ └── test_account
├── CMakeLists.txt
└── test_account
└── main.cpp
2.3.6 CMake中的PUBLIC、PRIVATE、INTERFACE
CMake中经常使用target_…()类似的命令,一般这样的命令支持通过PUBLIC、PRIVATE、INTERPACE关键字来控制传播。以target_link_libraries(A B)为例
从理解的角度来看
PRIVATE︰依赖项B仅链接到目标,如果有C链接了A,C不会链接INTERFACE︰依赖项B并不链接到目标A,如果有C链接了A,C会链接B
BPUBLIC ︰依赖项B链接到目标A,如果有C链接了A,C也会链接B
其实就是对象属性的传递,打个散烟的比方:
PRIVATE:就是自己抽,不给别人抽
INTERFACE:就是自己不抽,给别人抽
PUBLIC︰就是自己抽,也给别人抽
从使用的角度来说,如果有C链接了目标A
·如果B仅用于A的实现,且不在头文件中提供给c使用,使用PRIVATE
·如果B不用于A的实现,仅在头文件中作为借口给c使用,使用INTERFACE
·如果B既用于A的实现,也在头文件中提供给c使用,使用PUBLIC
举例:
#创建库
add_library(C c.cpp)
add_library(D d.cpp)
add_library(B b.cpp)
#C是B的PUBLIC依赖项
target_link_libraries(B PUBLIC C)
#D是B的PRIVATE依赖项
target_link_libraries(B PRIVATE D)
#添加可执行文件
add_executable(A a.cpp)
#将B链接到A
target_link_li.braries(A B)
因为C是B的PUBLIC依赖项,所以C会传播到A
因为D是B的PRIVATE依赖性,所以D不会传播到A
2.3.7 变量
文件位置:6.message_var_demo
像其他编程语言一样,我们应该将CMake理解为一门编程语言。我们也需要设定变量来储存我们的选项,信息。有时候我们通过变量来判断我们在什么平台上,通过变量来判断我们需要编译哪些Target,也通过变量来决定添加哪些依赖。
#6.message_var_demo/CMakeLists.txt
# 最低版本要求
cmake_minimum_required(VERSION 3.10)
project(message_var_demo)
# 输出消息
# message("输出消息")
# message("输出1","输出2","输出3")
# 设置变量
set(VAR1 "变量1")
message("VAR1=" ${VAR1}) #外部访问
message("输出变量VAR1:${VAR1}") # 内部拼接
message("\${VAR1}=${VAR1}") # 使用\转义输出$
unset(VAR1) #删除变量
message("\${VAR1}=${VAR1}") # 删除变量后,输出为空
# 设置变量缓存,"默认为value",可以在命令行中修改(cmake -S . -B build -D参数=xxxx)
set(CACHE_VARIABLE_TEST "value" CACHE STRING "变量缓存的描述")
message("变量缓存的值:${CACHE_VARIABLE_TEST}")
# 常见的内置变量,更多访问: https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html#variables
# 第一类:提供信息的变量
message("${PROJECT_NAME}")#项目名称
message("${CMAKE_SOURCE_DIR}")#源码目录
message("${CMAKE_BINARY_DIR}")# 编译目录
message("${CIMAKE_CURRENT_LIST_FILE}")#当前CMakeLists.txt文件路径
# 第二类:控制CMake运行的变量
set(BUILD_SHARED_LIBS ON)#生成动态库的变量开启
## 第三类:描述系统的变量,更多: https://cmake.org/cmake/help/latest/manual/cmake-variables.7.html
message("是否是windows系统${WIN32}")
message("是否是Linux系统:${UNIX}")
message("系统名称:${CMAKE_SYSTEM_NAME}")
# 生成库
add_library(${PROJECT_NAME} Account.cpp Account.h)
2.3.8 Include引入其他代码
附件位置:7.include_demo
#7.inlude_demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(include_demo)
message("调用include前的信息")
#include,引用一次就调用一次
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/module_1.cmake")
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/module_1.cmake")
message("调用include后的信息")
其中module_1.cmake代码如下:
message("模块内部被调用")
项目目录:
├── cmake
│ └── module_1.cmake
└── CMakeLists.txt
编译后输出:
2.3.9 条件控制
附件位置:8.if_demo
正如前面所讲,应该把CMake当成编程语言,除了可以设置变量以外,CMake还可以写条件控制。
if(variable)
#为true的常量:ON、YES、TRUE、Y、1、非0数字
else()
#为false的常量:OFF.NO、FALSE、N、0、空字符串、NOTFOUND
endif()
可以和条件一起使用的关键词有
NOT,TARGET,EXISTS(file) , DEFINED等
STREQUAL,AND,OR,MATCHES(regular expression),VERSION_LESS,VBRSION_LESS_EQUuAL等
2.3.10 CMake分步编译
附件位置:9.steps_demo
#查看所有目标
cmake -s - -B build
cd build
cmake --build . --target help
The following are some of the valid targets for this Makefilcr
... all (the default if no target is provided)
... clcan
... depend
... rebuild_cache
... edit_cache
... steps_demo
... main.o
... main.i
... nain.s
#1.预处理
emake --buildI. --target main .i
#输出:Preprocessinc cxx source to CMakeFiles/steps_demo.dirimain .cpp.i可以打开滑到底部
#2.编译
cmake --build . --target main .s
#输出汇编代码: compiling cxx source to assembly cMakeFiles/steps_demo.dir/main.cpp.s
#3.汇编
$ cmake --build . --target main .o
#输出二进制文件: Building cxx object CHakeFiles/steps_demo .dir i main .cpp.o
#链接
s cmake --build .
Scanning dependencies of target stepa_deno
[ 50%]LinkingCxx executable ateps_demo
[ 100% ]Built target steps_demo
#运行
./steps_demo
2.3.11 生成器表达式
附件位置:10.generator_expression
生成器表达式简单来说就是在CMake生成构建系统的时候根据不同配置动态生成特定的内容。有时用它可以让代码更加精简,我们介绍几种常用的。
需要注意的是,生成表达式被展开是在生成构建系统的时候,所以不能通过解析配置CMakeLists.txt阶段的nessage命令打印,可以用类似file(CENERATE OU7PUT “./generator_test.txt” CONTENT “ < < <BOOL:TRUE:TEST>")生成文件的方式间接测试。
在其最一般的形式中,生成器表达式是$<…>,尖括号中间可以是如下几种类型;
·条件表达式
·变量查询(variable-Query)
·目标查询(Target-Query)
·输出相关的表达式
#1.条件表达式:$<condition:true_string>,当condition为真时,返回true_string,否则返回空字符串
$<0:TEST>
$<1:TEST>
$<$<BOOL:TRUE>:TEST>
#2.变量查询〔variable-Query)
$<TARGET_EXITSTS:target>:判断目标是否存在
$<CONEFIG:Debug>:判断当前构建类型是否为Debug
#3.自标查询〔rarget-Query)
$<TARGET_FILE:target>:获取编译目标的文件路径
$<TARGET_FILE_NAME:target>:获取编译目标的文件名
4.输出相关表达式:用于在不同的环节使用不同参数,比如需要在insta11和bui1d环节分别用不同的参数,我可以这样写:
add_library (Foo ...)
target_include_directories(Foo
PUBLIC
$<BUILD_INTERFACE:${CHAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:$(CMARE_INSTATL_INCLUDEDIR)>
)
其中
<
B
U
I
L
D
I
N
T
E
R
F
A
C
E
:
<BUILD_INTERFACE:
<BUILDINTERFACE:<{CMAKE_CURRENT_SOURCE_DIR}>仅在build环节生效;
而
<
I
N
S
T
A
L
L
T
I
N
T
E
R
F
A
C
B
:
<INSTALLT_INTERFACB:
<INSTALLTINTERFACB:{CNAKE_INSTALA_INCLUDEDLR}>‘仅在install环节生效。通过设定不同阶段不同的参数,我们可以避免路径混乱的问题。
2.3.12 函数和宏
附件位置:11.function_macro
#定义一个宏,宏名为my _macro,没有参数
macro (my_macro)
message("宏内部的信息")
set(macro_var "宏内部变量test")
endmacro ( my_macro)
my_macro()
#定义一个函数,函数名为second_func,有两个参数
function(second_func argl arg2)
message("第一个参数:${arg1},第二个参数:${arg2}")
endfunction(second_func)
second_func("hello","666")
2.3.13 设置安装
附件位置:13.install_cemo
当需要发布项目时你需要指定项目文件的安装路径。下面的代码片段中,使用install安装demo_test,并分别将可执行文件安装在bin中,动态链接库和静态链接库都安装在lib,公共头文件安装在include。这里的路径都将添加$CMAKE_INSTALL_PREFIX;作为前缀(如果不设置CNAKE_INSTALE_PREFTX,则会安装到/usr/local目录下)。实现安装的功能在你需要发布你项目给其他人使用时,非常有用。
#设置安装
inatall(TARGETSdemo_test
RUNTIME DESTINATION bin #可执行文件
LIBRARY DESTINATION lib #动态库
ARCHIVE DESTINATIOH lib #静态库
PUBLIC_HEADER DESTINATIOH include #公共头文件
)
2.3.14 寻找依赖find_package
对于大部分支持了CMake的项目来说,均可以通过find_package找到对应的依赖库,参考附件∶13.find_demo
#使用find_package寻找<LibaryName>库,如果找到。一般都会有以下变量〈库作者设置)
<LibaryName>_FOUND: 表示是否找到
<LibaryName>_INCLUDE_DIR: 表示头文件目录
<LibaryName>_LIBRARIES: 表示库文件目录
假设我们编写了一个新的函数库,我们希望别的项目可以通过rind_package对它进行引用,我们有两种办法:
1.·编写一个Find《LibraryMame》.cmake,适用于导入非cmake安装的项目,参考附件:14.custom_find
2.·使用install安装,生成《LibraryName》Config.cmake文件,适用于导入自己开发的cmake项目,参考附件: 15.custom_install_demo
三、OpenCV CMake示例
3.1 读取图片
3.1.1安装OpenCV4.2.0
1、 安装依赖
# ubuntu16.04 默认自带安装
sudo apt-get install build-essential
# ubuntu16.04 除了git,其他默认自带安装
sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
# libdc1394-22-dev 需要安装,其他默认自带安装
sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
2、 下载源代码
# 克隆源代码
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git
# 选择4.2.0版本
cd opencv
git checkout 4.2.0
cd opencv_contrib
git checkout 4.2.0
3、编译安装包
cd opencv
mkdir build
cd build
cmake cmake -D CMAKE_BUILD_TYPE=RELEASE \
-DINSTALL_PYTHON_EXAMPLES=ON \
-DINSTALL_C_EXAMPLES=ON \
-DOPENCV_EXTRA_MODULES_PATH=home/hadoop/modules \
-DBUILD_EXAMPLES=ON ..
make
进行make时的,关键错误提示 opencv_contrib/modules/xfeatures2d/src/boostdesc.cpp:654:37: fatal error: boostdesc_bgm.i: No such file or directory compilation terminated.
解决:
进入build目录,查看CmakeDownloadLog.txt,查找boostdesc_bgm.i关键词,下载所需要的文件到opencv_contrib/modules/xfeatures2d/src/下,包含的文件有:
boostdesc_bgm.i
boostdesc_bgm_bi.i
boostdesc_bgm_hd.i
boostdesc_lbgm.i
boostdesc_binboost_064.i
boostdesc_binboost_128.i
boostdesc_binboost_256.i
vgg_generated_120.i
vgg_generated_64.i
vgg_generated_80.i
vgg_generated_48.i
下载缺少的文件,百度网盘:
链接:https://pan.baidu.com/s/18UN2rc4PZfgxu-TmvkP3dA
提取码:4u2j
继续make,随后报错
~/opencv-4.2.0/opencv_contrib/modules/xfeatures2d/test/test_features2d.cpp:51:62: fatal error: features2d/test/test_detectors_regression.impl.hpp: No such file or directory
compilation terminated.
modules/xfeatures2d/CMakeFiles/opencv_test_xfeatures2d.dir/build.make:134: recipe for target 'modules/xfeatures2d/CMakeFiles/opencv_test_xfeatures2d.dir/test/test_features2d.cpp.o' failed
make[2]: *** [modules/xfeatures2d/CMakeFiles/opencv_test_xfeatures2d.dir/test/test_features2d.cpp.o] Error 1
CMakeFiles/Makefile2:12703: recipe for target 'modules/xfeatures2d/CMakeFiles/opencv_test_xfeatures2d.dir/all' failed
make[1]: *** [modules/xfeatures2d/CMakeFiles/opencv_test_xfeatures2d.dir/all] Error 2
Makefile:149: recipe for target 'all' failed
make: *** [all] Error 2
解决办法
step.1 进入opencv源码目录~/opencv/modules/features2d/test/,拷贝test_detectors_regression.impl.hpp 及 test_descriptors_regression.impl.hpp 到 ~/opencv_contrib/modules/xfeatures2d/test/下
step.2 编辑 test_features2d.cpp ,修改include文件地址
// #include "features2d/test/test_detectors_regression.impl.hpp"
// #include "features2d/test/test_descriptors_regression.impl.hpp"
// 修改文件地址如下
#include "test_detectors_regression.impl.hpp"
#include "test_descriptors_regression.impl.hpp"
step3: 进入opencv源码目录~/opencv/modules/features2d/test/,拷贝test_descriptors_invariance.impl.hpp 及 test_detectors_invariance.impl.hpp和test_invariance_utils.hpp 到 ~/opencv_contrib/modules/xfeatures2d/test/下
step4:编辑 test_rotation_and_scale_invariance.cpp ,修改include文件地址
#include "test_detectors_invariance.impl.hpp" // main OpenCV repo
#include "test_descriptors_invariance.impl.hpp" // main OpenCV repo
继续执行make
顺利完成安装,上个段100%的安装进度信息
4、安装及配置
sudo make install
5、 添加修改环境变量
sudo gedit /etc/bash.bashrc
#在末尾添加如下内容
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
#保存,退出
source /etc/bash.bashrc
3.1.2测试OpenCV
附件位置:16.demo_opencv/
依赖和链接OpenCV与常规的添加依赖并没有太多不同,同时OpenCV提供了cmake find package的功能,因此我们可以通过find_package方便的定位opencv在系统中的位置和需要添加的依赖。
find_package(Opencv REQUIRED)
nessage("OPENCV INCLUDE DIRS: ${OpenCV_INCLUDE_DIRS}")
message("OPENCV LINK L工BRARIES: ${OpenCV_LIBS}")
构建CMakeLists
cmake_minimum_required(VERSION 3.10)
project(demo_opencv)
find_package(OpenCV REQUIRED)
if (OpenCV_FOUND)
message(STATUS "opencV library status :")
message(STATUS "libraries: ${OpenCV_LIBS}")
message(STATUS "include path: ${OpenCV_INCLUDE_DIRS}")
else()
message(FATAL_ERROR "Could not find Opencv")
endif()
add_executable(demo_opencv main.cpp)
target_include_directories(demo_opencv PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(demo_opencv ${OpenCV_LIBS})
main.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv )
{
if(argc != 2)
{
cout << "请输入图片路径" << endl;
}
else
{
Mat image;
image = imread(argv[1]);
Mat image_resize;
resize(image, image_resize, Size(400,400));
cvtColor(image_resize,image_resize,COLOR_BGR2GRAY);
imwrite("resized.png",image_resize);
cout << "图片处理完毕,并保存为resized.png" << endl;
}
return 0;
}
为方便构建项目并运行和调试,可以在vscode中添加配置文件
.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
//1.cmake 配置
{
"type": "cppbuild",
"label": "CMake配置",
"command": "cmake",
"args": [
"-DCMAKE_BUILD_TYPE=Debug",
"-S .",
"-B build"
],
"problemMatcher":"$msCompile",
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}"
}
},
// 2.cmake 构建
{
"type": "cppbuild",
"label": "CMake:构建",
"command": "cmake",
"args": [
"--build",
"build",
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}"
},
"dependsOn":[
"CMake配置"
]
},
//3. 删除Build目录
{
"type": "shell",
"label": "删除Build目录",
"command": "rm",
"args": [
"-rf",
"Build"
],
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}"
}
},
// 4.运行可执行文件
{
"type": "shell",
"label": "运行可执行文件",
"command": "./build/main",
"problemMatcher": "$msCompile",
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "${workspaceFolder}"
},
"dependsOn":[
"CMake:构建"
]
}
]
}
.vscode/launch.json
{
"type": "lldb",
"request": "launch",
"name": "C++ CMake Debug",
"program": "${workspaceRoot}/build/main",
"args": [],
"env": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": "CMake:构建" ,
}
3.2 读写本地视频流
CMakeLists:
cmake_minimum_required(VERSION 3.10)
project(2.video)
find_package(OpenCV REQUIRED)
if (OpenCV_FOUND)
message(STATUS "opencV library status :")
message(STATUS "libraries: ${OpenCV_LIBS}")
message(STATUS "include path: ${OpenCV_INCLUDE_DIRS}")
else()
message(FATAL_ERROR "Could not find Opencv")
endif()
find_package(gflags REQUIRED)
if(gflags_FOUND)
message(STATUS "gflags found")
message(STATUS "gflags include dir: ${gflags_INCLUDE_DIR}")
message(STATUS "gflags lib dir: ${gflags_LIBRARIES}")
else()
message(FATAL_ERROR "gflags not found")
endif()
include_directories(${OpenCV_INCLUDE_DIRS} ${gflags_INCLUDE_DIR})
link_libraries(${OpenCV_LIBS} ${gflags_LIBRARIES})
add_executable(2.video src/2.video.cpp)
2.video.cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
int main() {
// 打开摄像头
cv::VideoCapture capture("media/sample_1080p_h264.mp4");
cv::VideoCapture capture=cv::VideoCapture(rtsp1,cv::CAP_FFMPEG);
if (!capture.isOpened()) {
std::cout << "无法打开摄像头" << std::endl;
return -1;
}
// 获取摄像头的宽度和高度
int width = static_cast<int>(capture.get(cv::CAP_PROP_FRAME_WIDTH));
int height = static_cast<int>(capture.get(cv::CAP_PROP_FRAME_HEIGHT));
// 创建视频写入对象
std::string filename = "output/.mp4"; // 视频文件名
cv::VideoWriter writer(filename, cv::VideoWriter::fourcc('D', 'I', 'V', 'X'), 30, cv::Size(width, height));
if (!writer.isOpened()) {
std::cout << "无法创建视频文件" << std::endl;
return -1;
}
// 持续保存视频帧
while (true) {
cv::Mat frame;
capture.read(frame);
if (frame.empty()) {
std::cout << "无法获取视频帧" << std::endl;
break;
}
// 保存当前帧到视频文件
writer.write(frame);
// 显示当前帧
// cv::imshow("Video", frame);
// 按下 ESC 键退出循环
if (cv::waitKey(1) == 27) {
break;
}
}
// 释放资源
capture.release();
writer.release();
// 关闭窗口
cv::destroyAllWindows();
return 0;
}
3.2 读写网络视频流
CMkeLists:
cmake_minimum_required(VERSION 3.10)
project(2.video)
find_package(OpenCV REQUIRED)
if (OpenCV_FOUND)
message(STATUS "opencV library status :")
message(STATUS "libraries: ${OpenCV_LIBS}")
message(STATUS "include path: ${OpenCV_INCLUDE_DIRS}")
else()
message(FATAL_ERROR "Could not find Opencv")
endif()
find_package(gflags REQUIRED)
if(gflags_FOUND)
message(STATUS "gflags found")
message(STATUS "gflags include dir: ${gflags_INCLUDE_DIR}")
message(STATUS "gflags lib dir: ${gflags_LIBRARIES}")
else()
message(FATAL_ERROR "gflags not found")
endif()
include_directories(${OpenCV_INCLUDE_DIRS} ${gflags_INCLUDE_DIR})
link_libraries(${OpenCV_LIBS} ${gflags_LIBRARIES})
add_executable(4.rtsp_vedio src/4.rtsp_vedio.cpp)
4.rtsp_vedio.cpp:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <string>
int main()
{
std::string rtsp1 = "rtsp://localhost:8554/1ive1.sdp";
std::string rtsp2 = "rtsp://localhost:8554/1ive2.sdp";
std::string rtsp3 = "rtsp://localhost:8554/1ive3.sdp";
std::string rtsp4 = "rtsp://localhost:8554/1ive4.sdp";
//CAP_FFMPEG:使用ffmpeg解码
cv::VideoCapture stream1 = cv::VideoCapture(rtsp1,cv::CAP_FFMPEG);
cv::VideoCapture stream2 = cv::VideoCapture(rtsp2,cv::CAP_FFMPEG);
cv::VideoCapture stream3 = cv::VideoCapture(rtsp3,cv::CAP_FFMPEG);
cv::VideoCapture stream4 = cv::VideoCapture(rtsp4,cv::CAP_FFMPEG);
if (!stream1.isOpened() || !stream2.isOpened() || !stream3.isOpened() || !stream4.isOpened())
{
std::cout << "有视频流未打开" << std::endl;
}
cv::Mat frame1;
cv::Mat frame2;
cv::Mat frame3;
cv::Mat frame4;
cv::Mat H1,H2,V;
int width = static_cast<int>(stream1.get(cv::CAP_PROP_FRAME_WIDTH));
int height = static_cast<int>(stream1.get(cv::CAP_PROP_FRAME_HEIGHT));
std::string filename = "output/rtsp.mp4"; // 视频文件名
cv::VideoWriter writer(filename, cv::VideoWriter::fourcc('D', 'I', 'V', 'X'), 30, cv::Size(500, 300));
cv::namedWindow("rtsp",cv::WINDOW_AUTOSIZE);
while(true)
{
if(!stream1.read(frame1)||!stream2.read(frame2)||!stream3.read(frame3)||!stream4.read(frame4))
{
std::cout << "有视频未读取" << std::endl;
break;
}
cv::resize(frame1,frame1,cv::Size(250,150));
cv::resize(frame2,frame2,cv::Size(250,150));
cv::resize(frame3,frame3,cv::Size(250,150));
cv::resize(frame4,frame4,cv::Size(250,150));
cv::hconcat(frame1,frame2,H1);
cv::hconcat(frame3,frame4,H2);
cv::vconcat(H1,H2,V);
cv::imshow("rtsp",V);
writer.write(V);
if(cv::waitKey(1)==27)
{
break;
}
}
stream1.release();
stream2.release();
stream3.release();
stream4.release();
writer.release();
return 0;
}
可使用rtsp-sample-server模拟RTSP推流
安装ffmpeg6.0
#卸载旧版ffmpeg
sudo apt-get purge ffmpeg
# 安装依赖
apt install libavformat-dev
apt install libavcodec-dev
apt install libswresample-dev
apt install libswscale-dev
apt install libavutil-dev
apt install libsdl1.2-dev
# 下载x264
git clone https://code.videolan.org/videolan/x264.git
cd x264
./configure --disable-asm --enable-shared --enable-pic
make
sudo make install
# 下载ffmpeg6.0 地址https://ffmpeg.org/download.html#build-linux
cd ffmpeg
./configure --prefix=/usr/local/ffmpeg --enable-shared --disable-static --disable-doc --enable-gpl --enable-libx264
make
sudo make install
下载rtsp-sample-server:
weget https://github.com/bluenviron/mediamtx/releases/download/v0.23.6/mediamtx_v0.23.6_linux_amd64.tar.gz
sudo tar -zxvf mediamtx_v0.23.6_linux_amd64.tar.gz
chmod 755 ./ -R
写一个可执行文件start_server.sh,推流视频
./mediamtx mediamtx.yml &
ffmpeg -re -stream_loop -1 -i sample_1080p_h264.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/1ive1.sdp &
ffmpeg -re -stream_loop -1 -i sample_1080p_h264.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/1ive2.sdp &
ffmpeg -re -stream_loop -1 -i sample_1080p_h264.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/1ive3.sdp &
ffmpeg -re -stream_loop -1 -i sample_1080p_h264.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/1ive4.sdp &
先运行:
./start_server.sh
开始推流后,再运行C++可执行文件(本地查看)
./build/4.rtsp_vedio
VLC读取rtsp推流视频:(远程查看)
服务器端配置:
1、开启rtsp-server详见链接:3.2节
2、放行rtsp 8554端口:
// 安装firewalld
sudo apt-get install firewalld
//放行8554端口
sudo firewall-cmd --zone=public --add-port=8554/tcp --permanent
// 查看8554端口是否放行
sudo firewall-cmd --zone=public --query-port=8554/tcp --permanent
// 重启防火墙服务
sudo systemctl restart firewalld.service
客户端配置:
1、下载VLC media player
2、安装后打开,工具–>输入/编解码器–>选择RTS over RTSP(TCP)->保存
3、服务器端开启rtsp服务后,客户端用VLC播放推流来的视频
媒体–>打开网络串流–>输入url:rtsp://<客户端ipv4地址>:8554:<自定义结尾>
点击播放
3.3 人脸识别
1、下载预训练模型opencv_face_detector.pbtxt、opencv_face_detector_uint8.pb
https://github.com/ethand91/python-gender-age-detect/tree/master/weights
2、C++代码:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <string>
//初始化模型
const std::string tensorflowConfigFile = "/home/hadoop/Desktop/OpenCV/2.input_output/weights/opencv_face_detector.pbtxt";
const std::string tensorflowWleightFile = "/home/hadoop/Desktop/OpenCV/2.input_output/weights/opencv_face_detector_uint8.pb";
cv::dnn::Net net = cv::dnn::readNetFromTensorflow(tensorflowWleightFile,tensorflowConfigFile);
//检测并绘制矩形框
void detectDrawRect(cv::Mat &frame)
{
//获取图像的宽高
int frameHeight = frame.rows ;
int framewidth = frame.cols;
//预处理,resize + SwapRB +mean+ scale
cv::Mat inputBlob = cv::dnn::blobFromImage(frame, 1.9,cv::Size(300,300),cv::Scalar(104.0,177.0,123.0),false,false);//推理
net.setInput(inputBlob, "data" );
cv::Mat detection = net.forward ( "detection_out" );
//获取结果
cv::Mat detectionMat(detection.size[2],detection.size[3],CV_32F,detection.ptr<float>());
//遍历多个人脸结果
for (int i = 0; i < detectionMat.rows; i++)
{
//置信度
float confidence = detectionMat.at<float>(i,2);
if ( confidence > 0.2)
{
//两点坐标
int l = static_cast<int>(detectionMat.at<float>(i,3) * framewidth);
int t = static_cast<int>(detectionMat.at<float>(i,4) * frameHeight);
int r = static_cast<int>(detectionMat.at<float>(i,5) * framewidth);
int b = static_cast<int>(detectionMat.at<float>(i,6) * frameHeight);
cv::rectangle(frame, cv::Point(l,t),cv::Point(r,b),cv::Scalar(0,255,0),2);
}
}
}
void imageTest()
{
std::cout << "hello" << std::endl;
cv::Mat img = cv::imread("./media/person.jpg");
if(img.empty())
{
std::cout << "无法读取图片" << std::endl;
}
detectDrawRect(img);
cv::imshow("face",img);
cv::imwrite("./output/face_detect.jpg",img);
cv::waitKey(0);
cv::destroyAllWindows();
}
void videoTest()
{
cv::VideoCapture capture("./media/my_face.mp4");
cv::VideoWriter writer("./output/face.mp4", cv::VideoWriter::fourcc('H','2','6','4'), 30, cv::Size(640, 480));
cv::Mat frame;
if(capture.isOpened())
{
while(true)
{
capture.read(frame);
if(frame.empty())
{
std::cout << "视频播放完毕" << std::endl;
break;
}
cv::resize(frame,frame,cv::Size(640,480));
detectDrawRect(frame);
writer.write(frame);
cv::imshow("my_face",frame);
if(cv::waitKey(20)==27)
{
break;
}
}
capture.release();
writer.release();
cv::destroyAllWindows();
}
}
int main()
{
imageTest();
// videoTest();
return 0;
}