草庐IT

YoloV5 模型自定义评估-误报、漏报、错报

业余码农 2023-04-08 原文

YoloV5模型训练成功后,可以通过自带的val.py文件进行评估分析,其提供mAp、Iou以及混淆矩阵等,很好,但是……领导不认可……/(ㄒoㄒ)/~~。领导要的是最直观的东西,比如这个模型识别目标的准确率,还有误报率等……。那么,领导的要求就是我们开发的方向:

为了得到准确率以及误报、漏报、错报的情况,需要使用模型检测已经标注过的样本,将检测结果与标注进行比对。

YoloV5的val.py文件已经实现了大部分功能,可以直接拿来改造:

首先,原文件中对预测结果的NMS处理函数调用如下:

out = non_max_suppression(out, conf_thres, iou_thres, labels=lb, multi_label=True, agnostic=single_cls)

val.py本来的一个作用就是与train.py配合,在模型迭代训练过程中提供评估,但是我们这里不需要处理太多可能,只要置信度最高的就好了,修改如下:

out = non_max_suppression(out, conf_thres, iou_thres, multi_label=False, agnostic=single_cls)

然后将检测结果与标注结果做对比,原代码中是在process_batch这个函数中进行处理,计算所有的检测框和标注框的IoU,根据检测类比和标注类别再进行处理。这里做一下改造,代码如下:

def process_batch(batch_i, detections, labels, iouv):
    """
    Return correct predictions matrix. Both sets of boxes are in (x1, y1, x2, y2) format.
    Arguments:
        detections (Array[N, 6]), x1, y1, x2, y2, conf, class
        labels (Array[M, 5]), class, x1, y1, x2, y2
    Returns:
        correct (Array[N, 10]), for 10 IoU levels
    """

    # 正确:0   误报: 1   漏报: 2    错报: 3
    labelNum = labels.shape[0]
    detectNum = detections.shape[0]
    if labelNum == 0 and detectNum == 0:
        return 0
    elif labelNum == 0: #存在误报
        results = torch.zeros(detectNum, 6 , dtype=torch.float, device=iouv.device)
        results[:,0] = batch_i
        results[:,1] = 1
        results[:,2] = detections[:, 5]   #检测类别
        results[:,3] = -1.0                 #标注类别
        #results[:,4] = 0.0                 #检测框和标注框的IoU
        results[:,5] = detections[:, 4]   #检测置信度
        return results
    elif detectNum == 0:  #存在漏报
        results = torch.zeros(labelNum, 6, dtype=torch.float, device=iouv.device)
        results[:,0] = batch_i
        results[:,1] = 2
        results[:,2] = -1.0                #检测类别
        results[:,3] = labels[:, 0]      #标注类别
        #results[:,4] = 0.0                 #检测框和标注框的IoU
        #results[:,5] = 0.0                 #检测置信度
        return results
    else:
        tempresults = torch.zeros(0, 6, dtype=torch.float, device=iouv.device)
        indexes = torch.zeros(1, detectNum, dtype=torch.float, device=iouv.device)
        validNum = 0
        i=0 
        for label in labels:
            j=0
            bMatchLabel = False
            for detect in detections:
                iou = singlebox_iou(label[1:], detect[:4])
                if iou > 0.5:
                    bMatchLabel = True
                    indexes[:,j] = 1.0
                    validNum += 1
                    result = torch.zeros(1, 6 , dtype=torch.float, device=iouv.device)
                    result[:,0] = batch_i
                    result[:,1] = 0 if detect[5] == label[0] else 3
                    result[:,2] = detect[5]     #检测类别
                    result[:,3] = label[0]      #标注类别
                    result[:,4] = iou           #检测框和标注框的IoU
                    result[:,5] = detect[4]     #检测置信度
                    #tempresults.append(result)
                    tempresults = torch.cat((tempresults, result), 0)
                j += 1
            i += 1
            if not bMatchLabel:  #如果有标注狂没有匹配到检测框的为漏报
                validNum += 1
                result = torch.zeros(1, 6 , dtype=torch.float, device=iouv.device)
                result[:,0] = batch_i        
                result[:,1] = 2
                result[:,2] = -1.0          #检测类别
                result[:,3] = label[0]      #标注类别
                #tempresults.append(result)
                tempresults = torch.cat((tempresults, result), 0)

        #如果有检测框没有匹配到标注框的为误报
        noMatchDect = indexes[0][(indexes[0] == 0).nonzero()]
        if noMatchDect.shape[0] > 0:
            for index in noMatchDect:
                result = torch.zeros(1, 6 , dtype=torch.float, device=iouv.device)
                i = int(index[0])
                result[:,0] = batch_i
                result[:,1] = 1
                result[:,2] = detections[i][5]   #检测类别
                result[:,3] = -1.0               #标注类别
                result[:,5] = detections[i][4]   #检测置信度
                #tempresults.append(result)
                tempresults = torch.cat((tempresults, result), 0)

        return tempresults

