docker上でyolov3の環境を構築してGPUで動かす

# はじめに
## できること
docker上でopencv・GPUを使用してdarknet/yolov3を動かせるようになる。


## 本記事の流れ
以下の流れでdokcer container上でyoloV3が動く環境を作り動かしていきます。
- opencvのインストールとbuild
- darknetのインストールと重みのダウウンロード
- 実行


# ホスト環境
前提としてdockerが使える環境であることを想定しています。

- windows Pro11 
- Docker Compose version v2.20.2-desktop.1
- GPU: RTX3070
- nvidia-driver: 536.19
- 

# コンテナの環境
- OS: ubuntu20.04
- cuda:11.2.0
- cudnn: 8.0
- OpenCV: 4.5.1
- Python3.8


# ディレクトリ構造
```
yolov3
├── Dockerfile
├── docker-compose.yml
└── workspace
    └── darknet
        ├── Makefile
        ├── cfg
        ├── darknet
        ├── darknet.py
        ├── darknet_images.py
        ├── darknet_video.py
        ├── data
        ├── json_mjpeg_streams.sh
        ├── predictions.jpg
        └── yolov3.weights
```

# docker環境の構築
## コンテナの起動
`docker-compose.yml`と`Dockerfile`は以下のようになります。

```Dockerfile:Dockerfile
FROM nvcr.io/nvidia/cuda:11.2.0-cudnn8-devel-ubuntu20.04

ENV DEBIAN_FRONTEND=noninteractive

ARG OPENCV_VERSION="4.5.1"
ARG PYTHON_VERSION="3.8"

# cudaのレポジトリーのキーが変更されたので既存の情報を削除
RUN rm /etc/apt/sources.list.d/cuda.list

# 必要ツール
RUN apt-get update && apt-get -y upgrade &&\
    apt-get install -y --no-install-recommends \
    vim unzip byobu wget tree git cmake\
    && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

# pythonのインストール
RUN apt-get update && apt-get install -y python3-dev python3-pip &&\
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
RUN pip3 install -U pip
RUN pip3 install -U numpy

# OpenCVのためのツール
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc g++ libpng-dev libjpeg-dev libopenexr-dev libtiff-dev libwebp-dev libgtk-3-dev \
    libavformat-dev libswscale-dev libhdf5-serial-dev qt5-default \
    libgstreamer-plugins-base1.0-dev libgstreamer1.0-dev libopenblas-dev libgflags-dev &&\
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*


# OpenCVのインストール
RUN mkdir /cv
WORKDIR /cv

RUN git clone https://github.com/opencv/opencv.git
RUN git clone https://github.com/opencv/opencv_contrib.git
WORKDIR /cv/opencv_contrib
RUN git checkout -b ${OPENCV_VERSION} refs/tags/${OPENCV_VERSION}
WORKDIR /cv/opencv
RUN git checkout -b ${OPENCV_VERSION} refs/tags/${OPENCV_VERSION}


WORKDIR /cv/opencv/build

# OpenCVのインストール
RUN cmake -D CMAKE_BUILD_TYPE=RELEASE \
        -D CMAKE_INSTALL_PREFIX=/usr/local \
        -D PYTHON3_PACKAGES_PATH=/usr/local/lib/python3.8/dist-packages \
        -D WITH_TBB=ON \
        -D ENABLE_FAST_MATH=1 \
        -D CUDA_FAST_MATH=1 \
        -D WITH_CUBLAS=1 \
        -D WITH_CUDA=ON \
        -D WITH_CUDNN=ON \
        -D BUILD_opencv_cudacodec=OFF \
        -D OPENCV_DNN_CUDA=ON \
        -D CUDA_ARCH_BIN="7.5, 8.0" \
        -D WITH_V4L=ON \
        -D WITH_QT=ON \
        -D WITH_OPENGL=ON \
        -D WITH_GSTREAMER=ON \
        -D OPENCV_GENERATE_PKGCONFIG=ON \
        -D OPENCV_ENABLE_NONFREE=ON \
        -D OPENCV_EXTRA_MODULES_PATH=/cv/opencv_contrib/modules\
        -D INSTALL_PYTHON_EXAMPLES=OFF \
        -D INSTALL_C_EXAMPLES=OFF \
        -D BUILD_opencv_python2=OFF \
        -D BUILD_opencv_python3=ON \
        -D PYTHON3_EXECUTABLE=/usr/bin/python3\
        -D PYTHON3_INCLUDE_DIR=/usr/include/python3.8\
        -D PYTHON3_LIBRARY=/usr/lib/libpython3.8.so\
        -D PYTHON3_NUMPY_INCLUDE_DIRS=/usr/local/lib/python3.8/dist-packages/numpy/core/include\
        -D BUILD_EXAMPLES=OFF .. &&\
    make -j10 && make install && ldconfig


```

