cover

问题背景

这个问题是我在做海赛C2比赛编写部署自动打靶算法遇到的。先简单叙述一下起因,由于我们C2队伍使用的是Jetson Nano来识别靶心,进而实现自动打靶功能,于是算法识别这方面就由我来进展。在网上参考了一些部署方案。最终选择了使用Deepstream架构运行yolov5n的trt模型进行识别检测。但是由于我们的需求不仅是检测到靶心,还要将检测到的靶心的位置传递给下位机stm32,因此我需要在该架构中加入串口通信模块,实现该功能。由于Deepstream在国内的相关资料很少,故需要自己去了解并实现添加该模块到这个框架中。而且其代码基本都是C++代码,过程中要用到cmake等编译工具,故而接触到了cmake相关文件的编写与使用,并写下该篇博客对其进行记录。

一、cmake是什么

CMake是一个跨平台的编译(Build)工具,可以用简单的语句来描述所有平台的编译过程。
CMake能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性。
假如我们有一个深度学习框架的部分工程列表,里面有超过40个互相调用的工程共同组成,一些用于生成库文件,一些用于实现逻辑功能。他们之间的调用关系复杂而严格,如果我想在这样复杂的框架下进行二次开发,显然只拥有它的源码是远远不够的,还需要清楚的明白这几十个项目之间的复杂关系,在没有原作者的帮助下进行这项工作几乎是不可能的。即使原作者给出了相关的结构文档,对新手来说建立工程的过程依旧是漫长而艰辛的,因此CMake的作用就凸显出来了。原作者只需要生成一份CMakeLists.txt文档,框架的使用者们只需要在下载源码的同时下载作者提供的CMakeLists.txt,就可以利用CMake,在“原作者的帮助下”进行工程的搭建。

二、cmake的使用

2.1 CMakeLists.txt的语法

先附上相关代码

cmake_minimum_required(VERSION 3.10)
project(yolov5)
add_definitions(-std=c++11)
add_definitions(-DAPI_EXPORTS)
option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)

# TODO(Call for PR): make cmake compatible with Windows
set(CMAKE_CUDA_COMPILER /usr/local/cuda/bin/nvcc)
enable_language(CUDA)

include_directories(/opt/ros/melodic/include)

# include and link dirs of cuda and tensorrt, you need adapt them if yours are different
# cuda
include_directories(/usr/local/cuda/include)
link_directories(/usr/local/cuda/lib64)
# tensorrt
# TODO(Call for PR): make TRT path configurable from command line
include_directories(/home/nvidia/TensorRT-8.2.5.1/include/)
link_directories(/home/nvidia/TensorRT-8.2.5.1/lib/)

