【深度學習】Faster R-CNN+win10+tensorflow1.12.0+python3.6+CUDA9.0+cudnn7.3配置

本文簡要介紹Faster R-CNN在win10 64位環境下的配置。本文使用的Faster R-CNN源碼:https://github.com/endernewton/tf-faster-rcnn

其他環境爲:
tensorflow-gpu1.12.0
python3.6(通過Anaconda安裝的)
CUDA9.0、cudnn7.3(個人筆記本顯卡NVIDIA 840M)
VS2015或2017(這個在之後編譯cpu_nms/gpu_nms或者cocoAPI時需要用到,如果嫌臃腫可以只安裝編譯工具,本文安裝了vs2015編譯工具VC++ 14 BUILD TOOLS,進行在線下載安裝。鏈接:https://pan.baidu.com/s/1-VE1AxRvHaX2xDjtm8uAug 密碼:ag1v)

其他注意事項:

  1. 注意在安裝tensorflow-gpu時要與所安裝的CUDA和cudnn版本相對應,可查詢install/source_windows,版本不匹配的話容易出現以下錯誤ImportError: DLL load failed:找不到指定的模塊,可參考此博客目的還是是爲了解決tensorflow-gpu和CUDA版本不一致的問題。
  2. 在安裝CUDA時,稍注意下CUDA版本和驅動版本要相符,否則可能出現CUDA driver version is insufficient for CUDA runtime version ,這裏的CUDA runtime version指的就是CUDA toolkit版本,如CUDA10.0、CUDA9.0等,CUDA driver version指的是通過nvidia-smi命令查看到的驅動版本。下圖爲NVIDIA官網上CUDA toolkit與驅動版本對應表:在這裏插入圖片描述 在安裝CUDA的時候會自動安裝驅動,如果你之前已經裝了驅動,若已安裝的驅動版本低於CUDA安裝包中驅動的版本,則會覆蓋安裝高版本驅動(前提是顯卡能夠支持安裝較高版本驅動);如果已安裝驅動高於CUDA安裝包中驅動版本,則會跳過安裝步驟。注意安裝的驅動版本稍高於CUDA toolkit默認的驅動版本是可以的(比如CUDA10.0.130依賴的驅動版本>=411.31即可運行,因爲CUDA toolkit中接口的最終依賴還是需要驅動程序來支持的,驅動是向下兼容的)。
  3. 如果需要使用其他版本的python、CUDA、cudnn編譯的tensorflow,可以查看https://github.com/fo40225/tensorflow-windows-wheel
  4. 如果使用conda命令創建虛擬環境來安裝的tensorflow-gpu,通常會默認下載cudatoolkit及相應的cudnn包。若顯卡驅動版本支持condacudatoolkit版本,那麼用conda activate 虛擬環境命令激活該環境,則可以直接調用cudatoolkit包而不使用本地安裝的CUDA(若不激活環境去使用tensorflow,則使用不了cudatoolkit包,需要本地安裝與tensorflow相匹配的CUDA,tensorflow會到本地CUDA安裝目錄下加載所需的動態庫。詳見conda 指南中activate說明)。要說明的是:anaconda 的 cudatoolkit不包含完整安裝cuda的全部文件,只是包含了用於 tensorflow,pytorch,xgboost 和 Cupy 等所需要的共享庫文件,還是處於方便移植的目的,免去本地安裝CUDA(轉自:https://zhihu.com/question/344950161/answer/819075473)。

附上幾個原理及源碼解析鏈接:
Faster R-CNN源碼解析https://blog.csdn.net/u012457308/article/details/79566195
一文讀懂Faster RCNNhttps://zhuanlan.zhihu.com/p/31426458
Tensorflow 版本 Faster RCNN 代碼解讀https://zhuanlan.zhihu.com/p/32230004
從編程實現角度學習Faster R-CNN(附極簡實現)https://zhuanlan.zhihu.com/p/32404424(講解的代碼是基於pytorch版本的,但是基本思想是一樣的,講解得也很清楚。)

一、源碼下載

個人使用的是git-bash+MinGW模擬linux命令行環境,本文中shell語句如未說明在CMD終端下均爲在git-bash下執行。

git clone https://github.com/endernewton/tf-faster-rcnn.git

二、修改lib下部分文件,編譯Cython模塊

轉自https://github.com/endernewton/tf-faster-rcnn/issues/335,如果看着不清楚可以前往查看原文。
1.修改lib/nms/cpu_nms.pyx:第25行

cdef np.ndarray[np.int_t, ndim=1] order = scores.argsort()[::-1]

爲:

cdef np.ndarray[np.int64_t, ndim=1] order = scores.argsort()[::-1]

2.修改lib/nms/gpu_nms.pyx:第25行

cdef np.ndarray[np.int_t, ndim=1] \

爲:

cdef np.ndarray[np.int64_t, ndim=1] \

3.修改lib/datasets/pascal_voc.py:第226行

'{:s}.xml')

爲:

'{0:s}.xml')

