第 22.4 節

How to write third-party library dependencies

0瀏覽次數0訪問次數--跳出率--平均停留

This section focuses specifically on how to install third-party libraries, how to find them in CMake, and how to place them in the template's Third-party dependencies section.

The principle of this template is:

# ========================
# Third-party dependencies
# ========================
find_package(Eigen3 REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PUBLIC
    Eigen3::Eigen
)

That is: if the communication parameters (like baud rate) set in the microcontroller program are not consistent with those configured in the serial port software on the PC, the communication between the microcontroller and the computer will fail.

  1. Who uses third-party libraries, writes find_package.
  2. Third-party dependencies are centralized in the Third-party dependencies section of this module.
  3. Prioritize linking modern CMake targets, such as Eigen3::Eigen and OpenCV::opencv_core.
  4. Don't dump all third-party libraries at the top level CMakeLists.txt.

find_package Basic Grammar

find_package(<PackageName> [version] [REQUIRED] [COMPONENTS components...])

common practice

find_package(Eigen3 REQUIRED)
find_package(OpenCV REQUIRED)
find_package(Boost REQUIRED COMPONENTS system filesystem)
find_package(PCL REQUIRED COMPONENTS common io)

Parameter Description:

parametereffect
<PackageName>Package name, such as Eigen3, OpenCV
versionMinimum required version or exact version
REQUIREDreport an error if not found
COMPONENTSOnly search for certain components

REQUIRED

find_package(Eigen3 REQUIRED)

If Eigen is not found, the CMake configuration stage will fail immediately.

If I don't write

find_package(Eigen3)

then you need to judge for yourself:

if(Eigen3_FOUND)
  target_link_libraries(my_target PUBLIC Eigen3::Eigen)
endif()

Engineering templates commonly contain REQUIRED because when dependencies are missing, it's better to fail early.

COMPONENTS

Libraries such as OpenCV, Boost, and PCL usually feature multiple modules.

For example:

find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui)

indicates only:

  1. opencv_core
  2. opencv_imgproc
  3. opencv_highgui

This is clearer than importing all of OpenCV directly.

When linking third-party libraries, also consider PUBLIC, PRIVATE, and INTERFACE.

KeywordsWhen to use
PRIVATEThird-party libraries are only used within .cpp, for example, headers do not expose it.
PUBLICThe header file includes third-party library types, so users need to be aware of them as well.
INTERFACEThe current target does not compile itself, it only passes downstream.

For example, if the header file for lib1 is just like this:

#pragma once

namespace lib1 {
void run_eigen_vector_example();
}

If the header file does not expose Eigen types, Eigen can be used with PRIVATE:

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    Eigen3::Eigen
)

If the header file is written as:

#pragma once

#include <Eigen/Dense>

namespace lib1 {
Eigen::Vector3d make_vector();
}

Therefore, when downstream includes this header file, it also requires the Eigen include path, so PUBLIC should be used:

target_link_libraries(${PREFIX}_src_lib
  PUBLIC
    Eigen3::Eigen
)

This template example uses PUBLIC to demonstrate dependency propagation; in actual projects, you can choose based on whether the header file exposes third-party types.

Eigen3

Eigen is a commonly used linear algebra library, primarily a header-only library.

Install

Ubuntu/Debian:

sudo apt install libeigen3-dev

Fedora:

sudo dnf install eigen3-devel

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(Eigen3 REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PUBLIC
    Eigen3::Eigen
)

Using C++

#include <Eigen/Dense>

Eigen::Vector3d v(1.0, 2.0, 3.0);

Visibility recommendations

SceneLinking method
Eigen is only used in .cpp.PRIVATE Eigen3::Eigen
The header file exposes Eigen::Matrix and Eigen::Vector.PUBLIC Eigen3::Eigen

OpenCV4

OpenCV is a computer vision library with many modules; it's recommended to introduce by components.

Install

Ubuntu/Debian:

sudo apt install libopencv-dev

Fedora:

sudo dnf install opencv-devel

Common Components

componenteffect
coreBasic data structures, such as cv::Mat
imgprocimage processing
imgcodecsRead and write images
highguiSimple window display
videoioCamera and Video I/O
calib3dCamera calibration, geometry
features2dfeature points
dnnDNN inference module
# ========================
# Third-party dependencies
# ========================
find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs highgui)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    opencv_core
    opencv_imgproc
    opencv_imgcodecs
    opencv_highgui
)