include_directories(${PROJECT_SOURCE_DIR}/src/)
include_directories(${PROJECT_SOURCE_DIR}/plugin/)
file(GLOB_RECURSE SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp ${PROJECT_SOURCE_DIR}/src/*.cu)
file(GLOB_RECURSE PLUGIN_SRCS ${PROJECT_SOURCE_DIR}/plugin/*.cu)

add_library(myplugins SHARED ${PLUGIN_SRCS})
target_link_libraries(myplugins nvinfer cudart)

find_package(OpenCV)
include_directories(${OpenCV_INCLUDE_DIRS})

add_executable(yolov5_det yolov5_det.cpp ${SRCS})
target_link_libraries(yolov5_det nvinfer)
target_link_libraries(yolov5_det cudart)
target_link_libraries(yolov5_det myplugins)
target_link_libraries(yolov5_det ${OpenCV_LIBS})
target_link_libraries(yolov5_det /opt/ros/melodic/lib/libserial.so)

相关解释:
由于我也是浅浅地了解使用,就只介绍我理解的几个函数吧

  • project(yolov5) 表示定义一个名为yolov5的工程
  • include_directories(/opt/ros/melodic/include) 可以这样来理解,加入我们需要引入一个头文件<serial/serial.h>,但是这个头文件是在路径/opt/ros/melodic/include路径下,而你的文件路径却不在该路径下,如果使用#include "/opt/ros/melodic/include/serial/serial.h"的话未免太过麻烦了,但是如果在CmakeList.txt中加入include_directories(/opt/ros/melodic/include)一句,那么我们就可以使用#include "serial/serial.h"简单地引入该头文件了。
  • file(GLOB_RECURSE PLUGIN_SRCS ${PROJECT_SOURCE_DIR}/plugin/*.cu) 表示在项目源代码目录中的plugin子目录中查找所有扩展名为.cu的CUDA源文件,并将它们存储在PLUGIN_SRCS变量中
  • add_library(myplugins SHARED ${PLUGIN_SRCS}) 表示创建一个名为myplugins的共享库,并将PLUGIN_SRCS变量中的所有CUDA源文件链接到该库中。
  • link_directories(/home/nvidia/TensorRT-8.2.5.1/lib/) 是指将TensorRT库的路径添加到编译器的库搜索路径中,以便编译器可以找到所需的库文件
  • add_executable(yolov5_det yolov5_det.cpp ${SRCS}) 表示在当前路径下编译一个名为yolov5_det的可执行文件,该文件的源代码位于yolov5_det.cpp文件中,而其他源文件位于SRCS变量中。
  • target_link_libraries(yolov5_det /opt/ros/melodic/lib/libserial.so) 表示将名为yolov5_det的目标文件与名为libserial.so的库文件链接,以便在运行时可以使用该库文件中定义的函数和变量。

明白了上面这些语句的基本用法,我们就可以在源文件的基础上加上我们的代码,实现我们想要的功能了。

2.2 Makefile相关语法

有时候我们得到的文件中没有CMakeList.txt文件,但是会有Makefile文件,这时如果我们想修改一下代码以引入一些其他功能,也不是不可能,可以试着修改Makefile文件,尝试实现我们想要的功能。
先附上相关代码

CUDA_VER?=
ifeq ($(CUDA_VER),)
	$(error "CUDA_VER is not set")
endif

OPENCV?=
ifeq ($(OPENCV),)
	OPENCV=0
endif

CC:= g++
NVCC:=/usr/local/cuda-$(CUDA_VER)/bin/nvcc

CFLAGS:= -Wall -std=c++11 -shared -fPIC -Wno-error=deprecated-declarations
CFLAGS+= -I/opt/nvidia/deepstream/deepstream/sources/includes -I/usr/local/cuda-$(CUDA_VER)/include

ifeq ($(OPENCV), 1)
	COMMON= -DOPENCV
	CFLAGS+= $(shell pkg-config --cflags opencv4 2> /dev/null || pkg-config --cflags opencv)
	LIBS+= $(shell pkg-config --libs opencv4 2> /dev/null || pkg-config --libs opencv)
endif

CUFLAGS:= -I/opt/nvidia/deepstream/deepstream/sources/includes -I/usr/local/cuda-$(CUDA_VER)/include

LIBS+= -lnvinfer_plugin -lnvinfer -lnvparsers -L/usr/local/cuda-$(CUDA_VER)/lib64 -lcudart -lcublas -lstdc++fs
LFLAGS:= -shared -Wl,--start-group $(LIBS) -Wl,--end-group

INCS:= $(wildcard *.h)

SRCFILES:= $(filter-out calibrator.cpp, $(wildcard *.cpp))

ifeq ($(OPENCV), 1)
	SRCFILES+= calibrator.cpp
endif

SRCFILES+= $(wildcard layers/*.cpp)
SRCFILES+= $(wildcard *.cu)

TARGET_LIB:= libnvdsinfer_custom_impl_Yolo.so

TARGET_OBJS:= $(SRCFILES:.cpp=.o)
TARGET_OBJS:= $(TARGET_OBJS:.cu=.o)

all: $(TARGET_LIB)

%.o: %.cpp $(INCS) Makefile
	$(CC) -c $(COMMON) -o $@ $(CFLAGS) $<

%.o: %.cu $(INCS) Makefile
	$(NVCC) -c -o $@ --compiler-options '-fPIC' $(CUFLAGS) $<

$(TARGET_LIB) : $(TARGET_OBJS)
	$(CC) -o $@  $(TARGET_OBJS) $(LFLAGS)

clean:
	rm -rf $(TARGET_LIB)
	rm -rf $(TARGET_OBJS)

在上述代码中,我们只需要知道几个函数便可以实现我们想要的功能

  • CFLAGS+= -I/opt/nvidia/deepstream/deepstream/sources/includes -I/usr/local/cuda-$ 在这里表示定义一个名为CUFLAGS的变量,于指定编译CUDA源文件时要使用的选项。其中,-I选项用于指定要包含的头文件路径,/opt/nvidia/deepstream/deepstream/sources/includes和/usr/local/cuda-$(CUDA_VER)/include分别是两个包含的头文件路径。在Makefile中,变量用于存储值,可以在Makefile中的其他位置使用。如果我们想在待编译的cpp文件中引入<serial/serial.h>头文件,那么可以在其后加上这句:CFLAGS+= -I/opt/ros/melodic/include
  • LIBS+= -lnvinfer_plugin -lnvinfer -lnvparsers -L/usr/local/cuda-$(CUDA_VER)/lib64 -lcudart -lcublas -lstdc++fs 表示定义一个名为LIBS的变量,其中包含要链接的库文件列表,包括nvinfer_plugin、nvinfer、nvparsers、cudart、cublas和stdc++fs库文件。如果我们想链接库文件/opt/ros/melodic/lib/libserial.so,那么我们可以加上这句:LIBS+= -L/opt/ros/melodic/lib -lserial

一般来说了解了这两句就基本可以解决大部分问题了

2.3 LD_PRELOAD加载动态库

LD_PRELOAD是个环境变量,用于动态库的加载,动态库加载的优先级最高,一般情况下,其加载顺序为LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib>/usr/lib。程序中我们经常要调用一些外部库的函数,以open()和execve()为例,如果我们有个自定义这两函数,把它编译成动态库后,通过LD_PRELOAD加载,当程序中调用open函数时,调用的其实是我们自定义的函数。这个功能可以帮助我们实现在执行文件外部链接一些库文件。

2.4 相关编译命令

一般来说,我们会在CMakeList.txt的同级路径下创建一个build文件夹(文件夹命名可随意),然后进入到build文件夹后,打开终端,然后输入命令:cmake ..
之后待编译完成后,我们会发现在build文件夹下会出现Makefile文件,随后我们再在命令行中输入命令:make
成功之后,便会生成我们想要的可执行文件了。
相对路径如下所示:
----CMakeList.txt   ($ mkdir build)
----build          ($ cd build)($ cmake ..)
--------Makefile   ($ make)
--------相关可执行文件