1. 程式人生 > >ubuntu下dlib編譯C++(共享庫)及使用,即cmake編譯dlib開原始檔的步驟和檔案結構,

ubuntu下dlib編譯C++(共享庫)及使用,即cmake編譯dlib開原始檔的步驟和檔案結構,

一、cmake的工作機制

        使用CMake很簡單。 構建過程是通過建立一個或多個CMakeLists檔案(實際上是CMakeLists.txt,但本指南將在大多數情況下脫離擴充套件)控制在專案的每個目錄中。 CMakeLists檔案應該包含CMake簡單語言的專案描述。 語言表達為一系列命令。 每個命令按照它在CMakeLists檔案中出現的順序進行評估。

        一旦CMake安裝在您的系統上,使用它來建立一個專案很容易。 CMake在構建專案時使用了兩個主目錄:源目錄和二進位制目錄。 源目錄是您的專案的原始碼所在的位置。 這也是CMakeLists檔案被找到的地方。二進位制目錄是你希望CMake放置生成的目標檔案,庫和可執行檔案的位置。

        每個本地生成器都有一個類cmMakefile的例項,cmMakefile是儲存解析CMakeLists檔案結果的地方。具體而言,對於專案中的每個目錄,都會有一個cmMakefile例項,這就是cmMakefile類通常稱為目錄的原因。這對於不使用Makefiles的構建系統更為清晰。該例項將包含解析該目錄的CMakeLists檔案的所有資訊。一種考慮cmMakefile類的方法是以一種初始化其父目錄中的一些變數的結構,然後在處理CMakeLists檔案時填充該結構。讀取CMakeLists檔案只是CMake按照遇到的順序執行命令的一個步驟。

        通過上述步驟大概知道了,cmake是如何編譯多檔案目錄的開源庫,“cmake”命令就是指向CMakeLists.txt並解析它,根據檔案中的命令執行編譯過程。

二、ubuntu環境下cmake編譯dlib-19.10開源庫


按照官方網站中的說明How to compile的解答
1. 在examples目錄下新建一個build資料夾,存放編譯過程中生成的檔案
    mkdir build
    cd build
2. cmake ..
    表示CMakeLists.txt在當前目錄的上一級目錄。
    命令cmake.. 表示在當前目錄的上一級目錄(父目錄)尋找CMakeLists.txt,並讀取該CMakeLists.txt檔案,
    並執行檔案中的命令。