Some OpenCV installations provide imported targets like OpenCV::opencv_core which you can also use.

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    OpenCV::opencv_core
    OpenCV::opencv_imgproc
    OpenCV::opencv_imgcodecs
    OpenCV::opencv_highgui
)

If your system doesn't have these OpenCV:: targets, then use the previous opencv_core format.

CMake Approach: Simple and Brute-Force Version

find_package(OpenCV REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    ${OpenCV_LIBS}
)

This approach works, but it's not as clear as the component-based method. For tutorials, I recommend prioritizing component-based writing.

Using C++

#include <opencv2/opencv.hpp>

cv::Mat image = cv::imread("test.png");

Boost

Boost is a large collection of C++ libraries. Here are system and filesystem as examples.

Install

Ubuntu/Debian:

sudo apt install libboost-all-dev

Fedora:

sudo dnf install boost-devel

If you only want to install a few components, Ubuntu/Debian also allows you to install similar ones on demand:

sudo apt install libboost-system-dev libboost-filesystem-dev

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(Boost REQUIRED COMPONENTS system filesystem)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    Boost::system
    Boost::filesystem
)

Using C++

#include <boost/filesystem.hpp>

boost::filesystem::path p{"."};

Note: If using C++17's std::filesystem, Boost.Filesystem is often unnecessary in many scenarios.

PCL

PCL is the point cloud library, commonly used in robotics, 3D perception, and SLAM projects.

Install

Ubuntu/Debian:

sudo apt install libpcl-dev

Fedora:

sudo dnf install pcl-devel

Common Components

componenteffect
commonpoint type, basic tools
ioReading and writing PCD/PLY files and similar formats.
filtersfiltering
featuresfeature
registrationregistration
segmentationDivision
visualizationVisualization

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(PCL REQUIRED COMPONENTS common io filters)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    ${PCL_LIBRARIES}
)

target_include_directories(${PREFIX}_src_lib
  PRIVATE
    ${PCL_INCLUDE_DIRS}
)

target_compile_definitions(${PREFIX}_src_lib
  PRIVATE
    ${PCL_DEFINITIONS}
)

Some PCL versions also provide imported targets. If your environment supports them, you can prioritize using them as follows:

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    PCL::common
    PCL::io
    PCL::filters
)

The actual selection is based on the results provided by the local service find_package(PCL ...).

fmt

fmt is a formatted output library, and the style of C++20 std::format also derives from it.

Install

Ubuntu/Debian:

sudo apt install libfmt-dev

Fedora:

sudo dnf install fmt-devel

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(fmt REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    fmt::fmt
)

Using C++

#include <fmt/core.h>

auto s = fmt::format("value = {}", 42);

spdlog

spdlog is a commonly used logging library.

Install

Ubuntu/Debian:

sudo apt install libspdlog-dev

Fedora:

sudo dnf install spdlog-devel

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(spdlog REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    spdlog::spdlog
)

Using C++

#include <spdlog/spdlog.h>

spdlog::info("hello {}", "spdlog");

yaml-cpp

yaml-cpp is commonly used to read configuration files.

Install

Ubuntu/Debian:

sudo apt install libyaml-cpp-dev

Fedora:

sudo dnf install yaml-cpp-devel

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(yaml-cpp REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    yaml-cpp::yaml-cpp
)

Some older environments may have the target name yaml-cpp. If yaml-cpp::yaml-cpp is not present, adjust according to the error message.

nlohmann_json

nlohmann_json is a commonly used JSON library, typically a header-only library.

Install

Ubuntu/Debian:

sudo apt install nlohmann-json3-dev

Fedora:

sudo dnf install json-devel

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(nlohmann_json REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    nlohmann_json::nlohmann_json
)

Using C++

#include <nlohmann/json.hpp>

nlohmann::json data;
data["name"] = "cmake_template";

Threads

The C++ standard threading library typically does not require installing additional packages, but it is recommended to use CMake's Threads package during linking.

Install

Generally, no separate installation is required.

If the compiler toolchain is missing:

Ubuntu/Debian:

sudo apt install build-essential

Fedora:

sudo dnf install gcc gcc-c++ make

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(Threads REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    Threads::Threads
)

Using C++

#include <thread>

std::thread worker([] {
    // do work
});
worker.join();

OpenMP

OpenMP is a programming interface for multi-threaded parallel computing in shared-memory systems. It allows developers to parallelize code using pragmas and runtime library routines.

Install

Ubuntu/Debian:

sudo apt install libomp-dev

Fedora:

sudo dnf install libgomp

CMake syntax