4.修改lib/datasets/voc_eval.py:第121行

with open(cachefile, 'w') as f:

爲:

with open(cachefile, 'wb') as f:

5.修改lib/setup.py

# --------------------------------------------------------
# Fast R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick
# --------------------------------------------------------

import os
from os.path import join as pjoin
import numpy as np
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

def find_in_path(name, path):
    "Find a file in a search path"
    #adapted fom http://code.activestate.com/recipes/52224-find-a-file-given-a-search-path/
    for dir in path.split(os.pathsep):
        binpath = pjoin(dir, name)
        if os.path.exists(binpath):
            return os.path.abspath(binpath)
    return None

def locate_cuda():
    """Locate the CUDA environment on the system

    Returns a dict with keys 'home', 'nvcc', 'include', and 'lib64'
    and values giving the absolute path to each directory.

    Starts by looking for the CUDAHOME env variable. If not found, everything
    is based on finding 'nvcc' in the PATH.
    """

    # first check if the CUDAHOME env variable is in use
    if 'CUDAHOME' in os.environ:
        home = os.environ['CUDAHOME']
        nvcc = pjoin(home, 'bin', 'nvcc')
    else:
        # otherwise, search the PATH for NVCC
        nvcc = find_in_path('nvcc.exe', os.environ['PATH'])
        if nvcc is None:
            raise EnvironmentError('The nvcc binary could not be '
                'located in your $PATH. Either add it to your path, or set $CUDAHOME')
        home = os.path.dirname(os.path.dirname(nvcc))

    cudaconfig = {'home':home, 'nvcc':nvcc,
                  'include': pjoin(home, 'include'),
                  'lib64': pjoin(home, 'lib', 'x64')}
    for k, v in iter(cudaconfig.items()):
        if not os.path.exists(v):
            raise EnvironmentError('The CUDA %s path could not be located in %s' % (k, v))

    return cudaconfig
CUDA = locate_cuda()

# Obtain the numpy include directory.  This logic works across numpy versions.
try:
    numpy_include = np.get_include()
except AttributeError:
    numpy_include = np.get_numpy_include()

