最近由于业务需求,接触到了Jetson边缘AI计算主板,博主使用的是Jetson Orin NX 16GB这个版本,可以看到其算力达到了100TOPS,这是一个非常恐怖的算力了,接下来便是博主对其的环境配置过程,博主要在该主板上运行一个旋转检测模型,过程如下:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_CUDA

Jetson使用的是Arm架构,所以在配置时会存在差异

在使用Jetson系列设备时,为了实时查看CPUGPU,我们首先应该安装Jtop软件

1. 安装Jtop监测软件

1.1 安装 pip3

因为我们需要使用pip3来安装jtop,所以需要在系统中先安装pip3

sudo apt install python3-pip
  • 1.

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_CUDA_02

1.2 安装 jtop
sudo -H pip3 install -U jetson-stats
  • 1.

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_分类_03

随后直接输入jtop即可:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_bc_04

随后我们点击INFO,查看系统信息:
可用看到,此时CUDAcuDNN都是没有的,OpenCV也是不支持CUDA加速的。

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_CUDA_05

那么,我们接下来该怎么做呢,难道要像WindowUbuntu下那样安装CUDAcudNN吗,不不不,Jetson为方便开发者,提供了 Jetpack开发套件,其帮了我们大忙

2. 安装JETPACK套件

为啥安装 Jetpack,那就是 JetpackNvidiaJetson系列开发板开发的一款软件开发包,常用的开发工具基本都包括了,并在在安装 Jetpack的时候,会自动安装匹配版本的CUDAcuDNNTensorRT等。

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_bc_06

我们耐心等待即可,安装完成后,再次调用 JTop 查看,此时我们看到CUDAcuDNN已经装好了,接下来便是对其进行配置,并重新编译OpenCV,使其能够支持CUDA加速

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_c++_07

目前,我们只是安装了CUDA和cuDNN,接下来我们需要对其进行配置

3. CUDA与cuDNN配置

cuDNN 默认安装路径在 /usr/lib/aarch64-linux-gnuCUDA 默认安装路径在 /usr/local/cuda

3.1 CUDA配置

配置Cuda环境变量:CUDA 已经默认安装在了/usr/local/cuda下,运行如下指令:

sudo vim ~/.bashrc		# 打开~/.bashrc

#在文本末输入如下代码:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64
export PATH=$PATH:/usr/local/cuda/bin
export CUDA_HOME=$CUDA_HOME:/usr/local/cuda
 
source ~/.bashrc	#保存环境变量
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_opencv_08

我们使用nvcc-V查看一下CUDA配置:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_bc_09

3.2 cuDNN配置

虽然安装了cuDNN,但没有将对应的头文件、库文件放到cuda目录。

cuDNN的头文件在:/usr/include,库文件位于:/usr/lib/aarch64-linux-gnu。将头文件与库文件复制到cuda目录下:

# 复制文件到cuda目录下
cd /usr/include && sudo cp cudnn* /usr/local/cuda/include
cd /usr/lib/aarch64-linux-gnu && sudo cp libcudnn* /usr/local/cuda/lib64
  • 1.
  • 2.
  • 3.

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_CUDA_10

随后创建软链接,这个步骤不能少,否则在OpenCV 编译时会报错,因为其找不到cuDNN

# 修改文件权限,修改复制完的头文件与库文件的权限,所有用户都可读,可写,可执行:
sudo chmod 777 /usr/local/cuda/include/cudnn.h 
sudo chmod 777 /usr/local/cuda/lib64/libcudnn*

# 重新软链接,这里的9.3.0和9对应安装的cudnn版本号和首数字
cd /usr/local/cuda/lib64

sudo ln -sf libcudnn.so.9.3.0 libcudnn.so.9

sudo ln -sf libcudnn_ops_train.so.9.3.0 libcudnn_ops_train.so.9
sudo ln -sf libcudnn_ops_infer.so.9.3.0 libcudnn_ops_infer.so.9