このDockerfileではnvidiaのレポジトリからnvidia-driverが入ったレポジトリを取得します。
そして、その上にOpenCVとPythonの環境を構築しています。

OpemCVのインストールではcudaを使ってGPUが使えるようにしてあります。
また`opencv_contrib`のインストールやpythonでOpenCVが使えるように設定してあります。


```docker-compose.yml

version: "3.2"
services:
  ubuntu:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: "y3"
    volumes:
      - /dev/bus/usb:/dev/bus/usb
      - ./workspace:/workspace
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      - NVIDIA_DRIVER_CAPABILITIES=all
    tty: true

```

docker-compose.ymlではコンテナ上でGPUが使えるようにセッティングしてあります。
またコードの書き換えがしやすいように`workspace`をマウントしています。
コンテナ名は`y3`としていますのでお好きな名前に変更していただいて結構です。



`yolov3/`以下で
```
docker compose build 
docker compose  up 
docker exec -it y3 bash
```
を行いコンテナの中に入れるか確認します。
※ビルドには結構時間がかかります。





### OpenCVの確認
コンテナ内でopencvが使えるか確認します。
```
opencv_version
4.5.1
```
`python`から見えるか確認


```
python3
Python 3.8.10 (default, May 26 2023, 14:05:08)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.__version__
'4.5.1'
```


# darknetのインストール
コンテナ外の`workspace`以下にgitファイルをクローン
```
git clone git@github.com:AlexeyAB/darknet.git
```

`darknet`ディレクトリに入り、その中の`Makefile`を編集します。