还得解释一下这里对误报、漏报、错报的定义:

误报:没有标注,但是检测出目标

漏报:有标注,但是没检测出目标

错报:有标注,和检测目标的类别不一样

所有样本处理完后进行统计,统计每个类别的误报、漏报数量,以及错报(其实混淆矩阵就是这个功能,只不过这里用的是数量),代码如下:

def analyse(totalResults, names, save_dir):
    classnames =[]
    for i in range(len(names)):
        classnames.append(str(names[i]))
        
    #误报
    misIndexes = torch.where(totalResults[:,1] == 1.0)
    misItems = totalResults[misIndexes]
    misStats = torch.zeros(1, len(names), dtype=torch.int, device=totalResults.device)
    line = "误报- "
    for i in range(len(names)):
        n = len(torch.where(misItems[:,2] == i)[0])
        misStats[:,i] = n
        line += names[i] + ":" + str(n) +" "

    print(line + "\n")

    #plt.rcParams['font.sans-serif']=['SimHei']
    #plt.style.use('ggplot')
    #plt.title("误报")
    #plt.xlabel("异物类别")
    #plt.ylabel("数量")
    #plt.bar(classnames, misStats[0].tolist(), width=1.2, color='blue')
    #plt.savefig(str(save_dir) + "/evaluates/误报.jpg")

    #漏报
    failIndexes = torch.where(totalResults[:,1] == 2.0)
    failItems = totalResults[failIndexes]
    failStats = torch.zeros(1, len(names), dtype=torch.int, device=totalResults.device)
    line = "漏报- "
    for i in range(len(names)):
        n = len(torch.where(failItems[:,3] == i)[0])
        failStats[:,i] = n
        line += names[i] + ":" + str(n) +" "

    #plt.title("漏报")
    #plt.xlabel("异物类别")
    #plt.ylabel("数量")
    #plt.bar(classnames, failStats[0].tolist(), width=1.2, color='blue')
    #plt.savefig(str(save_dir) + "/evaluates/漏报.jpg")

    print(line + "\n")

    #错报
    errorIndexes  = torch.where(totalResults[:,1] == 3.0)
    errorItems = totalResults[errorIndexes]
    errorStats = torch.zeros(len(names), len(names), dtype=torch.int, device=totalResults.device)
    for item in errorItems:
        errorStats[int(item[2]), int(item[3])] += 1

    print("错报- \n")
    print(errorStats)

有关YoloV5 模型自定义评估-误报、漏报、错报的更多相关文章

  1. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  5. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  6. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  7. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  8. ruby - 主要 :Object when running build from sublime 的未定义方法 `require_relative' - 2

    我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby​​1.9+ 关于ruby-主要:Objectwhenrun

  9. ruby-on-rails - 如何将验证与模型分开 - 2

    我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:

  10. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

随机推荐