• boundary IoU 的计算方式


    Boundary IoU 说白了就是计算 预测图片的边界GT图片的边界IoU

    在这里插入图片描述
    就是酱紫的两个边界计算 IoU 就好

    问题是怎么计算上图这样的边界呢? ,用Canny边缘检测之类的? 算个梯度? 可是要边界啊,在分割的区域内部也有可能有边缘啊,这个不是边界

    我们可以采用Boundary IoU原文的计算方式

    这是原图:
    在这里插入图片描述

    我们可以让原图缩小一圈,就像这样
    在这里插入图片描述

    然后原图减去缩小版的图,就得到了边界
    在这里插入图片描述

    那怎么才能得到缩小版的原图呢? 这就请出了主角:腐蚀

    可以参考这里:
    OpenCV 图像处理之膨胀与腐蚀

    腐蚀操作和膨胀操作相反,也就是将毛刺消除,判断方法为:在卷积核大小中对图片进行卷积。
    取图像中(3 * 3)区域内的最小值。由于我们是二值图像,也就是取0(黑色)。 总结: 只要原图片3 * 3范围内有黑的,该像素点就是黑的。

    接下来直接看看代码吧,代码见 Reference:

    # GitHub repo: https://github.com/bowenc0221/boundary-iou-api
    # Reference: https://gist.github.com/bowenc0221/71f7a02afee92646ca05efeeb14d687d
    
    import cv2
    import numpy as np
    import matplotlib.pyplot as plt
    
    
    # General util function to get the boundary of a binary mask.
    # 该函数用于获取二进制 mask 的边界
    def mask_to_boundary(mask, dilation_ratio=0.02):
        """
        Convert binary mask to boundary mask.
        :param mask (numpy array, uint8): binary mask
        :param dilation_ratio (float): ratio to calculate dilation = dilation_ratio * image_diagonal
        :return: boundary mask (numpy array)
        """
        h, w = mask.shape
        img_diag = np.sqrt(h ** 2 + w ** 2) # 计算图像对角线长度
        dilation = int(round(dilation_ratio * img_diag))
        if dilation < 1:
            dilation = 1
        # Pad image so mask truncated by the image border is also considered as boundary.
        new_mask = cv2.copyMakeBorder(mask, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0)
        kernel = np.ones((3, 3), dtype=np.uint8)
        new_mask_erode = cv2.erode(new_mask, kernel, iterations=dilation)
        
        # 因为之前向四周填充了0, 故而这里不再需要四周
        mask_erode = new_mask_erode[1 : h + 1, 1 : w + 1]
        
        # G_d intersects G in the paper.
        return mask - mask_erode
    
    • 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
    def boundary_iou(gt, dt, dilation_ratio=0.02):
        """
        Compute boundary iou between two binary masks.
        :param gt (numpy array, uint8): binary mask
        :param dt (numpy array, uint8): binary mask
        :param dilation_ratio (float): ratio to calculate dilation = dilation_ratio * image_diagonal
        :return: boundary iou (float)
        """
        gt_boundary = mask_to_boundary(gt, dilation_ratio)
        dt_boundary = mask_to_boundary(dt, dilation_ratio)
        intersection = ((gt_boundary * dt_boundary) > 0).sum()
        union = ((gt_boundary + dt_boundary) > 0).sum()
        boundary_iou = intersection / union
        return boundary_iou
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    mask_to_boundary 函数用于计算边界的 mask,而 boundary_iou 用于计算 boundary_iouboundary_iou 中会调用 mask_to_boundary .

    这一行用于给原图的四周添加0, 这样连边界区域的目标像素也会被腐蚀掉

    # Pad image so mask truncated by the image border is also considered as boundary.
    new_mask = cv2.copyMakeBorder(mask, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0)
    
    • 1
    • 2

    以下是 cv2.copyMakeBorder 操作的示意图,其实直接叫 Padding 就好了
    在这里插入图片描述

    这两行用于给图像做腐蚀操作,kernel size 是(3, 3)

    kernel = np.ones((3, 3), dtype=np.uint8)
    new_mask_erode = cv2.erode(new_mask, kernel, iterations=dilation) # iterations 指的是腐蚀的次数
    
    • 1
    • 2

    再来看下 dilation 的计算:

    h, w = mask.shape
    img_diag = np.sqrt(h ** 2 + w ** 2) # 计算图像对角线长度
    dilation = int(round(dilation_ratio * img_diag))
    if dilation < 1:
        dilation = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    腐蚀的次数与对角线的长度成正比,如果小于1则直接给1,dilation_ratio 是函数的参数

    再看最后一步:

    # 因为之前向四周填充了0, 故而这里不再需要四周
    mask_erode = new_mask_erode[1 : h + 1, 1 : w + 1]
    
    • 1
    • 2

    将周边的padding像素去掉,之后再将二者减掉就可:

    return mask - mask_erode
    
    • 1

    最后就得到这个图:
    在这里插入图片描述

    boundary_iou 的计算方式和一般的 IoU 计算方式一样,有个问题就是,如果交集onion==0时,可能存在除0错误的问题,他这个代码里没有

    所以应该改为:

    def boundary_iou(gt, dt, dilation_ratio=0.02):
        """
        Compute boundary iou between two binary masks.
        :param gt (numpy array, uint8): binary mask
        :param dt (numpy array, uint8): binary mask
        :param dilation_ratio (float): ratio to calculate dilation = dilation_ratio * image_diagonal
        :return: boundary iou (float)
        """
        gt_boundary = mask_to_boundary(gt, dilation_ratio)
        dt_boundary = mask_to_boundary(dt, dilation_ratio)
        intersection = ((gt_boundary * dt_boundary) > 0).sum()
        union = ((gt_boundary + dt_boundary) > 0).sum()
        if union < 1:
        	return 0
        boundary_iou = intersection / union
        return boundary_iou
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    如何实现一个AI聊天功能
    【游戏专区】飞机大战
    认知战壳吉桔:认知战战略不是从思潮开始,而在策划!
    商业银行最全数据-66年跨度、100+指标(涵盖业务、股东、高管、员工、审计等数据)
    使用Karmada实现Helm应用的跨集群部署
    OpenText EnCase Mobile Investigator 查看、分析和报告被调查手机的证据
    现代 CPU 技术发展
    【数据挖掘】推荐系统
    Docker Compose 一键快速部署 RocketMQ
    面向6G承载网的路由优化算法研究
  • 原文地址:https://blog.csdn.net/HaoZiHuang/article/details/125613577