3. cmake --build . --config Release
    在當前目錄(“.”表示當前目錄)構建,--build表示構建選項,--config Release表示構建模式在Release下。
    原型是cmake --build <dir> ,意思是在目錄dir構建。--config <cfg>,意思是對於多配置工具,請選擇配置<cfg>;
    整個命令也可以用make 代替,只是構建模式為預設模式。
    
    而常用的利用多個cpu核編譯程式碼的命令:make -j4(利用cpu的四個核,根據自己的電腦情況),也可以利用cmake命令代       替:cmake --build build -- -j3,意思是在目錄build構建專案,並同時利用3個cpu核,一般預設的只用其中一個cpu核。

    經過以上三個步驟,完成了dlib庫的原始碼編譯。

    下面我們從CMakeLists.txt中詳細分析cmake編譯的過程。

    (1) 從第一條命令開始,mkdir build和cd build,就是在 */dlib/examples目錄下建立資料夾build,並進入資料夾。
    (2) 第二條命令 cmake .. , 之前也解釋了就是在當前目錄的上一級目錄(父目錄,也就是和examples同一級目錄)尋找CMakeLists.txt,並解析CMakeLists.txt,主要命令如下:
        1)開啟CMakeLists.txt就有一個非常醒目的一句話“THIS IS A TUTORIAL, READ THE COMMENTS !!(這是一個教程,閱讀註釋)”,因為很重要所以才寫的非常醒目。
        2)命令 “cmake_minimum_required(VERSION 2.8.12)    ” 提出了對編譯dlib庫原始碼的cmake最低版本的要求,如果低於這個版本,會報錯並讓你升級cmake。
        3)命令 “add_subdirectory(../dlib dlib_build)” 關於這個命令有必要好好解釋一下:
        如果遇到add_subdirectory( source_dir [binary_dir])時,進入目錄source_dir對其中的CMakeLists.txt進行解析。


            cmake 官方網站解釋:
            向構建新增一個子目錄。source_dir指定源CMakeLists_txt和程式碼檔案所在的目錄。
            如果它是一個相對路徑,它將根據當前目錄(典型用法)進行計算,但它也可能是一個絕對路徑。binary_dir指定放置
            輸出檔案的目錄。如果它是一個相對路徑,它將根據當前輸出目錄計算,但它也可能是一個絕對路徑。如果沒有指定
            binary_dir,在展開任何相對路徑之前,將使用source_dir的值(典型用法)。CMake將立即處理指定源目錄中的
            CMakeLists_txt檔案,然後在此命令之外繼續處理當前輸入檔案。


        CMakeLists.txt檔案類似程式碼檔案,從上到下順序執行,而add_subdirectory相當於函式呼叫,執行source_dir中的CMakeLists.txt。執行完之後跳轉回來,
        順序向下繼續執行。
        4)先把當前CMakeLists.txt命令看完。命令 “add_executable(assignment_learning_ex assignment_learning_ex.cpp)”,在此命令之前有一段註釋:

            # The next thing we need to do is tell CMake about the code you want to
            # compile.  We do this with the add_executable() statement which takes the name
            # of the output executable and then a list of .cpp files to compile.  Here we
            # are going to compile one of the dlib example programs which has only one .cpp
            # file, assignment_learning_ex.cpp.  If your program consisted of multiple .cpp
            # files you would simply list them here in the add_executable() statement.  
            接下來我們需要做的是告訴CMake你要編譯的程式碼。 我們使用add_executable()語句執行此操作,
            該語句獲取輸出可執行檔案的名稱,然後是要編譯的.cpp檔案列表。 這裡我們將編譯一個dlib示例程式,
            它只有一個.cpp檔案,assignment_learning_ex.cpp。 如果您的程式由多個.cpp檔案組成,您只需在add_executable()語句中列出它們。

            總之一句話,該命令就是指定生成可執行檔案要用到的原始碼。
        5)命令 “target_link_libraries(assignment_learning_ex dlib::dlib)” ,意思是告訴CMake這個程式assignment_learning_ex依賴於dlib。
    (3)在第三條命令 “add_subdirectory(../dlib dlib_build)” ,相當於函式呼叫跳轉到當前目錄(即example資料夾所包含的CMakeLists.txt檔案所在目錄,命令 “cmake ..” 已經跳轉到examples資料夾中的CMakeLists.txt)的上一級目錄(父目錄)的dlib資料夾,然後解析dlib資料夾中的CMakeLists.txt.

    下面解析dlib資料夾中的CMakeLists.txt.
    裡面的命令對比著https://blog.gmem.cc/cmake-study-note的關於cmake的講解,基本都能夠看明白。裡面的內容,一般情況下與需要更改。編譯dlib庫最主要的目的就是使用dlib庫,靜態庫(libdlib.a)或者共享庫(動態庫,libdilb.so)。而原始碼中的CMakeLists.txt中設定的是靜態庫,實現方式是通過命令 “set(BUILD_SHARED_LIBS false)”(第60行左右),可以通過更改false為true來使得編譯得到的dlib庫為共享庫。

