Jetson用のCUDAを有効にしたOpenCLおよびOpenCVをビルドする方法

##はじめに

これはどちらかというと、かなりチラシの裏的な備忘録です。
題名の通り、Jetso AGX XavierやNXなどでCUDAを有効にしたOpenCLやOpenCVをビルドすることが非常に大変だったので、覚書です。

##そもそも

JetsonシリーズはOpenCLに公式には対応していません。そこでpoCLという互換？のものを使用します。
また、OpenCVに関しては、標準でJetPackに含まれているものはOpenCLはおろかCUDAにすら対応したものではないので、自前で用意する必要があります。

この記事は基本的に以下の記事をXavierなどに対応させたものになります。

https://yunusmuhammad007.medium.com/build-and-install-opencv-4-5-3-on-jetson-nano-with-cuda-opencl-opengl-and-gstreamer-enable-6dc7141be272

##準備
OpenGL絡みでパッチを当てておく必要があるのと、必要なqt4-defaultを入れておきます。

```console
sudo apt-get install qt4-default
wget https://gist.githubusercontent.com/Muhammad-Yunus/2981f35eda97228525203b5d1be2570f/raw/064cdae27b3faf4a221315f1a876a43d7faff4ed/OpenGLHeader.patch
sudo patch -N /usr/local/cuda-10.2/targets/aarch64-linux/include/cuda_gl_interop.h OpenGLHeader.patch
```
また以下のファイルを編集して、コメントアウトをすることで、JetPack内に含まれているOpenCVが入らないようにします。

```console
vi /etc/apt/sources.list.d/nvidia-l4t-apt-source.list
```

そのあと、

```console
sudo apt update
sudo apt purge libopencv* opencv*
```

でOpenCVを消しておきます。

念の為CMakeも最新にしておきます。

```console
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null
echo "deb https://apt.kitware.com/ubuntu/ bionic main" | sudo tee -a /etc/apt/sources.list
sudo apt-get update
sudo apt-get install kitware-archive-keyring
sudo rm /etc/apt/trusted.gpg.d/kitware.gpg
sudo apt install cmake
```

##poCLのXavier/NX向けのビルド

poCLはClang/LLVMを使用してコンパイルを行います。コンパイルを行う際にCPUを指定する必要がありますが、上記のサイトのJetso Nanoのやり方では、CPUが異なるため、よろしくありません。（Jetson Nanoはcortex-a57でXavier/NXはcarmel）

そこで、Xavier/NXのCarmelに対応したClamg/LLVMの12を使用した方法でビルドする必要があります。

```console
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-12 main" | sudo tee -a /etc/apt/sources.list
sudo apt-get update
LLVM_VERSION=12
sudo apt install -y build-essential ocl-icd-libopencl1 cmake git pkg-config libclang-${LLVM_VERSION}-dev clang-${LLVM_VERSION} llvm-${LLVM_VERSION} make ninja-build ocl-icd-libopencl1 ocl-icd-dev ocl-icd-opencl-dev libhwloc-dev zlib1g zlib1g-dev clinfo dialog apt-utils libxml2-dev libclang-cpp${LLVM_VERSION}-dev libclang-cpp${LLVM_VERSION} llvm-${LLVM_VERSION}-dev libncurses5
```
Clang/LLVMを12が使われるように指定します。以下のスクリプトを取ってきて使うと楽なので、それを使います。

```console
wget https://gist.githubusercontent.com/junkdog/70231d6953592cd6f27def59fe19e50d/raw/92f0e73d2558402b7316021c1ab408b30e534de6/update-alternatives-clang.sh
chmod +x update-alternatives-clang.sh
sudo ./update-alternatives-clang.sh 12 50
```
以下のコマンドで12が選択されているか確認できます。

```console
update-alternatives --config clang
update-alternatives --config llvm-config
```

続いてpoCLをCloneしてきてビルドをしてきます。（ここでは1.7を使っています。）

