【第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_world454.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

<<:  企业资料通讯Week6 (3) | Transport Layer_婴儿食品版

>>:  【从零开始的 C 语言笔记】第二十二篇-多重回圈 & 九九乘法表

Day26-Alpine.js vs Vue.js浅谈(3)

要收假收心了~大家继续加油啦! 今天要看得比较是事件处里, Alpine.js和Vue.js都可直接...

每个人都该学的30个Python技巧|技巧 8:进阶判断—巢状判断式(字幕、衬乐、练习)

昨天教的是判断式,那它会用到程序区块,那Python要怎麽显示程序区块呢?是要用冒号以及缩排来显示,...

[Day 18]从零开始学习 JS 的连续-30 Days---网路请求POST

网路请求POST介绍 PSOT 功能与 GET 一样都是 HTML Form 表单资料传递使用方式,...

[Day26]Solidity小实作

hi~经过三天有关solidity语法讲解的过程,那今天就来做一个小实作!我们来写一个有关加法与减...

下个赌场更诱人

今天彼特币又喷了 台股跌、美股跌 其实有K棒的地方都可以用技术分析 ...