三、錯誤分析 

        而最令人鬱悶的是,在編譯得到libdlib.so之後(通過cmake-gui,設定了DLIB_USE_CUDA,在後面打勾,說明編譯過程中使用了cuda),引用libdlib.so之後,以人臉檢測為例使用dnn_face_detection,正常除錯通過之後,執行出現 “CUDA NOT ENABLED” 的錯誤提示。就是這個錯誤讓我百思不得其解,當時對cmake還只是一知半解,明明編譯的時候已經使用了cuda,反而提示 “CUDA NOT ENABLED” 。當時,把cuda從8.0升級到了9.2,cudnn相應升級,cuda驅動升級。後來還是提示 “CUDA NOT ENABLED” ,整整折騰了差不多一個星期,中間幾次想放棄,但是不甘心,因為感覺它本身不難,肯定是哪裡沒有學習到(cmake最全的學習資料是第一個連結)。在最後仿照第一個連結中的巨集定義和初始值設定,在自己工程的CMakeLists.txt,自己重新定義了DLIB_USE_CUDA:

        # 定義巨集MACRO_NAME,注意前面的-D
        add_definitions(-DDLIB_USE_CUDA)
        # 賦值
        option(DLIB_USE_CUDA "DLIB_USE_CUDA" ON)

    再次執行自己的工程,不再提示 “CUDA NOT ENABLED” 但是提示當前cuda驅動不匹配cuda9.2。於是按照https://blog.csdn.net/davidhopper/article/details/81144914的
    第七種方法利用 “7.2通過新增ppa源的方法安裝驅動” 成功升級cuda驅動:

        sudo add-apt-repository ppa:graphics-drivers/ppa  
        sudo apt-get update  
        sudo apt-get install nvidia-390 # 此處的390要根據上面查詢到的版本號適當更改(查詢地址:https://www.geforce.cn/drivers)

    升級之後,再次執行沒有出現驅動不匹配,反而出現使用cudaMalloc的時候,提示 “out of memory”(記憶體不足)。原因是載入每一張圖片是有一個要求:

            while(img.size() < 1800*1800)
                pyramid_up(img);

    圖片太大,導致分配記憶體時,出現 “out of memory”,改成 900*900就正常執行。

    結束了。
    
    還是不知道變數 “DLIB_USE_CUDA” 這個變數或者巨集在哪裡定義的,繼續探尋

四、其他關於cmake的變數定義

CMAKE_BUILD_TYPE 可以設定為debug或Release,dlib編譯的時候設定為Release,而相應的CMAKE_CXX_FLAGS_DEBUG和CMAKE_CXX_FLAGS_RELEASE 也會被設定(是系統預設的)。

CMAKE_CXX_FLAGS_DEBUG  -g,表示可執行程式包含除錯資訊。下面是各個選項的含義。

-g 可執行程式包含除錯資訊
-o 指定輸出檔名
-c 只編譯不連結

CMAKE_CXX_FLAGS_RELEASE -O3 -DNDBUG,表示開啟編譯優化,等級為三。

debug模式下,加-Os,速度會快。

Release模式下,加 -O3和不加,有比較顯著的差異。

-DNDEBUG 是告訴如G++之類的編譯器在每個translation unit中定義macro NDEBUG,進而導致所有assert()都被關閉!

參考資料:
    https://blog.gmem.cc/cmake-study-note                (CMake學習筆記)
    https://blog.csdn.net/qqwangfan/article/details/79093527    (linux下使用CmakeLists.txt生成makefile檔案進行編譯)
    https://blog.csdn.net/u012150179/article/details/17852273    (CMake 使用方法 & CMakeList.txt)
    https://www.cnblogs.com/tla001/p/6827808.html            (簡易cmake多檔案多目錄工程模板)
    https://www.cnblogs.com/autophyte/p/6147751.html        (使用CMake構建複雜工程)
    https://blog.csdn.net/a794226986/article/details/18616511    (cmake處理多原始檔目錄的方法)
    https://www.ibm.com/developerworks/cn/linux/l-cn-cmake/        (在 linux 下使用 CMake 構建應用程式)
    https://cmake.org/cmake/help/v3.11/command/add_subdirectory.html?highlight=add_subdirectory
    https://blog.csdn.net/sinat_38431275/article/details/78487370#22-%E8%87%AA%E5%B7%B1%E5%BB%BA%E7%AB%8Bcmake(【譯文】Mastering CMake 第二章 開始)
    https://blog.csdn.net/sinat_38431275/article/details/78506051    (【譯文】Mastering CMake 第三章 關鍵概念)
    https://cmake.org/cmake/help/v3.11/manual/cmake.1.html?highlight=options%20native%20options#build-tool-mode(cmake --build . --config Release的官網解釋)

  https://blog.csdn.net/yuhengyue/article/details/78626102