```console
cd ~
git clone --single-branch --branch release_1_7 https://github.com/pocl/pocl.git
cd pocl
mkdir build
cd build
```
そしてビルドしてきます。

Jetson Nanoの場合

```console
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/pocl/ -DENABLE_CUDA=ON  -DCMAKE_BUILD_TYPE=Release -DLLC_HOST_CPU=cortex-a57  ..
make 
sudo make install
```
Jetson AGX Xavier/NXの場合

```console
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/pocl/ -DENABLE_CUDA=ON  -DCMAKE_BUILD_TYPE=Release -DLLC_HOST_CPU=carmel  ..
make 
sudo make install
```

終わったら以下のファイルを作成し編集します。

```console
mkdir -p /etc/OpenCL/vendors/
cd /etc/OpenCL/vendors/
sudo vi pocl.icd
```

以下の一行のみ記述

```console
/usr/local/pocl/lib/libpocl.so
```

きちんとpoCLがインストールできると以下のコマンドでOpenCLが使えるかどうかがわかります。

```console
clinfo
```

##OpenCVのCUDA有効にしたビルド

これは色々と手順が複雑であることもあるので、以下のスクリプトを作成して実行します。ここではJetPackに標準で入っている4.1.1をCUDA/CUDNN/OpenCLを有効にしてビルドします。
またsfmでエラーが出るときは以下を追加します。（エラーが出ないときは消せばOK）
-D BUILD_opencv_sfm=OFF"