sudo ln -sf libcudnn_adv_train.so.9.3.0 libcudnn_adv_train.so.9
sudo ln -sf libcudnn_adv_infer.so.9.3.0 libcudnn_adv_infer.so.9

sudo ln -sf libcudnn_cnn_train.so.9.3.0 libcudnn_cnn_train.so.9
sudo ln -sf libcudnn_cnn_infer.so.9.3.0 libcudnn_cnn_infer.so.9

sudo ldconfig
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
3.3 测试cuDNN

执行下面的命令:

sudo cp -r /usr/src/cudnn_samples_v9/ ~/  #将系统中自带的 cuDNN 示例代码文件夹 cudnn_samples_v8 复制到你的用户主目录(~/)  方便测试
cd ~/cudnn_samples_v9/mnistCUDNN
sudo chmod 777 ~/cudnn_samples_v9  #给整个 cudnn_samples_v8 目录赋予“所有用户”完全权限(读、写、执行
sudo make clean && sudo make  #清理之前的编译产物并重新编译示例程序
./mnistCUDNN
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

默认会复制到这个路径下:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_c++_11

如果测试通过的话,会最终输出:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_CUDA_12

如果在执行sudo make clean && sudo make时报错:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_opencv_13

那么就表示缺少对应的库,安装下面的库即可:

sudo apt-get install libfreeimage3 libfreeimage-dev
  • 1.

安装完成之后再进行sudo make,基本上就可以编译成功了。

4. OpenCV with CUDA编译

先前我们安装的OpenCV无法通过CUDA编译,因此我们要考虑是否是OpenCV版本不匹配导致的,先前我们按照的是OpenCV 4.9,而我们的CUDAcuDNN版本分别是12.69.3,我们需要查看一下与之匹配的OpenCV版本:

理论上博主使用OpenCV 4.9 应该是没有问题的,奈何就是不行,果断使用最新版本的OpenCV,即4.13,直接上就完了。

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_c++_14

4.1 运行 CMake 配置

下载对应的 opencv 源码和 opencv_contrib 源码,然后创建,这里博主把 opencv_contrib 放到了opencv 下面,然后创建build文件目录,开始CMake配置。
注意,一定要下载opencv_contrib的源码,否则会报错,下面博主在执行时便遇到了这个问题。

因为OpenCV 的一些 CUDA 功能(如内存管理、底层工具)被移到了 opencv_contrib 仓库中的 cudev 模块中,所以我们需要下载opencv_contrib 将下载后的opencv_contrib放到opencv文件夹下:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_c++_15

随后切换到build文件夹下,执行下面的脚本:

cmake -D CMAKE_BUILD_TYPE=RELEASE \
      -D CMAKE_INSTALL_PREFIX=/usr/local \
      -D WITH_CUDA=ON \
      -D OPENCV_DNN_CUDA=ON \
      -D ENABLE_FAST_MATH=ON \
      -D CUDA_FAST_MATH=ON \
      -D WITH_CUBLAS=ON \
      -D OPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules \
      -D BUILD_opencv_cudev=ON \
      ..
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

完成后的效果如下:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_分类_16

4.2 OpenCV with CUDA 编译与安装

生成CMake文件后,进行编译并安装

make -j$(nproc)  #调用最大CPU可用数量,用于加速make编译
  • 1.

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_bc_17

这个过程会比较漫长,预计得半个多小时 。。。。。

最终编译完成后效果:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_bc_18

在完成后,我们执行安装命令:

sudo make install
  • 1.

安装成功:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_CUDA_19

安装完成后,我们就可以进行测试了,我们依旧选择一个分类模型进行测试:cd/opencv/samples/cpp/example_cmake目录下,然后打开opencv_example.cpp文件,替换为下面的代码,这个代码执行的便是 加载分类ONNX模型进行推理。

#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <iostream>
#include <vector>
#include <cmath>

