• 第七届NVIDIA Sky Hackathon项目报告书


    系列文章目录

    1. 数据集收集和标注情况
      1.1ASR语音数据集收集和标注情况
      1.2CV图像数据集收集和标注情况
    2. 进行模型的训练
      2.1进行ASR模型的训练
      2.2进行CV模型的训练
    3. 在Jetson平台进行部署和推理
      3.1部署和推理ASR模型
      3.2部署和推理CV模型
    4. 优化UI界面


    1. 数据集收集和标注情况

    1.1ASR语音数据集收集和标注情况

    首先收集语音数据集,包含以下语音:

    请检测出果皮
    请检测出瓶子
    请检测出纸箱
    请检测出纸箱和果皮
    请检测出果皮和纸箱
    请检测出纸箱果皮和瓶子
    请检测出果皮纸箱和瓶子
    请检测出果皮瓶子和纸箱
    请检测出纸箱瓶子和果皮
    请检测出瓶子果皮和纸箱
    请检测出果皮和瓶子
    请检测出瓶子纸箱和果皮
    请检测出瓶子和果皮
    请检测出纸箱和瓶子
    请检测出瓶子和纸箱

    数据集类型以及数量

    数据集类型语音数量
    测试集111条男声+75条女声
    训练集221条(男+女)

    通过librosa计算语音时长

    asr_result = quartznet.transcribe(paths2audio_files=["xxx"]) # 调用transcribe函数测试预训练模型识别效果  
    print(asr_result)  
    
    • 1
    • 2

    制作好训练集的json文件和测试集的json文件

    1.2CV图像数据集收集和标注情况

    收集箱子图片200+,瓶子图片100+,香蕉皮图片100+
    比赛方提供了2636张图片的数据集,但是因为训练效果并不好,所以我们对数据集进行了清洗,并加入了我们自己的图片。
    最后完整的数据集共2276张图片和标签。
    我们使用labelimg进行图像标注,但是因为labelimg只能标注为Pascal VOC格式,所以我们需要将其手动转换为KITTI格式

    # -*- coding: utf-8 -*-
    import sys
    from os import listdir
    from os.path import isfile, isdir, join, dirname, splitext, basename
    import xml.etree.ElementTree as ET
    
    path=""
    class XMLReader:
        def __init__(self, path):
            file = open(path, 'r')
    
            self.path = path
            self.content = file.read()
            self.root = ET.fromstring(self.content)
            self.template = "{name} 0.00 0 0.0 {xmin}.00 {ymin}.00 {xmax}.00 {ymax}.00 0.0 0.0 0.0 0.0 0.0 0.0 0.0"
    
        def get_filename(self):
            return splitext(basename(self.path))[0]
    
        def get_dir(self):
            return dirname(self.path)
    
        def get_objects(self):
            objects = []
    
            for object in self.root.findall("object"):
                objects.append({
                    "name" : object.find("name").text,
                    "xmin" : object.find("bndbox").find("xmin").text,
                    "ymin" : object.find("bndbox").find("ymin").text,
                    "xmax" : object.find("bndbox").find("xmax").text,
                    "ymax" : object.find("bndbox").find("ymax").text
                })
    
            return objects
    
        def fill_template(self, object):
            return self.template.format(**object)
    
        def export_kitti(self):
            objects = self.get_objects()
    
            #Skip empty
            if len(objects) == 0: return False
    
            file = open(join(self.get_dir(), self.get_filename()) + ".txt", 'w')
    
            for object in objects[:-1]:
                file.write(self.fill_template(object) + "\n")
            # Write last without '\n'
            file.write(self.fill_template(objects[-1]))
    
            file.close()
    
            return True
    
    
    def process_file(path):
        xml_reader = XMLReader(path)
    
        return xml_reader.export_kitti()
    
    
    def get_directory_xml_files(dir):
        return [join(dir, f) for f in listdir(dir) if isfile(join(dir, f)) and splitext(f)[1].lower() == ".xml"]
    
    
    def check_argv(argv):
        return len(argv) > 1
    
    
    def main():
        if not check_argv(sys.argv):
            print("Wrong arguments. You should specify xml files or directory with xml files")
    
        # remove script name
        args = sys.argv[1:]
        processed_file_count = 0
    
        for path in args:
            files = []
    
            if isfile(path):
                files.append(path)
            elif isdir(path):
                files += get_directory_xml_files(path)
    
            for file in files:
                if process_file(file): processed_file_count += 1
    
        print("Finished. {0} Files are processed".format(processed_file_count))
    
    if __name__ == "__main__":
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    2.进行模型的训练

    2.1进行ASR模型的训练

    导入相关包和yaml文件后进行训练,在yaml文件中修改batch_size和数据集路径等
    训练150轮后查看训练结果,并保存nemo模型

    2.2进行CV模型的训练

    在CV模型的训练中,最主要的是完成数据集的加载以及模型训练时参数的设置

    training_config {
      batch_size_per_gpu: 8
      num_epochs: 80
      enable_qat: false
      learning_rate {
      soft_start_annealing_schedule {
        min_learning_rate: 5e-5
        max_learning_rate: 2e-3
        soft_start: 0.15
        annealing: 0.8
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们修改了学习率为0.002,并且有80个epochs,最后将训练的模型进行剪枝
    最后用tao converter转换为bin的形式

    注意!!!
    这里要在节点上转换,不能在本地转换


    3.在Jetson平台进行部署和推理

    3.1部署和推理ASR模型

    将本地保存好的nemo模型上传到节点
    修改app.py的模型路径

    nemo_asr.models.EncDecCTCModel.restore_from("/home/nvidia/7th_ASR/7th_asr_model_best.nemo") 
    
    • 1

    启动flask服务后进入前端的界面
    在这里插入图片描述
    点击加载模型
    在这里插入图片描述
    选择文件->上传语音
    在这里插入图片描述
    输入正确答案后识别
    在这里插入图片描述
    可以得到正确率和错字率

    3.2部署和推理CV模型

    需要在garbage_detection.py文件中修改自己模型的路径和名字
    将训练好的模型通过flask部署到节点上,并且进行测试

    在这里插入图片描述
    在这里插入图片描述
    上传图片并进行识别,可以得到识别结果
    在这里插入图片描述


    4. 优化UI界面

    这一部分来自于我们团队中设计前端的同学
    Css样式设计流程:
    第一步:去除浏览器默认边界和覆盖浏览器默认字体,设置box类型
    第二步: 设计body样式为display:flex(弹性布局),justify-conten:center(水平居中),align-items:center(垂直居中)
    Background: #fff(背景为纯黑), min-height:100vh(元素会被撑开与屏幕高度一致)
    第三步:设计比赛标题样式以及队名样式
    第四步:设计div标签统一样式: 使用text-decoration:none(取消文本原样式)、position:relative(依据父级定位) 、padding:10px 8px(设计边界距离)、color : #21ebff(统一字体背景色)、font-size:20px(统一字体大小)、
    第五步:给div设计边框,设计阴影效果,实现鼠标悬停触发特效。给button与input设计统一样式,微调位置,实现界面整洁和工整。
    第六步:利用浏览器调试工具,进一步,美化界面,调试按钮、上传等功能

    <html>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    
    <head>
      <script src="https://unpkg.com/vue@3">script>
      <script src="https://unpkg.com/axios/dist/axios.min.js">script>
      <link rel="stylesheet" type="text/css" href="/style.css">
      <style>
      .image img{
        max-height: 600px;
      }
    
      * {
                margin: 0;
                padding: 0;
                font-family: '微软雅黑', sans-serif;
                box-sizing: border-box;
            }
     
            body {
                display: flex;
                justify-content: center;
                align-items: center;
                background: #000;
                min-height: 100vh;
            }
     
            div {
                position: relative;
                padding: 10px 30px;
                margin: 0 45px;
                color: #21ebff;
                text-decoration: none;
                font-size: 20px;
                /* text-transform: uppercase; */  
                transition: 0.5s;
                overflow: hidden;
                /* -webkit-box-reflect: below 1px linear-gradient(transparent, #0003); */
            }
     
            /* div:hover {
                box-shadow: 0 0 33px #21ebff;
                    
            } */
            div:hover {
                /* background: #21ebff; */
                /* color: #0d7377; */
                color: #21ebff;
                /* box-shadow: 0 0 33px #21ebff; */
                box-shadow: 0 0 33px #0d7377;
                    
            }
     
            
     
            div::before {
                top: 0;
                left: 0;
                width: 10px;
                height: 10px;
                border-top: 2px solid #21ebff;
                border-left: 2px solid #21ebff;
                transition: 0.5s;
                transition-delay: 0.5s;
            }
     
            div:hover::before {
                width: 100%;
                height: 100%;
                transition-delay: 0s;
            }
     
            div::after {
                right: 0;
                bottom: 0;
                width: 10px;
                height: 10px;
                border-bottom: 2px solid #21ebff;
                border-right: 2px solid #21ebff;
                transition: 0.5s;
                transition-delay: 0.5s;
            }
     
            div:hover::after {
                width: 100%;
                height: 100%;
                transition-delay: 0s;
            }
    
            button {
                color: black;
                font-family: '微软雅黑', sans-serif;
                font-size: 18px;
            }
    
            input {
                color: #0d7377;
                font-family: '微软雅黑', sans-serif;
                font-size: 16px;
            }
                
      style>
    head>
    
    <body id="app">
    
      <div class="loading" v-if="loading!=''">
        <div class="pad">%%loading%%div>
      div>
    
      <div class="content">
        <h2>7th Sky Hackathonh2>
    
        <div class="loaded">
          
          <fieldset class="asr">
            <legend>ASRlegend>
            <button @click="loadModel()" v-if="!modelLoaded" style="display:block;margin:0 auto">加载模型button>
            <div v-if="modelLoaded" class="modelLoaded">模型加载成功div>
            <div class="field file">
              <input type="file" name="file" ref="file" @change="handleFileUpload($event)" />
              <button @click="submitFile('asr')">上传语音button>
            div>
            <div class="note">仅支持 .wav 和单声道格式div>
            <div class="field">
              <label>请输入正确答案: label>
              <input type="text" name="defaultText" v-model="defaultText" />
            div>
            <div class="field" v-if="asrStatus=='uploaded' || asrStatus=='identified'">
              <div class="audio"><audio controls :src="audioOriginal">audio>div>
              <div class="action"><button @click="identifyAudio()">识别语音button>div>
            div>
            <div class="field result asr" v-if="asrStatus=='identified'">
              <ul>
                <li v-for="(value, key) in asrResult">%%key%%: %%value%%li>
              ul>
            div>
          fieldset>
          
    
          
          <fieldset class="cv">
            <legend>CVlegend>
    
            <div class="field">
              <div class="item action"><button @click="getFps()" class="inline">获取 FPSbutton>div>
              <div class="item result">FPS: %%cvFps%%div>
            div>
    
            <div class="field">
              <div class="item"><button @click="getMap()" class="inline">获取 mAPbutton>div>
              <div class="item result">mAP: %%cvMap%%div>
            div>
    
            <div class="field file">
              <input type="file" name="file" ref="file" @change="handleFileUpload($event)" />
              <button @click="submitFile('cv')">上传图片button>
            div>
    
    
    
            <div class="field">
              <div class="image original" v-if="imageOriginal!=''">
                <div class="label">原图div>
                <image :src="imageOriginal" />
              div>
              <div class="image result cv" v-if="imageResult!=''">
                <div class="label">结果图div>
                <image :src="imageResult" />
              div>
            div>
            <div class="action" v-if="imageOriginal!=''">
              <button @click="identifyImage()">识别图片button><br>
            div>
    
          fieldset>
          
        div>
      div>
    body>
    <script>
      const {
        createApp
      } = Vue
      createApp({
        data() {
          return {
            file: '',
            defaultText: '请检测瓶子',
            modelLoaded: false,
            imageOriginal: '',
            imageResult: '',
            audioOriginal: '',
            error: '',
            asrResult: {},
            cvMap: '',
            cvFps: '',
            loading: '',
            asrStatus: 'pending',
            cvStatus: 'pending'
          }
        },
        // Avoid conflict with Flask delimiters
        compilerOptions: {
          delimiters: ["%%", "%%"]
        },
        methods: {
          async loadModel() {
            if (this.loading != '') return showError('在运行中,无法执行')
            this.loading = '加载模型中,请耐心等待...'
            this.modelLoaded = false
            try {
              var {
                data,
                status
              } = await axios.get('/asr/load')
              if (status == 200) {
                this.modelLoaded = true
              }
            } catch (err) {
              showError(err.response.data)
            }
            this.loading = ''
          },
          async submitFile(fileType) {
            let formData = new FormData()
            formData.append('file', this.file)
            statusType = fileType + 'Status'
            this.loading = '上传中...'
            try {
              var {
                data,
                status
              } = await axios.post('/' + fileType + '/upload', formData, {
                headers: {
                  'Content-Type': 'multipart/form-data'
                }
              })
              if (status == 200) {
                this[statusType] = 'uploaded'
                if (fileType == 'cv') {
                  this.imageOriginal = data
                } else {
                  this.audioOriginal = data
                }
              }
            } catch (err) {
              showError(err.response.data)
            }
            this.loading = ''
          },
          handleFileUpload(event) {
            this.file = event.target.files[0];
          },
          async identifyAudio(event) {
            // if (this.loading != '') return showError('在运行中,无法执行')
            this.loading = '识别中...'
            try {
              let formData = new FormData()
              formData.append('defaultText', this.defaultText)
              console.log('t', this.defaultText)
              var result = await axios.post('/asr/identify', formData)
              this['asrStatus'] = 'identified'
              this.asrResult = result.data
            } catch (err) {
              if (err.response.status == 500) this.modelLoaded = false
              showError(err.response.data)
            }
            this.loading = ''
          },
          async identifyImage(event) {
            if (this.loading != '') return showError('在运行中,无法执行')
            this.loading = '识别中...'
            this.cvStatus = 'pending'
            try {
              var {
                data
              } = await axios.get('/api/detect/image')
              this.imageResult = data['detection_result_image_path']
            } catch (err) {
              showError(err.response.data)
            }
            this.loading = ''
          },
          async getFps(event) {
            if (this.loading != '') return showError('在运行中,无法执行')
            this.loading = '获取 FPS...'
            try {
              var {
                data
              } = await axios.get('/api/detect/fps')
              this.cvFps = data['detection_FPS']
            } catch (err) {
              showError(err.response.data)
            }
            this.loading = ''
          },
          async getMap(event) {
            // 接口路径: /api/detect/map
            // 方式: GET
            if (this.loading != '') return showError('在运行中,无法执行')
            this.loading = '获取 mAP...'
            try {
              var {
                data
              } = await axios.get('/api/detect/map')
              this.cvMap = data['detection_mAP']
            } catch (err) {
              showError(err.response.data)
            }
            this.loading = ''
          }
        }
      }).mount('#app')
    
      function showError(msg) {
        alert(msg || '错误')
      }
    script>
    
    html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322

    总结

    在这次比赛中,我们完整的做了一个深度学习的项目,从制作数据集开始,到训练神经网络,到最后部署到用户端,完整的实现了从0到1的过程。感觉这次比赛考察的能力非常多,从最开始的配置环境开始,就给我们出了一道难题,要从最基础的Linux操作学起,安装好双系统,再去配置环境,再去运行程序,从程序中的一条条error,去搜索,搜不到就去问,解决问题才有意义,从debug中一点一点前进,才能进步。
    感觉最大的遗憾就是没有更彻底的自动化整个流程,因为没有接触过python,所以在制作语音数据集的时候,都是让队员复制过去的,没想到去写一个json,在制作cv数据集时也没想到用爬虫去找数据。
    在这里插入图片描述

  • 相关阅读:
    C#中委托和事件的使用总结
    20231010-学习笔记
    Spring-Spring之AOP底层源码解析(下)
    计算机操作系统-第十天
    鸡哥的 AI 驾驶 (Gym - 103186H)
    小程序容器技术助力券商数字营销突围
    setTimeout 、setInterval、requestAnimationFrame
    2022年9月8号Java23设计模式学习(课时四)建造者模式
    [linux] 把txt文本文件分成10个子文件,并保存。 linux命令
    学习记忆——宫殿篇——记忆宫殿——数字编码——记忆数字知识点
  • 原文地址:https://blog.csdn.net/qq_42887663/article/details/128066492