iT邦幫忙

2021 iThome 鐵人賽

2
AI & Data

手寫中文字之影像辨識系列 第 31

【第31天】番外篇-Windows + YOLOV4 本地端訓練

摘要

  1. 前言
  2. 工具
  3. 流程

前言

  1. 【第3天】資料前處理-YOLOv4與自動框選中文字曾提及,Windows + YOLOV4在呼叫本地端GPU時出現異常,因而部署到Colab訓練。
  2. 然而,使用免費Colab GPU訓練時,若訓練樣本數量超過3000張,上傳檔案耗費大量時間(40~60分鐘),且可能訓練到一半Colab斷線,導致前功盡棄。(變相地限制了樣本數量)
  3. 故近期參考網路上分享文章,嘗試在Windows編譯Darknet與本地端訓練YOLOV4模型,提高訓練樣本數與模型訓練效率。
  4. 在YOLOV4 Darknet環境部署時BUG頻發,整整踩了2天的坑,想起來還是一陣後怕。此次撰文和大家分享YOLOV4部署到Windows本地端可能遭遇的問題與解決方法。同時,也是做個筆記,下次部署時,同樣的坑可不想踩第2次了。

工具

  1. 更新顯卡驅動程式:詳細請參閱【第3天】資料前處理-YOLOv4與自動框選中文字

  2. Visual stutio 2019 與 Microsoft Visual C++ 2015-2019:建議先安裝Visual stutio 2019 與 Microsoft Visual C++ 2015-2019,再安裝CUDA與cuDNN。

    2.1 下載:請點擊此處

    2.2 安裝:

    • 點擊安裝Visual stutio 2019

    • 勾選使用C++的桌面開發

  3. CUDA與cuDNN:CUDA版本10.1、cuDNN版本7.6,詳細請參閱【第3天】資料前處理-YOLOv4與自動框選中文字

  4. OpenCV:版本4.5.4

    4.1 下載:請點擊此處

    4.2 安裝並記下安裝路徑

    4.3 新增環境變數(系統變數)

    • OpenDIR:C:\Users\88691\Desktop\YOLOV4\opencv\build

    • Path:C:\Users\88691\Desktop\YOLOV4\opencv\build\x64\vc15\bin

  5. Cmake:請點擊此處下載

  6. ZED SDK:請點擊此處下載

  7. AlexeyAB/darknet:請點擊此處下載