def customize_compiler_for_nvcc(self):
    # _msvccompiler.py imports:
    import os
    import shutil
    import stat
    import subprocess
    import winreg

    from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
                                 CompileError, LibError, LinkError
    from distutils.ccompiler import CCompiler, gen_lib_options
    from distutils import log
    from distutils.util import get_platform

    from itertools import count

    super = self.compile
    self.src_extensions.append('.cu')
    # find python include
    import sys
    py_dir = sys.executable.replace('\\', '/').split('/')[:-1]
    py_include = pjoin('/'.join(py_dir), 'include')

    # override method in _msvccompiler.py, starts from line 340
    def compile(sources,
                output_dir=None, macros=None, include_dirs=None, debug=0,
                extra_preargs=None, extra_postargs=None, depends=None):

        if not self.initialized:
            self.initialize()
        compile_info = self._setup_compile(output_dir, macros, include_dirs,
                                           sources, depends, extra_postargs)
        macros, objects, extra_postargs, pp_opts, build = compile_info

        compile_opts = extra_preargs or []
        compile_opts.append('/c')
        if debug:
            compile_opts.extend(self.compile_options_debug)
        else:
            compile_opts.extend(self.compile_options)

        add_cpp_opts = False

        for obj in objects:
            try:
                src, ext = build[obj]
            except KeyError:
                continue
            if debug:
                # pass the full pathname to MSVC in debug mode,
                # this allows the debugger to find the source file
                # without asking the user to browse for it
                src = os.path.abspath(src)

            if ext in self._c_extensions:
                input_opt = "/Tc" + src
            elif ext in self._cpp_extensions:
                input_opt = "/Tp" + src
                add_cpp_opts = True
            elif ext in self._rc_extensions:
                # compile .RC to .RES file
                input_opt = src
                output_opt = "/fo" + obj
                try:
                    self.spawn([self.rc] + pp_opts + [output_opt, input_opt])
                except DistutilsExecError as msg:
                    raise CompileError(msg)
                continue
            elif ext in self._mc_extensions:
                # Compile .MC to .RC file to .RES file.
                #   * '-h dir' specifies the directory for the
                #     generated include file
                #   * '-r dir' specifies the target directory of the
                #     generated RC file and the binary message resource
                #     it includes
                #
                # For now (since there are no options to change this),
                # we use the source-directory for the include file and
                # the build directory for the RC file and message
                # resources. This works at least for win32all.
                h_dir = os.path.dirname(src)
                rc_dir = os.path.dirname(obj)
                try:
                    # first compile .MC to .RC and .H file
                    self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])
                    base, _ = os.path.splitext(os.path.basename(src))
                    rc_file = os.path.join(rc_dir, base + '.rc')
                    # then compile .RC to .RES file
                    self.spawn([self.rc, "/fo" + obj, rc_file])

                except DistutilsExecError as msg:
                    raise CompileError(msg)
                continue
            elif ext == '.cu':
                # a trigger for cu compile
                try:
                    # use the cuda for .cu files
                    # self.set_executable('compiler_so', CUDA['nvcc'])
                    # use only a subset of the extra_postargs, which are 1-1 translated
                    # from the extra_compile_args in the Extension class
                    postargs = extra_postargs['nvcc']
                    arg = [CUDA['nvcc']] + sources + ['-odir', pjoin(output_dir, 'nms')]
                    for include_dir in include_dirs:
                        arg.append('-I')
                        arg.append(include_dir)
                    arg += ['-I', py_include]
                    # arg += ['-lib', CUDA['lib64']]
                    arg += ['-Xcompiler', '/EHsc,/W3,/nologo,/Ox,/MD']
                    arg += postargs
                    self.spawn(arg)
                    continue
                except DistutilsExecError as msg:
                    # raise CompileError(msg)
                    continue
            else:
                # how to handle this file?
                raise CompileError("Don't know how to compile {} to {}"
                                   .format(src, obj))

            args = [self.cc] + compile_opts + pp_opts
            if add_cpp_opts:
                args.append('/EHsc')
            args.append(input_opt)
            args.append("/Fo" + obj)
            args.extend(extra_postargs)

            try:
                self.spawn(args)
            except DistutilsExecError as msg:
                raise CompileError(msg)

        return objects

    self.compile = compile

# run the customize_compiler
class custom_build_ext(build_ext):
    def build_extensions(self):
        customize_compiler_for_nvcc(self.compiler)
        build_ext.build_extensions(self)

ext_modules = [
    Extension(
        "utils.cython_bbox",
        ["utils/bbox.pyx"],
        extra_compile_args={'gcc': ["-Wno-cpp", "-Wno-unused-function"]},
        include_dirs = [numpy_include]
    ),
    Extension(
        "nms.cpu_nms",
        ["nms/cpu_nms.pyx"],
        include_dirs = [numpy_include]
    ),
    Extension('nms.gpu_nms',
        ['nms/nms_kernel.cu', 'nms/gpu_nms.pyx'],
        library_dirs=[CUDA['lib64']],
        libraries=['cudart'],
        language='c++',
        # this syntax is specific to this build system
        # we're only going to use certain compiler args with nvcc and not with gcc
        # the implementation of this trick is in customize_compiler() below
        extra_compile_args={'gcc': ["-Wno-unused-function"],
                            'nvcc': ['-arch=sm_52',
                                     '--ptxas-options=-v',
                                     '-c']},
        include_dirs = [numpy_include, CUDA['include']]
    )
]

setup(
    name='tf_faster_rcnn',
    ext_modules=ext_modules,
    # inject our custom trigger
    cmdclass={'build_ext': custom_build_ext},
)

這裏說明一下爲什麼修改setup.py,該文件是爲了編譯Cython 模塊,而源碼的編譯環境爲linux所以會使用gcc來編譯,但在windows下需要使用MSVC來編譯,如本文開頭所說可以安裝VC++ 14 BUILD TOOLS來解決這個問題。
同時修改lib/setup.py第224行的參數'-arch=sm_52',需與自己的GPU架構相符合,針對我個人的GPU將其改爲了sm_50。可以查閱源碼的installation guide。瞭解更多請移步NVIDIA GPU Compilation
修改完成後,進入到lib目錄下,執行python setup.py build_ext。這時會報錯:

nms\gpu_nms.cpp(2075): error C2664: "void _nms(int *,int *,const float *,int,int,float,int)": cannot convert parameter 1 from '__pyx_t_5numpy_int32_t *' to 'int *'

需要修改lib/nms/gpu_nms.cpp