```conole
#!/usr/bin/env bash
# 2019 Michael de Gans

set -e

# change default constants here:
readonly PREFIX=/usr/local  # install prefix, (can be ~/.local for a user install)
readonly DEFAULT_VERSION=4.1.1  # controls the default version (gets reset by the first argument)
readonly CPUS=$(nproc)  # controls the number of jobs

# better board detection. if it has 6 or more cpus, it probably has a ton of ram too
if [[ $CPUS -gt 5 ]]; then
    # something with a ton of ram
    JOBS=$CPUS
else
    JOBS=1  # you can set this to 4 if you have a swap file
    # otherwise a Nano will choke towards the end of the build
fi

cleanup () {
# https://stackoverflow.com/questions/226703/how-do-i-prompt-for-yes-no-cancel-input-in-a-linux-shell
-script
    while true ; do
        echo "Do you wish to remove temporary build files in /tmp/build_opencv ? "
        if ! [[ "$1" -eq "--test-warning" ]] ; then
            echo "(Doing so may make running tests on the build later impossible)"
        fi
        read -p "Y/N " yn
        case ${yn} in
            [Yy]* ) rm -rf /tmp/build_opencv ; break;;
            [Nn]* ) exit ;;
            * ) echo "Please answer yes or no." ;;
        esac
    done
}

setup () {
    #if [[ -d "build_opencv" ]] ; then
    #    echo "It appears an existing build exists in /tmp/build_opencv"
        #cleanup
    #fi
    mkdir build_opencv
    cd build_opencv
}

git_source () {
    echo "Getting version '$1' of OpenCV"
    git clone --depth 1 --branch "$1" https://github.com/opencv/opencv.git
    git clone --depth 1 --branch "$1" https://github.com/opencv/opencv_contrib.git
}

install_dependencies () {
    # open-cv has a lot of dependencies, but most can be found in the default
    # package repository or should already be installed (eg. CUDA).
    echo "Installing build dependencies."
    sudo apt-get update
    sudo apt-get dist-upgrade -y --autoremove
    sudo apt-get install -y \
        build-essential \
        cmake \
        git \
        gfortran \
        libatlas-base-dev \
        libavcodec-dev \
        libavformat-dev \
        libavresample-dev \
        libcanberra-gtk3-module \
        libdc1394-22-dev \
        libeigen3-dev \
        libglew-dev \
        libgstreamer-plugins-base1.0-dev \
        libgstreamer-plugins-good1.0-dev \
        libgstreamer1.0-dev \
        libgtk-3-dev \
        libjpeg-dev \
        libjpeg8-dev \
        libjpeg-turbo8-dev \
        liblapack-dev \
        liblapacke-dev \
        libopenblas-dev \
        libpng-dev \
        libpostproc-dev \
        libswscale-dev \
        libtbb-dev \
        libtbb2 \
        libtesseract-dev \
        libtiff-dev \
        libv4l-dev \
        libxine2-dev \
        libxvidcore-dev \
        libx264-dev \
        pkg-config \
        python-dev \
        python-numpy \
        python3-dev \
        python3-numpy \
        python3-matplotlib \
        qv4l2 \
        v4l-utils \
        v4l2ucp \
        qt4-default \
        libgtk2.0-dev \
        pkg-config \
        zlib1g-dev
}


configure () {
    local CMAKEFLAGS="
        -D BUILD_EXAMPLES=OFF
        -D BUILD_opencv_python2=ON
        -D BUILD_opencv_python3=ON
        -D CMAKE_BUILD_TYPE=RELEASE
        -D CMAKE_INSTALL_PREFIX=${PREFIX}
        -D BUILD_opencv_world=OFF
        -D CUDA_ARCH_BIN=5.3,6.2,7.2
        -D CUDA_ARCH_PTX=
        -D CUDA_FAST_MATH=ON
        -D CUDNN_VERSION='8.0'
        -D OPENCV_DNN_CUDA=ON
        -D WITH_CUBLAS=ON
        -D WITH_CUDA=ON
        -D WITH_CUDNN=ON
        -D OPENCV_EXTRA_MODULES_PATH=~/build_opencv/opencv_contrib/modules
        -D EIGEN_INCLUDE_PATH=/usr/include/eigen3 
        -D ENABLE_NEON=ON
        -D WITH_OPENCL=ON
        -D WITH_OPENMP=OFF
        -D WITH_TBB=ON
        -D WITH_1394=OFF
        -D OPENCV_ENABLE_NONFREE=ON
        -D OPENCV_GENERATE_PKGCONFIG=ON
        -D WITH_GSTREAMER=ON
        -D WITH_LIBV4L=ON
        -D WITH_OPENGL=ON
        -D WITH_GTK=ON
        -D WITH_QT=4
        -D BUILD_opencv_sfm=OFF"

    if [[ "$1" != "test" ]] ; then
        CMAKEFLAGS="
        ${CMAKEFLAGS}
        -D BUILD_PERF_TESTS=OFF
        -D BUILD_TESTS=OFF"
    fi

    echo "cmake flags: ${CMAKEFLAGS}"

    cd opencv
    mkdir build
    cd build
    cmake ${CMAKEFLAGS} .. 2>&1 | tee -a configure.log
}
main () {

    local VER=${DEFAULT_VERSION}

    # parse arguments
    if [[ "$#" -gt 0 ]] ; then
        VER="$1"  # override the version
    fi

    if [[ "$#" -gt 1 ]] && [[ "$2" == "test" ]] ; then
        DO_TEST=1
    fi

    # prepare for the build:
    setup
    install_dependencies
    git_source ${VER}

    if [[ ${DO_TEST} ]] ; then
        configure test
    else
        configure
    fi

    # start the build
    make -j${JOBS} 2>&1 | tee -a build.log

    if [[ ${DO_TEST} ]] ; then
        make test 2>&1 | tee -a test.log
    fi

    # avoid a sudo make install (and root owned files in ~) if $PREFIX is writable
    if [[ -w ${PREFIX} ]] ; then
        make install 2>&1 | tee -a install.log
    else
        sudo make install 2>&1 | tee -a install.log
    fi

    #cleanup --test-warning

}

main "$@"
```

## まとめ
ROSを使いたいときは、cv_bridgeなどがOpenCVの3を前提にしているので、自前でビルドする必要があります。
Jetsonはいろいろめんどくさいですが、これで一通りCUDAなどを活用した画像処理ができると思っていますが、本当にちゃんと動くかはよくわからないのでご注意くだされ。