# ========================
# Third-party dependencies
# ========================
find_package(OpenMP REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    OpenMP::OpenMP_CXX
)

Using C++

#include <omp.h>

#pragma omp parallel for
for (int i = 0; i < 100; ++i) {
    // parallel work
}

Sophus

Sophus is a Lie group and algebra library commonly used in SLAM and robot pose calculations.

Install

Often, distribution repositories don't provide the appropriate version, so the common practice is to install from source or rely on dependency management by ROS or the project.

If system repositories are available, you can try searching:

apt search sophus
dnf search sophus

CMake syntax

If already installed and providing CMake config:

# ========================
# Third-party dependencies
# ========================
find_package(Sophus REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    Sophus::Sophus
)

Sophus often depends on Eigen. If your code also directly uses Eigen, you can explicitly write:

find_package(Eigen3 REQUIRED)
find_package(Sophus REQUIRED)

target_link_libraries(${PREFIX}_src_lib
  PUBLIC
    Eigen3::Eigen
  PRIVATE
    Sophus::Sophus
)

Common dependencies quick reference

libraryUbuntu/DebianFedoraCMake searchCommon Link Targets
Eigen3libeigen3-deveigen3-develfind_package(Eigen3 REQUIRED)Eigen3::Eigen
OpenCVlibopencv-devopencv-develfind_package(OpenCV REQUIRED COMPONENTS ...)opencv_core etc.
Boostlibboost-all-devboost-develfind_package(Boost REQUIRED COMPONENTS ...)Boost::system etc.
PCLlibpcl-devpcl-develfind_package(PCL REQUIRED COMPONENTS ...)${PCL_LIBRARIES} or PCL::common
fmtlibfmt-devfmt-develfind_package(fmt REQUIRED)fmt::fmt
spdloglibspdlog-devspdlog-develfind_package(spdlog REQUIRED)spdlog::spdlog
yaml-cpplibyaml-cpp-devyaml-cpp-develfind_package(yaml-cpp REQUIRED)yaml-cpp::yaml-cpp
nlohmann_jsonnlohmann-json3-devjson-develfind_package(nlohmann_json REQUIRED)nlohmann_json::nlohmann_json
ThreadsUsually no need to install separatelyUsually no need to install separatelyfind_package(Threads REQUIRED)Threads::Threads
OpenMPlibomp-devlibgompfind_package(OpenMP REQUIRED)OpenMP::OpenMP_CXX

Package names may vary slightly across different distributions and versions. If installation fails, first try using:

apt search <关键>
dnf search <关键>

Confirming the package name.

Incorporating both Eigen and OpenCV into a single module involves handling dependencies for two distinct, yet often complementary, libraries. Eigen is primarily used for linear algebra and matrix operations, while OpenCV focuses on computer vision and image processing.

Here’s a general guide for integrating them into a C++ or Python project:

1. Environment Setup & Installation

  • C++: Both libraries must be installed and findable by your build system. You can install them via system package managers (e.g., apt, brew) or build them from source. Most modern C++ projects use a package manager like vcpkg or Conan, or rely on CMake to locate them via find_package().
  • Python: Use pip for installation:
    pip install numpy  # Eigen is typically used via NumPy bindings in Python
    pip install opencv-python
    

2. Project Configuration (CMake Example)

For a C++ project using CMake, your CMakeLists.txt would include directives to find and link both libraries:

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# Find the packages
find_package(Eigen3 3.3 REQUIRED)
find_package(OpenCV 4.0 REQUIRED)

# Define your executable or library
add_executable(my_module main.cpp)

# Link against both
target_link_libraries(my_module PRIVATE ${OpenCV_LIBS} Eigen3::Eigen)

# Ensure include directories are available
target_include_directories(my_module PRIVATE
    ${OpenCV_INCLUDE_DIRS}
    # Eigen's include directory is handled automatically by the imported target
)

3. Code Usage (C++ Example)

In your source code, you can freely mix types and functions from both libraries. A common use case is converting data between OpenCV's cv::Mat and Eigen's matrices.

#include <opencv2/opencv.hpp>
#include <Eigen/Dense>
#include <iostream>