流程

  1. 建立專案

    1.1 開啟Visula Studio 2019,點擊建立新的專案。

    1.2 點擊空白專案。

    1.3 點擊新增項目,並新增C++檔(.cpp),供後續驗證OpenCV是否成功設定。

  2. 設定OpenCV路徑

    2.1 點擊左下方「屬性管理員」,右鍵點擊release X64,並點選「加入新的屬性專案工作表」。

    2.2 設定「屬性專案工作表」

    • 點擊PropertySheet開啟屬性 → VC++目錄 → Include目錄 → 新增下列3個路徑。(上圖編號3)

      • C:\Users\88691\Desktop\YOLOV4\opencv\build\include
      • C:\Users\88691\Desktop\YOLOV4\opencv\build\include\opencv
      • C:\Users\88691\Desktop\YOLOV4\opencv\build\include\opencv2

    • VC++目錄 → 程式庫目錄 → 新增下列路徑。(上圖編號4)

      • C:\Users\88691\Desktop\YOLOV4\opencv\build\x64\vc15\lib

      ※ 注意:Visula Studio 2015選擇vc14;Visula Studio 2017、2019選擇vc15。

    • 連結器 → 輸入 → 其他相依性 → 新增下列路徑。

      • opencv_world454.lib

    • 驗證OpenCV正常啟用

      • 將待驗證圖片放入Project2

      • 程式碼:在剛剛新增的C++檔(.cpp)中輸入下列程式碼。

      //Opencv 僅支援64位元處理器
      #include <opencv2/opencv.hpp>
      #include <iostream>
      
      using namespace std;
      using namespace cv;
      
      int main() {
      	Mat img; //宣告一個儲存影像的矩陣
          img = imread("123.jpg"); //讀取影像
      	if (img.empty())
          {
          	cout << "請確認影像檔路徑正確" << endl;
          	return -1;
          }
      	imshow("test", img); //印出圖片
      	waitKey(0);
          system("pause");
          return 0;
      }
      
      • 執行結果:選取Release與x64,點擊「本機Windows偵錯工具」,成功顯示影像。

    2.3 若執行後出現錯誤,解決方法如下。

    • 找不到opencv_world454.lib:

      • 新增系統環境變數 C:\Users\88691\Desktop\YOLOV4\opencv\build\x64\vc15\lib

    • 找不到opencv_world454.dll

      • 新增系統環境變數 C:\Users\88691\Desktop\YOLOV4\opencv\build\x64\vc15\bin

  3. 編譯Darknet

    3.1 將AlexeyAB/darknet下載的darknet-master.zip解壓縮。

    3.2 開啟Cmake並設定路徑

    3.3 點擊ConFigure → 輸入x64 → 點擊finish

    3.4 若出現Looking for a CUDA compiler – NOTFOUND,解決方法如下。確認是否改善時,記得要重新開啟Cmake,Delete Cache後,再次點擊ConFigure → 輸入x64 → 點擊finish。

    • 重新安裝:先安裝Visual Stutio再安裝CUDA

    • 到控制台 → 新增移除程式 → 查看是否有安裝到NVIDIA Tools Extension SDK(NVTX)

    • 確認路徑中是否存在下列4個檔案,否則將路徑1的檔案複製到路徑2。

      • 路徑1:C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\extras\visual_studio_integration\MSBuildExtensions

      • 路徑2:C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\BuildCustomizations

    • 嘗試安裝不同版本的CUDA

    3.5 點擊Generate(產出新的Darknet.sln) → Open Project

    3.6 生成解決方案:選取Release與x64,點擊建置 → 建置方案

    3.7 若生成解決方案時,出現拒絕存取。

    • 點擊方案總管 → 分別點擊滑鼠右鍵建置ALL_BUILD與INSTALL。

    3.8 成功生成解方方案後,將 \build\darknet\Release5 中5個檔案複製到 \build\darknet\x64。

  4. 執行Darknet訓練模型

    4.1 事前準備

    • 按此下載train.rar,並解壓縮成train資料夾。

    • 將資料集放進train下的\VOCdevkit\VOC2021\JPEGImages

    • 將LabelImg標記產生的XML檔,放進train底下\VOCdevkit\VOC2021\Annotations

    • 將train資料夾移到C:\Users\88691\Desktop\YOLOV4\darknet-master\build\darknet\x64

    4.2 執行gen_train_val.py:分配訓練集與測試集。

    4.3 執行voc_label.py:datasets預處理,標記Train/Test/Val資料集。

    4.4 開啟obj.data,將路徑修改成絕對路徑。

    4.5 開啟cmd視窗,輸入指令cd C:\Users\88691\Desktop\YOLOV4\darknet-master\build\darknet\x64

    4.6 輸入指令darknet detector train C:\Users\88691\Desktop\YOLOV4\darknet-master\build\darknet\x64\train\obj.data C:\Users\88691\Desktop\YOLOV4\darknet-master\build\darknet\x64\train\yolov4-tiny-myobj.cfg C:\Users\88691\Desktop\YOLOV4\darknet-master\build\darknet\x64\train\yolov4-tiny.conv.29 -map

    4.7 若執行時出現錯誤,解決方法如下。

    • 找不到 pthreadVC2.dll:

      • 前往 C:\Users\88691\Desktop\YOLOV4\darknet-master\3rdparty\pthreads\bin 複製pthreadVC2.dll。
      • 複製到C:\Windows\System32C:\Windows\System64資料夾內。

    • 找不到 opencv_world454.dll

      • 前往C:\Users\88691\Desktop\YOLOV4\opencv\build\x64\vc15\bin複製opencv_world453.dll
      • 複製到C:\Users\88691\Desktop\YOLOV4\darknet-master\build\darknet\x64資料夾

    4.8 YOLOV4模型訓練完成

    4.9 模型預測

    • 程式碼
    import cv2
    import numpy as np
    import os
    import shutil
    
    #讀取模型與訓練權重
    def initNet():
        CONFIG = './train_finished_1/yolov4-tiny-myobj.cfg'
        WEIGHT = './yolov4-tiny-myobj_last.weights'
        # WEIGHT = './train_finished/yolov4-tiny-myobj_last.weights'
        net = cv2.dnn.readNet(CONFIG, WEIGHT)
        model = cv2.dnn_DetectionModel(net)
        model.setInputParams(size=(416, 416), scale=1/255.0)
        model.setInputSwapRB(True)
        return model
    
    #物件偵測
    def nnProcess(image, model):
        classes, confs, boxes = model.detect(image, 0.4, 0.1)
        return classes, confs, boxes
    
    #框選偵測到的物件,並裁減
    def drawBox(image, classes, confs, boxes):
        new_image = image.copy()
        for (classid, conf, box) in zip(classes, confs, boxes):
            x, y, w, h = box
            if x - 18 < 0:
                x = 18
            if y - 18 < 0:
                y = 18
            cv2.rectangle(new_image, (x - 18, y - 18), (x + w + 20, y + h + 24), (0, 255, 0), 3)
        return new_image
    
    # 裁減圖片
    def cut_img(image, classes, confs, boxes):
        cut_img_list = []
        for (classid, conf, box) in zip(classes, confs, boxes):
            x, y, w, h = box
            if x - 18 < 0:
                x = 18
            if y - 18 < 0:
                y = 18
            cut_img = image[y - 18:y + h + 20, x - 18:x + w + 25]
            cut_img_list.append(cut_img)
        return cut_img_list[0]
    
    # 儲存已完成前處理之圖檔(中文路徑)
    def saveClassify(image, output):
        cv2.imencode(ext='.jpg', img=image)[1].tofile(output)
    
    if __name__ == '__main__':
        source = './public_training_data/public_training_data/'
        # source = './public_training_data/public_testing_data/'
        files = os.listdir(source)
        print('※ 資料夾共有 {} 張圖檔'.format(len(files)))
        print('※ 開始執行YOLOV4物件偵測...')
        model = initNet()
        success = fail = uptwo = 0
        number = 1
        for file in files:
            print(' ▼ 第{}張'.format(number))
            img = cv2.imdecode(np.fromfile(source+file, dtype=np.uint8), -1)
            classes, confs, boxes = nnProcess(img, model)
            if len(boxes) == 0:
                # 儲存原始除檔照片
                # saveClassify(img, './public_training_data/YOLOV4_pre/fail/' + file)
                # saveClassify(img, './test123/fail/' + file)
                fail += 1
                print('  字元偵測失敗:{}'.format(file))
                # cv2.imshow('img', img)
            elif len(boxes) >= 2:
                print('  字元偵測超過2個')
                box_img = drawBox(img, classes, confs, boxes)
                # saveClassify(box_img, './public_training_data/YOLOV4_pre/uptwo/' + file)
                # saveClassify(img, './test123/uptwo/' + file)
                # cv2.imshow('img', img)
                uptwo += 1
            else:
                # 框選後圖檔
                frame = drawBox(img, classes, confs, boxes)
                # 裁剪後圖檔
                cut = cut_img(img, classes, confs, boxes)
                # 儲存裁剪後圖檔
                # saveClassify(cut, './public_training_data/YOLOV4_pre/success/' + file)
                # saveClassify(img, './test123/success/' + file)
                success += 1
                print('  字元偵測成功:{}'.format(file))
                # cv2.imshow('img', frame)
                # cv2.imshow('cut', cut)
            print('=' * 60)
            # cv2.waitKey()
            number += 1
        print('※ 程式執行完畢')
        print('※ 總計:成功 {} 張、失敗 {} 張'.format(success, fail))
        print('※ 偵測超過兩個字元組 {} 張'.format(uptwo))
    
    • 執行結果(成功框選字串)


小結

久違了大家,原以為這篇寫到一半就會偷懶擱置,畢竟Debug已經夠累了。出乎意料地,回過神就寫完了。我想:參加鐵人賽的一個月,讓我養成了學習與撰文分享的好習慣。

讓我們繼續看下去...


參考資料

  1. OpenCV Visual Studio安裝教學
  2. YOLOv4 win10 配置 + 訓練自己的資料 + 測試
  3. YOLOV4建置流程with within windows10 & VS2019

上一篇
【第30天】最終回
系列文
手寫中文字之影像辨識31

尚未有邦友留言

立即登入留言