【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;
}
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值