_nms((&(*__Pyx_BufPtrStrided1d(__pyx_t_5numpy_int32_t *, __pyx_pybuffernd_keep.rcbuffer->pybuffer.buf, __pyx_t_10, ...

爲:

_nms((&(*__Pyx_BufPtrStrided1d(int *, __pyx_pybuffernd_keep.rcbuffer->pybuffer.buf, __pyx_t_10, ...

之後再次運行python setup.py build_ext,將lib/build/lib.win-amd64-3.6下生產的所有文件,複製到lib下,就完成編譯啦。


需要注意的一點小問題:如果已編譯成功想再次編譯時或者編譯出現某些奇怪錯誤,可以嘗試刪除生成的.c.cpp文件(lib/nms/cpu_nms.clib/nms/gpu_nms.cpplib/utils/bbox.c)以及libbuild文件夾,否則會如下圖所示直接跳過而不編譯(該提示圖僅針對已編譯成功再次編譯情況)或者提示錯誤信息。
直接


三、安裝COCO API

源碼中提供的Python COCO API並不支持windows,這部分教程轉自在 Windows 下安裝 COCO API(pycocotools)
有兩種安裝方式供選擇:
(1)使用pip安裝
在CMD終端下運行如下代碼:

pip install git+https://github.com/philferriere/cocoapi.git#subdirectory=PythonAPI

(2)通過源碼安裝

python setup.py build_ext

也可以執行make命令,但需要安裝MinGW。
注意:此種安裝方法同樣需要使用 Microsoft Visual C++ 14.0 對 COCO 源碼進行編譯。
本文選擇方式2,同時爲了和Faster R-CNN源碼中目錄保持一致,將cocoapi-master改爲coco複製到tf-faster-rcnn/data下。


四、下載數據

下載pascalVOC數據集,包括訓練集train、驗證集val、訓練驗證集trainval和測試集test。

http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar
http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCdevkit_08-Jun-2007.tar

將這三個壓縮包解壓,合併,主要文件夾結構如下:

├── VOCdevkit
    ├── VOC2007
    |   ├── Annotations
    |   ├── ImageSets
    |   ├── JPEGImages
    |   ├── SegmentationClass  # 用於圖像分割
    |   ├── SegmentationObject  # 用於圖像分割
    ├── results  # 在測試時會在該文件夾下的/VOC2007/Main中臨時保存結果文件,否則刪除該文件夾測試時會提示No such file or directory錯誤

其餘文件夾或.m文件可以刪除。
本文中將VOCdevkit更改爲VOCdevkit2007,將該數據集複製到tf-faster-rcnn/data下,用於之後使用該數據集自己訓練模型。


五、運行demo並測試預訓練模型

(1)下載預訓練模型

原作者提供了已經訓練好的模型,鏈接:預訓練模型 提取碼:4k5c。
可以利用原作者提供的軟鏈接方式來使用預訓練模型,如下shell命令:

NET=res101
TRAIN_IMDB=voc_2007_trainval+voc_2012_trainval
mkdir -p output/${NET}/${TRAIN_IMDB}
cd output/${NET}/${TRAIN_IMDB}
ln -s ../../../data/voc_2007_trainval+voc_2012_trainval ./default
cd ../../..

或者解壓後將voc_2007_trainval+voc_2012_trainval文件夾放在data目錄下,同時在tf-faster-rcnn根目錄下新建output/res101/voc_2007_trainval+voc_2012_trainval/default,將voc_2007_trainval+voc_2012_trainval下的4個模型數據文件複製到default中。

(2)運行demo

執行如下shell命令運行demo.py,測試幾個給定的示例圖片。

# at repository root  要在tf-faster-rcnn根目錄下
GPU_ID=0
CUDA_VISIBLE_DEVICES=${GPU_ID} ./tools/demo.py

(3)測試預訓練模型

首先爲了在windows下運行.sh文件,需將/experiments/scripts/test_faster_rcnn.sh:第58行

  CUDA_VISIBLE_DEVICES=${GPU_ID} time python ./tools/test_net.py \

和第67行

  CUDA_VISIBLE_DEVICES=${GPU_ID} time python ./tools/test_net.py \

中的time刪掉。同樣的爲了後續順利執行訓練代碼,將/experiments/scripts/train_faster_rcnn.sh中的第62/73行中的time刪掉。
之後,執行如下shell命令,即會進行測試過程:

# at repository root  要在tf-faster-rcnn根目錄下
GPU_ID=0
./experiments/scripts/test_faster_rcnn.sh $GPU_ID pascal_voc_0712 res101

六、基於pascal VOC數據集自己訓練模型

(1)下載預訓練模型和權重

當前源碼支持VGG16和Resnet V1模型,下載鏈接:VGG16 提取碼:2b7y。下載鏈接:Resnet101 提取碼:x73i 。
之後,在tf-faster-rcnn/data下新建imagenet_weights文件夾,將解壓後的模型放到該文件夾下,同時將vgg_16.ckpt修改爲vgg16.ckptresnet_v1_101.ckpt修改爲 res101.ckpt。本文中僅使用了res101。

(2)訓練模型(及測試、評估過程)

執行如下命令:

./experiments/scripts/train_faster_rcnn.sh [GPU_ID] [DATASET] [NET]
# GPU_ID is the GPU you want to test on
# NET in {vgg16, res50, res101, res152} is the network arch to use
# DATASET {pascal_voc, pascal_voc_0712, coco} is defined in test_faster_rcnn.sh
# Examples:
./experiments/scripts/train_faster_rcnn.sh 0 pascal_voc res101
./experiments/scripts/train_faster_rcnn.sh 1 coco vgg16

默認情況下,訓練得到的模型會存儲在:

output/[NET]/[DATASET]/default/

測試輸出保存在:

output/[NET]/[DATASET]/default/[SNAPSHOT]/

訓練和驗證的Tensorboard信息保存在:

tensorboard/[NET]/[DATASET]/default/
tensorboard/[NET]/[DATASET]/default_val/

說明
(1)輸出路徑裏的DATASET與前邊shell語句中的DATASET不同。如果使用pascal_voc數據集,根據train_faster_rcnn.sh中第19/20行:

case ${DATASET} in
  pascal_voc)
    TRAIN_IMDB="voc_2007_trainval"
    TEST_IMDB="voc_2007_test"

訓練時輸出路徑中的DATASETvoc_2007_trainval,測試時輸出路徑中的DATASETvoc_2007_test
(2)在執行train_faster_rcnn.sh時會在末尾執行test_faster_rcnn.sh進行測試,及進行評估過程計算mAP。比如我們使用pascal_voc數據集,可以在train_faster_rcnn.shtest_faster_rcnn.sh中修改DATASETpascal_voc數據集的ITERS,以調整迭代次數,或者針對性自行設置其他參數。(如果只是爲了跑通代碼,排查錯誤,可以將兩個.sh中的ITERS設置低一些,比如30,以減少運行時間)


七、利用Faster R-CNN框架訓練自己項目數據

前面都是在說如何運行起來Faster R-CNN,當前這一部分纔是如何將Faster R-CNN運用到自己的數據上面。

(1)數據標註及格式轉換

許多目標檢測框架的源碼都使用的是pascal VOC格式的數據,Faster R-CNN框架也不例外。這裏可以使用labelImg進行數據標註,可直接生成pascal VOC格式的標註。
具體細節可參見爲目標檢測製作PASCAL VOC2007格式的數據集

一些說明

  1. 其中的圖片改名步驟個人認爲不是必須的,我自己的圖片數據已經用一串阿拉伯數字命名瞭如03150_1.jpg,實測不改也可行。
  2. 如果你的圖片數據格式不是jpg的話,可以先轉化爲jpg再製作數據集。或者直接將tf-faster-rcnn/lib/datasets/pascal_voc.py中第43行self._image_ext設置爲你的圖片格式。

按照這個步驟會得到如下形式的數據集目錄:

├── VOC2007
    ├── Annotations
    ├── ImageSets
    |    ├──Main
    |        ├── test.txt
    |        ├── train.txt
    |        ├── trainval.txt
    |        ├── val.txt
    ├── JPEGImages

將這個新數據集替換掉tf-faster-rcnn/data/VOCdevkit2007中的VOC2007即可。

(2)開始訓練

首先,在tf-faster-rcnn/lib/datasets目錄下的pascal_voc.py裏第36行更改自己的類別,'__background__'切記不可刪掉,把後面的原來的20個label換成自己的,不用更改類別數目,代碼中會自己計算該元組的長度獲得類別數目。其他參數如迭代次數可以在前文中講到的文件中修改,學習率等參數在tf-faster-rcnn/lib/model/config.py中修改。
之後,需要把之前訓練產生的模型以及cache刪除掉,分別在tf-faster-rcnn/output/res101/voc_2007_trainval/default路徑下和tf-faster-rcnn/data/cache路徑下。
最後,終於要訓練了,同樣執行如下shell命令:

./experiments/scripts/train_faster_rcnn.sh 0 pascal_voc res101

剩下的工作交給GPU吧。


八、參考資料

python3+Tensorflow+Faster R-CNN訓練自己的數據https://blog.csdn.net/char_QwQ/article/details/80980505

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章