```
GPU=1
CUDNN=1
CUDNN_HALF=0
OPENCV=1
AVX=0
OPENMP=0
LIBSO=1
ZED_CAMERA=0
ZED_CAMERA_v2_8=0

# set GPU=1 and CUDNN=1 to speedup on GPU
# set CUDNN_HALF=1 to further speedup 3 x times (Mixed-precision on Tensor Cores) GPU: Volta, Xavier, Turing, Ampere, Ada and higher
# set AVX=1 and OPENMP=1 to speedup on CPU (if error occurs then set AVX=0)
# set ZED_CAMERA=1 to enable ZED SDK 3.0 and above
# set ZED_CAMERA_v2_8=1 to enable ZED SDK 2.X

USE_CPP=0
DEBUG=0

ARCH= -gencode arch=compute_50,code=[sm_50,compute_50] \
      -gencode arch=compute_52,code=[sm_52,compute_52] \
	    -gencode arch=compute_61,code=[sm_61,compute_61]

OS := $(shell uname)

# Naming confusion with recent RTX cards.
# "NVIDIA Quadro RTX x000" and T1000/Tx00 are Turing Architecture Family with Compute Capability of 7.5
# "NVIDIA RTX Ax000" are Ampere Architecture Family with Compute Capability of 8.6
# NVIDIA "RTX x000 Ada" are Ada Lovelace Architecture Family with Compute Capability of 8.9
# Source https://developer.nvidia.com/cuda-gpus

# KEPLER, GeForce GTX 770, GTX 760, GT 740
# ARCH= -gencode arch=compute_30,code=sm_30

# MAXWELL, GeForce GTX 950, 960, 970, 980, 980 Ti, "GTX" Titan X
# ARCH= -gencode arch=compute_52,code=sm_52

# Jetson TX1, Tegra X1, DRIVE CX, DRIVE PX, Jetson Nano (2GB, 4GB)
# ARCH= -gencode arch=compute_53,code=[sm_53,compute_53]

# GP100/Tesla P100 - DGX-1
# ARCH= -gencode arch=compute_60,code=sm_60

# PASCAL, GTX 10x0, GTX 10x0 Ti, Titan Xp, Tesla P40, Tesla P4
# ARCH= -gencode arch=compute_61,code=[sm_61,compute_61]

# For Jetson TX2, Jetson Nano TX2 or Drive-PX2 uncomment:
# ARCH= -gencode arch=compute_62,code=[sm_62,compute_62]

# Tesla V100
# ARCH= -gencode arch=compute_70,code=[sm_70,compute_70]

# Jetson XAVIER, XAVIER NX
# ARCH= -gencode arch=compute_72,code=[sm_72,compute_72]

# GeForce Titan RTX, RTX 20x0, RTX 20x0 Ti, Quadro RTX x000, Tesla T4, XNOR Tensor Cores
# ARCH= -gencode arch=compute_75,code=[sm_75,compute_75]

# Tesla A100 (GA100), DGX-A100, A30, A100, RTX 3080
# ARCH= -gencode arch=compute_80,code=[sm_80,compute_80]

# GeForce RTX 30x0, 30x0 Ti, Tesla GA10x, RTX Axxxx, A2, A10, A16, A40
ARCH= -gencode arch=compute_86,code=[sm_86,compute_86]

# NOT TESTED, THEORETICAL
# Jetson ORIN, ORIN NX, ORIN NANO
# ARCH= -gencode arch=compute_87,code=[sm_87,compute_87]

# NOT TESTED, THEORETICAL
# GeForce RTX 4070 Ti, 4080, 4090, L4, L40
# ARCH= -gencode arch=compute_89,code=[sm_89,compute_89]

# NOT TESTED, THEORETICAL
# Nvidia H100
# ARCH= -gencode arch=compute_90,code=[sm_90,compute_90]

VPATH=./src/
EXEC=darknet
OBJDIR=./obj/

ifeq ($(LIBSO), 1)
LIBNAMESO=libdarknet.so
APPNAMESO=uselib
endif

ifeq ($(USE_CPP), 1)
CC=g++
else
CC=gcc
endif

CPP=g++ -std=c++11
NVCC=nvcc
OPTS=-Ofast
LDFLAGS= -lm -pthread
COMMON= -Iinclude/ -I3rdparty/stb/include
CFLAGS=-Wall -Wfatal-errors -Wno-unused-result -Wno-unknown-pragmas -fPIC -rdynamic

ifeq ($(DEBUG), 1)
#OPTS= -O0 -g
#OPTS= -Og -g
COMMON+= -DDEBUG
CFLAGS+= -DDEBUG
else
ifeq ($(AVX), 1)
CFLAGS+= -ffp-contract=fast -mavx -mavx2 -msse3 -msse4.1 -msse4.2 -msse4a
endif
endif

CFLAGS+=$(OPTS)

ifneq (,$(findstring MSYS_NT,$(OS)))
LDFLAGS+=-lws2_32
endif

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

ifeq ($(OPENMP), 1)
    ifeq ($(OS),Darwin) #MAC
	    CFLAGS+= -Xpreprocessor -fopenmp
	else
		CFLAGS+= -fopenmp
	endif
LDFLAGS+= -lgomp
endif

ifeq ($(GPU), 1)
COMMON+= -DGPU -I/usr/local/cuda/include/
CFLAGS+= -DGPU
ifeq ($(OS),Darwin) #MAC
LDFLAGS+= -L/usr/local/cuda/lib -lcuda -lcudart -lcublas -lcurand
else
LDFLAGS+= -L/usr/local/cuda/lib64 -lcuda -lcudart -lcublas -lcurand
endif
endif

ifeq ($(CUDNN), 1)
COMMON+= -DCUDNN
ifeq ($(OS),Darwin) #MAC
CFLAGS+= -DCUDNN -I/usr/local/cuda/include
LDFLAGS+= -L/usr/local/cuda/lib -lcudnn
else
CFLAGS+= -DCUDNN -I/usr/local/cudnn/include
LDFLAGS+= -L/usr/local/cudnn/lib64 -lcudnn
endif
endif

ifeq ($(CUDNN_HALF), 1)
COMMON+= -DCUDNN_HALF
CFLAGS+= -DCUDNN_HALF
ARCH+= -gencode arch=compute_70,code=[sm_70,compute_70]
endif

ifeq ($(ZED_CAMERA), 1)
CFLAGS+= -DZED_STEREO -I/usr/local/zed/include
ifeq ($(ZED_CAMERA_v2_8), 1)
LDFLAGS+= -L/usr/local/zed/lib -lsl_core -lsl_input -lsl_zed
#-lstdc++ -D_GLIBCXX_USE_CXX11_ABI=0
else
LDFLAGS+= -L/usr/local/zed/lib -lsl_zed
#-lstdc++ -D_GLIBCXX_USE_CXX11_ABI=0
endif
endif

OBJ=image_opencv.o http_stream.o gemm.o utils.o dark_cuda.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o darknet.o detection_layer.o captcha.o route_layer.o writing.o box.o nightmare.o normalization_layer.o avgpool_layer.o coco.o dice.o yolo.o detector.o layer.o compare.o classifier.o local_layer.o swag.o shortcut_layer.o representation_layer.o activation_layer.o rnn_layer.o gru_layer.o rnn.o rnn_vid.o crnn_layer.o demo.o tag.o cifar.o go.o batchnorm_layer.o art.o region_layer.o reorg_layer.o reorg_old_layer.o super.o voxel.o tree.o yolo_layer.o gaussian_yolo_layer.o upsample_layer.o lstm_layer.o conv_lstm_layer.o scale_channels_layer.o sam_layer.o
ifeq ($(GPU), 1)
LDFLAGS+= -lstdc++
OBJ+=convolutional_kernels.o activation_kernels.o im2col_kernels.o col2im_kernels.o blas_kernels.o crop_layer_kernels.o dropout_layer_kernels.o maxpool_layer_kernels.o network_kernels.o avgpool_layer_kernels.o
endif

OBJS = $(addprefix $(OBJDIR), $(OBJ))
DEPS = $(wildcard src/*.h) Makefile include/darknet.h

all: $(OBJDIR) backup results setchmod $(EXEC) $(LIBNAMESO) $(APPNAMESO)

ifeq ($(LIBSO), 1)
CFLAGS+= -fPIC

$(LIBNAMESO): $(OBJDIR) $(OBJS) include/yolo_v2_class.hpp src/yolo_v2_class.cpp
	$(CPP) -shared -std=c++11 -fvisibility=hidden -DLIB_EXPORTS $(COMMON) $(CFLAGS) $(OBJS) src/yolo_v2_class.cpp -o $@ $(LDFLAGS)

$(APPNAMESO): $(LIBNAMESO) include/yolo_v2_class.hpp src/yolo_console_dll.cpp
	$(CPP) -std=c++11 $(COMMON) $(CFLAGS) -o $@ src/yolo_console_dll.cpp $(LDFLAGS) -L ./ -l:$(LIBNAMESO)
endif

$(EXEC): $(OBJS)
	$(CPP) -std=c++11 $(COMMON) $(CFLAGS) $^ -o $@ $(LDFLAGS)

$(OBJDIR)%.o: %.c $(DEPS)
	$(CC) $(COMMON) $(CFLAGS) -c $< -o $@

$(OBJDIR)%.o: %.cpp $(DEPS)
	$(CPP) -std=c++11 $(COMMON) $(CFLAGS) -c $< -o $@

$(OBJDIR)%.o: %.cu $(DEPS)
	$(NVCC) $(ARCH) $(COMMON) --compiler-options "$(CFLAGS)" -c $< -o $@

$(OBJDIR):
	mkdir -p $(OBJDIR)
backup:
	mkdir -p backup
results:
	mkdir -p results
setchmod:
	chmod +x *.sh

.PHONY: clean

clean:
	rm -rf $(OBJS) $(EXEC) $(LIBNAMESO) $(APPNAMESO)

```