int main() {
    // 1. Create an OpenCV image
    cv::Mat cv_img(3, 3, CV_64F, cv::Scalar(0));
    cv_img.at<double>(0, 0) = 1.0;

    // 2. Convert OpenCV Mat to Eigen Matrix
    Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
        eigen_map(cv_img.ptr<double>(), cv_img.rows, cv_img.cols);
    Eigen::MatrixXd eigen_matrix = eigen_map;

    // 3. Perform an Eigen operation (e.g., matrix multiplication)
    Eigen::MatrixXd result = eigen_matrix * eigen_matrix.transpose();

    // 4. Convert the result back to OpenCV Mat for display/saving
    cv::Mat cv_result(result.rows(), result.cols(), CV_64F);
    Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
        result_map(cv_result.ptr<double>(), result.rows(), result.cols());
    result_map = result;

    // Use OpenCV to print or process the result
    std::cout << "OpenCV Mat result:\n" << cv_result << std::endl;

    return 0;
}

4. Code Usage (Python Example)

In Python, the integration is seamless because both libraries use NumPy arrays as their primary dense data structure.

import numpy as np
import cv2

# 1. Create a NumPy array (Eigen-like linear algebra via NumPy)
eigen_matrix = np.array([[1.0, 2.0], [3.0, 4.0]])

# 2. Use it directly with OpenCV
# OpenCV functions accept NumPy arrays
processed = cv2.GaussianBlur(eigen_matrix.astype(np.float32), (5, 5), 0)

# 3. Perform Eigen/NumPy-style operations
result = np.linalg.inv(eigen_matrix)  # Matrix inversion

print("NumPy array result:\n", result)

Key Points:

  • C++: The primary challenge is ensuring both libraries are correctly found and linked by the build system. Using modern CMake targets (Eigen3::Eigen, ${OpenCV_LIBS}) is the recommended approach.
  • Python: The ecosystem is unified around NumPy arrays. Conversion between cv::Mat and Eigen matrices is straightforward in C++, and nearly automatic in Python.
  • Type Conversion: Be mindful of data types (e.g., CV_64F in OpenCV corresponds to double in Eigen). Ensure matrices are in a compatible format (like row-major for OpenCV and Eigen's RowMajor mapping).
  • Documentation: Refer to the official tutorials for both libraries: Eigen Tutorials and OpenCV Tutorials.

This combination is powerful for robotics, computer vision, and scientific computing projects where efficient linear algebra is needed alongside image processing.

For example lib1 simultaneously using Eigen and OpenCV:

# ========================
# Third-party dependencies
# ========================
find_package(Eigen3 REQUIRED)
find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs)

target_link_libraries(${PREFIX}_src_lib
  PUBLIC
    Eigen3::Eigen
  PRIVATE
    opencv_core
    opencv_imgproc
    opencv_imgcodecs
)

If the header file does not expose Eigen types, you can also change Eigen to PRIVATE:

target_link_libraries(${PREFIX}_src_lib
  PRIVATE
    Eigen3::Eigen
    opencv_core
    opencv_imgproc
    opencv_imgcodecs
)
# 顶层 CMakeLists.txt
find_package(Eigen3 REQUIRED)
find_package(OpenCV REQUIRED)
find_package(PCL REQUIRED)

Question

  1. The top level is getting messier.
  2. Not sure which module actually uses which library.
  3. When deleting modules, it's easy to accidentally miss dependencies.
  4. Newcomers struggle to understand dependencies when reading projects.
include_directories(${OpenCV_INCLUDE_DIRS})
link_libraries(${OpenCV_LIBS})

Question

  1. Affects all targets.
  2. Dependencies are unclear.
  3. This could accidentally introduce include ordering issues.

Modern CMake Recommendations:

target_link_libraries(my_target
  PRIVATE
    opencv_core
)

If a library provides an imported target, include paths and link libraries will automatically propagate with the target.

library not found in find_package

Step 1: Check if system packages are installed.

dpkg -l | grep eigen
rpm -qa | grep eigen

Step 2: Check that CMake can detect the config file

Common file names:

Eigen3Config.cmake
OpenCVConfig.cmake
PCLConfig.cmake
fmtConfig.cmake

Step 3: Manually Specify Search Path

If the library is installed in a non-standard directory, you can add the following during configure:

cmake --preset linux-debug -DCMAKE_PREFIX_PATH=/opt/some_library

or add in the preset:

"CMAKE_PREFIX_PATH": "/opt/some_library"

Multiple paths can be separated by semicolons:

"CMAKE_PREFIX_PATH": "/opt/lib_a;/opt/lib_b"

Step 4: Check CMake error messages

CMake will usually tell you which config file is missing, for example:

Could not find a package configuration file provided by "OpenCV"

First, check in this case:

  1. Whether the development package is installed.
  2. Is the package name correct?
  3. Whether it was installed to a non-standard path.
音乐页