// Softmax 函数
void softmax(const float* data, float* output, int size) {
    float max_val = -INFINITY;
    for (int i = 0; i < size; ++i) {
        max_val = std::max(max_val, data[i]);
    }

    float sum_exp = 0.0f;
    for (int i = 0; i < size; ++i) {
        sum_exp += std::exp(data[i] - max_val);
    }

    for (int i = 0; i < size; ++i) {
        output[i] = std::exp(data[i] - max_val) / sum_exp;
    }
}

int main() {
    // 检查是否有可用的 CUDA 设备
    if (cv::cuda::getCudaEnabledDeviceCount() == 0) {
        std::cerr << "没有检测到 CUDA 设备,请检查 OpenCV 是否启用了 WITH_CUDA 和 OPENCV_DNN_CUDA" << std::endl;
        return -1;
    } else {
        std::cout << "CUDA is Used,Detected " << cv::cuda::getCudaEnabledDeviceCount() << "  GPU" << std::endl;
    }

    // 加载模型
    std::string modelPath = "juan_cls.onnx";
    cv::dnn::Net net = cv::dnn::readNetFromONNX(modelPath);

    if (net.empty()) {
        std::cerr << "无法加载模型,请检查路径是否正确" << std::endl;
        return -1;
    }

    // 设置网络后端为 CUDA
    net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
    net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA);

    // 图像输入
    cv::Mat img = cv::imread("img.jpg");
    if (img.empty()) {
        std::cerr << "无法加载图像 img.jpg" << std::endl;
        return -1;
    }

    // 预处理
    int inputWidth = 640;
    int inputHeight = 640;
    cv::Mat blob = cv::dnn::blobFromImage(img, 1.0, cv::Size(inputWidth, inputHeight),
                                         cv::Scalar(), true, false);
    net.setInput(blob);

    // 推理(GPU)
    cv::Mat out = net.forward();
    std::cout << "Inference Shape: " << out.size << std::endl;

    // 假设输出是 [1 x C x H x W]
    int batchSize = out.size[0];   // 1
    int channels = out.size[1];    // 2
    int height = out.size[2];      // 640
    int width = out.size[3];       // 640

    std::vector<float> avgScores(channels, 0.0f);

    for (int c = 0; c < channels; ++c) {
        double sum = 0.0;
        float* ptr = out.ptr<float>(0, c);  // batch=0, channel=c
        for (int h = 0; h < height; ++h) {
            for (int w = 0; w < width; ++w) {
                sum += ptr[h * width + w];
            }
        }
        avgScores[c] = static_cast<float>(sum / (height * width));
    }

    // Softmax 归一化
    float probs[2];
    softmax(avgScores.data(), probs, 2);

    // 输出结果
    std::cout.precision(4);
    std::cout << std::fixed;
    std::cout << "\nResult" << std::endl;
    std::cout << "Cls 0 Score: " << probs[0] << std::endl;
    std::cout << "Cls 1 Score: " << probs[1] << std::endl;
    std::cout << "Cls: " << (probs[0] > probs[1] ? 0 : 1) << std::endl;

    return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.

随后执行下面的命令:

cmake .
make
./opencv_example
  • 1.
  • 2.
  • 3.

最终效果如下,可以看到检测到了一个GPU,并且在执行时能够明显感觉到速度提升了。

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_bc_20

此时我们再使用jtop查看环境,可用看到此时的OpenCV便是可用使用CUDA的了。

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_opencv_21

踩坑

当我们没有下载opencv_contrib代码时,我们在CMake时执行这段编译代码:

cmake -D CMAKE_BUILD_TYPE=RELEASE \
      -D CMAKE_INSTALL_PREFIX=/usr/local \
      -D WITH_CUDA=ON \
      -D OPENCV_DNN_CUDA=ON \
      -D ENABLE_FAST_MATH=ON \
      -D CUDA_FAST_MATH=ON \
      -D WITH_CUBLAS=ON \
      -D BUILD_opencv_cudacodec=OFF \
      -D BUILD_EXAMPLES=OFF ..
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_bc_22

会发生报错:

Jetson边缘计算主板:Ubuntu 环境配置 CUDA 与 cudNN 推理环境 + OpenCV 与 C++ 进行目标分类_分类_23