`GPU=1`
`CUDNN=1`
`OPENCV=1`
`LIBSO=1`
をそれぞれ1にして使えるようにします。

また、`ARCH= `のところのコメントアウトを外します。
このPCはGTX3070なので
`# GeForce RTX 30x0, 30x0 Ti, Tesla GA10x, RTX Axxxx, A2, A10, A16, A40`
のところのコメントアウトを外しました。

そのあとコンテナ内に入り`darknet/`で下のコマンドを実行します。
```
make
```



# YoloV3の実行
以下はコンテナ内で実行します。

初めに重みをダウンロードします。
今回はYoloV3なので以下のコマンドでV3用の重みを`darknet/`にダウンロードします。

```
wget https://pjreddie.com/media/files/yolov3.weights
```

証明書エラーが出たときは`--no-check-certificate`をつけて回避します。


続いて以下のコマンドでYoloV3を実行します。
```
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
```
実行すると`predictions.jpg`に結果が出力されますので
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3565468/f043ae69-8468-16db-0e34-8e1c814d1f65.png)
こんな感じの写真が出ればOKです。
`darknet/data/`には他にもデモ写真が含まれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3565468/bf6fd2b1-a8f6-e67c-862a-043c6bc3d24e.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3565468/5e699db8-5fe4-4d3b-0ea3-d2987f8ef042.png)


`python`で実行する場合は
```
python3 darknet_images.py --input data/giraffe.jpg --weights yolov3.weights  --dont_show --config_file cfg/yolov3.cfg  --data_file cfg/coco.data 
```



```
watch -n 0.1 nvidia-smi
```
で動作を見てるとちゃんとGPUが動いているのがわかります。




'''


# 参考文献
- https://qiita.com/wakaba130/items/3d215cbf62b7de4100e1
- https://www.rootf-datum.com/blog/2022/05/11/nvidia%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92docker%E3%81%AB%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%97%E3%82%88%E3%81%86%E3%81%A8%E3%81%97%E3%81%A6%E3%81%AF/
- https://qiita.com/usk81/items/98e54e2463e9d8a11415