• 竞赛选题 深度学习YOLO抽烟行为检测 - python opencv


    1 前言

    🔥 优质竞赛项目系列,今天要分享的是

    🚩 基于深度学习YOLO抽烟行为检测

    该项目较为新颖,适合作为竞赛课题方向,学长非常推荐!

    🥇学长这里给一个题目综合评分(每项满分5分)

    • 难度系数:3分
    • 工作量:3分
    • 创新点:4分

    🧿 更多资料, 项目分享:

    https://gitee.com/dancheng-senior/postgraduate

    1 课题背景

    公共场合抽烟的危害很大,国家也相应地出台了在公共场合禁烟的政策。以前实行相关的政策都是靠工作人员巡逻发现并出言禁止,这样做效率很低下。计算机视觉领域发展迅速,而抽烟检测也属于一种计算机视觉目标检测的行为,可以采用目标检测的方法来实现。目前,目标检测在很多领域都取得显著成就,但是在抽烟检测领域方面进行研究却几乎没有。该研究可以有效节省成本,对公共场合禁烟政策的实行有很大的推动作用。

    2 实现效果

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    左图为原图,右图为推理后的图片,以图片方式展示,视频流和实时流也能达到这个效果,由于视频转GIF大小原因,这里暂不演示。

    3 Yolov5算法

    3.1 简介

    YOLO系列是基于深度学习的回归方法。该系列陆续诞生出YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5。YOLOv5算法,它是一种单阶段目标检测的算法,该算法可以根据落地要求灵活地通过chaneel和layer的控制因子来配置和调节模型,所以在比赛和落地中应用比较多。同时它有YOLOv5x、YOLOv5l、YOLOv5m、YOLOv5s四种模型。
    具有以下优点:

    • 在pytorch环境下编写;
    • 可以很容易编译成ON⁃NX和Core ML;
    • 运行速度很快,每秒可以达到140FPS的速度;
    • 模型精度高;
    • 集成了YOLOv3和YOLOv4的部分优秀特性,进行了推陈出新的改进。

    3.2 相关技术

    Mosaic数据增强

    Mosaic数据增强技术采用了四张图片的随机缩放、随机剪裁、随机排布的方式对数据进行拼接,相比CutMix数据增强多用了两张图片。在目标识别过程中,要识别的目标有大目标、中等目标、小目标,并且三种目标的占比例不均衡,其中,小目标的数量是最多的,但是出现的频率很低,这种情况就会导致在bp时对小目标的优化不足,模型正确识别小目标的难度比识别中、大目标的难度要大很多,于是对于小目标来说很容易出现误检和漏检的情况。Mosaic数据增强技术做出改进后,上述的问题得到有效的解决。
    该技术的优点是:

    • 丰富了数据集,采用“三个随机”的方式对数据进行拼接丰富了检测的数据集,尤其是随机缩放增加了很多小目标,克服了小目标的不足,让网络的鲁棒性得到提高;
    • 减少GPU的使用,在Mosaic增强训练时,四张图片拼接在一起,GPU可以直接计算四张图片的数据,让Mini-batch的大小减少了很多,这使得一个GPU就可以达到比较可观的效果。
      在这里插入图片描述

    自适应anchor
    自适应anchor是check_anchors函数通过遗传算法与Kmeans迭代算出的最大可能召回率的anchor组合。在网络模型的训练过程中,网络在初始化的锚框的基础上输出预测框,然后与真实框groundtruth进行对比,计算两个框之间的差值,再根据差值进行反向更新,迭代网络参数,最后求出最佳的锚框值。自适应的anchor能够更好地配合网络训练,提高模型的精度,减少对anchor的设计难度,具有很好的实用性。

    自适应图片缩放
    为了提高模型的推理速度,YOLOv5提出自适应图片缩放,根据长宽比对图像进行缩放,并添加最少的黑边,减少计算量。该方法是用缩放后的长边减去短边再对32进行取余运算,求出padding。在训练时并没有采用缩减黑边的方法,该方法只是在测试模型推理的时候才使用,这样提高了目标检测的准确率和速度。

    Focus结构
    该结构采用切片操作,将特征切片成四份,每一份将当成下采样的特征,然后在channel维度进行concat。例如:原始608 608
    3的数据图片,经过切片操作先变成304 304 12的特征图,再经过一次32个卷积核的卷积操作,变成304 304 32的特征图。
    在这里插入图片描述
    在这里插入图片描述
    CSP结构
    YOLOv5中的CSP[5]结构应用于两处,一处是CSP1_X结构应用于Backbone的主干网络中,另一处的CSP2_X结构应用于Neck中,用于加强网络的特征融合的能力。CSPNet主要从网络结构设计的角度解决推理中从计算量很大的问题。该结构的优点有:1)增强CNN的学习能力,使得模型在轻量化的同时保持较高的准确性;2)减低计算的瓶颈问题;3)减低内存的分险。

    PFN+PAN结构
    这个结构是FPN和PAN的联合。FPN是自顶向下的,将高层的特征信息通过上采样的方式进行传递融合,得到进行预测的特征图,而PAN正好与FPN的方向是相反的方向,它是自底向上地采取特征信息。两个结构各自从不同的主干层对不同的检测层进行参数聚合。两个结构的强强联合让得到的特征图的特征更加明显和清楚。

    Bounding box的损失函数
    Bounding
    box损失函数[6]增加了相交尺度的衡量方式,有效缓解了当两个框不相交和两个框大小完全相同的两种特殊情况。因为当预测框和目标框不相交时,IOU=0,无法反应两个框距离的远近的时候,此时的损失函数不可导;两个框大小完全相同,两个IOU也相同,IOU_LOSS无法区分以上两种特殊情况。

    nms非极大值抑制
    在目标检测过程的后续处理中,对于大量的目标框的筛选问题,通常会进行nms操作,以此来达到一个不错的效果。YO⁃LOv5算法同样采用了加权的nms操作。

    4 数据集处理及实验

    数据集准备

    由于目前针对吸烟图片并没有现成的数据集,我们使用Python爬虫利用关键字在互联网上获得的图片数据,编写程序爬了1w张,筛选下来有近1000张可用,以及其他途径获取到的,暂时可用数据集有5k张,

    深度学习图像标注软件众多,按照不同分类标准有多中类型,本文使用LabelImg单机标注软件进行标注。LabelImg是基于角点的标注方式产生边界框,对图片进行标注得到xml格式的标注文件,由于边界框对检测精度的影响较大因此采用手动标注,并没有使用自动标注软件。

    考虑到有的朋友时间不足,博主提供了标注好的数据集和训练好的模型,需要请联系。

    数据标注简介

    通过pip指令即可安装

    pip install labelimg
    
    • 1

    在命令行中输入labelimg即可打开

    在这里插入图片描述

    5 部分核心代码

    
    
        # data/smoke.yaml
    
    
        # COCO 2017 dataset http://cocodataset.org
        # Download command: bash yolov5/data/get_coco2017.sh
        # Train command: python train.py --data ./data/coco.yaml
        # Dataset should be placed next to yolov5 folder:
        #   /parent_folder
        #     /coco
        #     /yolov5
    
    
        # train and val datasets (image directory or *.txt file with image paths)
        train: data\train.txt  # 上面我们生成的train,根据自己的路径进行更改
        val: data\test.txt  # 上面我们生成的test
        #test: ../coco/test-dev2017.txt  # 20k images for submission to https://competitions.codalab.org/competitions/20794
        
        # number of classes
        nc: 1   #训练的类别
        
        # class names
        names: ['smoke']
        
        # Print classes
        # with open('data/coco.yaml') as f:
        #   d = yaml.load(f, Loader=yaml.FullLoader)  # dict
        #   for i, x in enumerate(d['names']):
        #     print(i, x)
    
    
        # model/yolov5s.yaml
    
        # parameters
        nc: 1  # number of classes
        depth_multiple: 0.33  # model depth multiple
        width_multiple: 0.50  # layer channel multiple
        
        # anchors
        anchors:
          - [116,90, 156,198, 373,326]  # P5/32
          - [30,61, 62,45, 59,119]  # P4/16
          - [10,13, 16,30, 33,23]  # P3/8
        
        # YOLOv5 backbone
        backbone:
          # [from, number, module, args]
          [[-1, 1, Focus, [64, 3]],  # 0-P1/2
           [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
           [-1, 3, BottleneckCSP, [128]],
           [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
           [-1, 9, BottleneckCSP, [256]],
           [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
           [-1, 9, BottleneckCSP, [512]],
           [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
           [-1, 1, SPP, [1024, [5, 9, 13]]],
          ]
        
        # YOLOv5 head
        head:
          [[-1, 3, BottleneckCSP, [1024, False]],  # 9
        
           [-1, 1, Conv, [512, 1, 1]],
           [-1, 1, nn.Upsample, [None, 2, 'nearest']],
           [[-1, 6], 1, Concat, [1]],  # cat backbone P4
           [-1, 3, BottleneckCSP, [512, False]],  # 13
        
           [-1, 1, Conv, [256, 1, 1]],
           [-1, 1, nn.Upsample, [None, 2, 'nearest']],
           [[-1, 4], 1, Concat, [1]],  # cat backbone P3
           [-1, 3, BottleneckCSP, [256, False]],
           [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 18 (P3/8-small)
        
           [-2, 1, Conv, [256, 3, 2]],
           [[-1, 14], 1, Concat, [1]],  # cat head P4
           [-1, 3, BottleneckCSP, [512, False]],
           [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 22 (P4/16-medium)
        
           [-2, 1, Conv, [512, 3, 2]],
           [[-1, 10], 1, Concat, [1]],  # cat head P5
           [-1, 3, BottleneckCSP, [1024, False]],
           [-1, 1, nn.Conv2d, [na * (nc + 5), 1, 1]],  # 26 (P5/32-large)
        
           [[], 1, Detect, [nc, anchors]],  # Detect(P5, P4, P3)
          ]
    
    
        # 训练部分主函数
    
    
        if __name__ == '__main__':
            check_git_status()
            parser = argparse.ArgumentParser()
            parser.add_argument('--epochs', type=int, default=300)
            parser.add_argument('--batch-size', type=int, default=16)
            parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='*.cfg path')
            parser.add_argument('--data', type=str, default='data/smoke.yaml', help='*.data path')
            parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='train,test sizes')
            parser.add_argument('--rect', action='store_true', help='rectangular training')
            parser.add_argument('--resume', action='store_true', help='resume training from last.pt')
            parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
            parser.add_argument('--notest', action='store_true', help='only test final epoch')
            parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')
            parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters')
            parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
            parser.add_argument('--cache-images', action='store_true', help='cache images for faster training')
            parser.add_argument('--weights', type=str, default='', help='initial weights path')
            parser.add_argument('--name', default='', help='renames results.txt to results_name.txt if supplied')
            parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
            parser.add_argument('--adam', action='store_true', help='use adam optimizer')
            parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%')
            parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset')
            opt = parser.parse_args()
            opt.weights = last if opt.resume else opt.weights
            opt.cfg = check_file(opt.cfg)  # check file
            opt.data = check_file(opt.data)  # check file
            print(opt)
            opt.img_size.extend([opt.img_size[-1]] * (2 - len(opt.img_size)))  # extend to 2 sizes (train, test)
            device = torch_utils.select_device(opt.device, apex=mixed_precision, batch_size=opt.batch_size)
            if device.type == 'cpu':
                mixed_precision = False
        
            # Train
            if not opt.evolve:
                tb_writer = SummaryWriter(comment=opt.name)
                print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/')
                train(hyp)
        
            # Evolve hyperparameters (optional)
            else:
                tb_writer = None
                opt.notest, opt.nosave = True, True  # only test/save final epoch
                if opt.bucket:
                    os.system('gsutil cp gs://%s/evolve.txt .' % opt.bucket)  # download evolve.txt if exists
        
                for _ in range(10):  # generations to evolve
                    if os.path.exists('evolve.txt'):  # if evolve.txt exists: select best hyps and mutate
                        # Select parent(s)
                        parent = 'single'  # parent selection method: 'single' or 'weighted'
                        x = np.loadtxt('evolve.txt', ndmin=2)
                        n = min(5, len(x))  # number of previous results to consider
                        x = x[np.argsort(-fitness(x))][:n]  # top n mutations
                        w = fitness(x) - fitness(x).min()  # weights
                        if parent == 'single' or len(x) == 1:
                            # x = x[random.randint(0, n - 1)]  # random selection
                            x = x[random.choices(range(n), weights=w)[0]]  # weighted selection
                        elif parent == 'weighted':
                            x = (x * w.reshape(n, 1)).sum(0) / w.sum()  # weighted combination
        
                        # Mutate
                        mp, s = 0.9, 0.2  # mutation probability, sigma
                        npr = np.random
                        npr.seed(int(time.time()))
                        g = np.array([1, 1, 1, 1, 1, 1, 1, 0, .1, 1, 0, 1, 1, 1, 1, 1, 1, 1])  # gains
                        ng = len(g)
                        v = np.ones(ng)
                        while all(v == 1):  # mutate until a change occurs (prevent duplicates)
                            v = (g * (npr.random(ng) < mp) * npr.randn(ng) * npr.random() * s + 1).clip(0.3, 3.0)
                        for i, k in enumerate(hyp.keys()):  # plt.hist(v.ravel(), 300)
                            hyp[k] = x[i + 7] * v[i]  # mutate
        
                    # Clip to limits
                    keys = ['lr0', 'iou_t', 'momentum', 'weight_decay', 'hsv_s', 'hsv_v', 'translate', 'scale', 'fl_gamma']
                    limits = [(1e-5, 1e-2), (0.00, 0.70), (0.60, 0.98), (0, 0.001), (0, .9), (0, .9), (0, .9), (0, .9), (0, 3)]
                    for k, v in zip(keys, limits):
                        hyp[k] = np.clip(hyp[k], v[0], v[1])
        
                    # Train mutation
                    results = train(hyp.copy())
        
                    # Write mutation results
                    print_mutation(hyp, results, opt.bucket)
        
                    # Plot results
                    # plot_evolution_results(hyp)
    
    
    
    
    • 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

    6 最后

    🧿 更多资料, 项目分享:

    https://gitee.com/dancheng-senior/postgraduate

  • 相关阅读:
    Echarts绘制各种数据可视化图表案例(效果+代码)
    R语言dplyr包distinct函数基于dataframe数据中指定单个变量移除重复数据行、keep_all函数用于保留输出数据框中的所有其它变量
    kubernetespod控制器详解2与service详解1
    集群常用群起脚本
    Uniapp软件库源码 全新带勋章功能(包含前后端源码)
    南美智利市场最全分析开发攻略,收藏一篇就够了
    百度飞桨(武汉)人工智能产业赋能中心签约,推动AI技术与汉阳“1+6”产业深度融合
    程序员如何用海外平台接单?
    【算法 】两组随机变量协方差矩阵 矩阵的特征值与特征向量
    【pod进阶】
  • 原文地址:https://blog.csdn.net/laafeer/article/details/133885870