【问题标题】:Which loss function calculates the distance between two contours哪个损失函数计算两个轮廓之间的距离
【发布时间】:2021-05-14 07:17:08
【问题描述】:

在我的轮廓生成网络中,我使用nn.L1Loss() 来计算有多少像素是错误的。这适用于训练,但真实轮廓和假轮廓之间的 2D 距离会更好。我的目标是之后测量生成轮廓的长度。这个两个二进制图像的代码示例显示了nn.L1Loss() 失败的地方。

import cv2
import torch
from torch import nn

p1 = [(15, 15),(45,45)]
p2 = [(16, 15),(46,45)]

real = cv2.rectangle(np.ones((60,60)), p1[0], p1[1], color=0, thickness=1)
fake = cv2.rectangle(np.ones((60,60)), p2[0], p2[1], color=0, thickness=1)

cv2.imshow('image',np.hstack((real,fake)))
cv2.waitKey(0)

real = torch.tensor(real)
fake = torch.tensor(fake)

losss = [nn.L1Loss(), nn.MSELoss(), nn.BCELoss(), nn.HingeEmbeddingLoss(), nn.SmoothL1Loss()]
print(my_loss(real, fake))

for k, loss in enumerate(losss):
    err = loss(real, fake)
    print(err*60)

如果我将矩形向右移动 1 个像素:

-> L1 损失为 0.0333 * 60 = 2

如果我将矩形向右移动 1 个像素,向左移动 1 个像素:

-> L1 损失为 0.0656 * 60 = 3.933

如果我将矩形向右移动 10 像素,向左移动 10 像素:

-> L1 损失为 0.0656 * 60 = 3.933
还是一样!这并不奇怪,错误像素的数量是相同的。但是到它们的距离改变了 10 * 2**1/2。

我还考虑了两个中心之间的距离:

    M = cv2.moments(c)
    cX = int(M['m10'] /M['m00'])
    cY = int(M['m01'] /M['m00'])
    centers.append([cX,cY])

这里的问题是生成的轮廓与真实的轮廓不相同,因此具有不同的中心。

这个答案与我正在寻找的答案很接近,但是计算成本很高吗?!

https://stackoverflow.com/a/36505073/12337147

是否有像我描述的那样确定距离的自定义损失函数?

【问题讨论】:

  • 嗯,你试过用洪水填充形状吗?如果这样做,您可以使用 Intersection-over-Union、Tversky Index 或 Dice Coefficient 来计算预测输出和真实输出之间的误差。
  • 如何在张量内填充轮廓?转换为 cpu,然后转换为 numpy,然后转换为 cv2.findContourcv2.drawContour(thickness=-1),然后返回张量对于损失函数来说似乎非常耗时?

标签: python contour loss-function generative-adversarial-network completion


【解决方案1】:

这是你想要的等式吗

如果轮廓彼此足够相似,则与下一个方程给出的曲线之间的面积相反

表示从一个轮廓中的点到点的累积平方距离 到另一个轮廓中的最近点?

给定两个带有轮廓上的点的数组,我可以直接在 GPU 上以复杂度 O(M * N) 计算,其中 C1 有 M 个点,C2 有 N 个点。或者,它可以在 O(W * H) 中计算,其中 W * H 是图像的维度。

如果这正是您想要的,我可以发布解决方案。

解决方案

首先让我们创建一些示例数据。

import torch
import math
from torch import nn
import matplotlib.pyplot as plt;

# Number of points in each contour
M, N = 1000, 1500
t1 = torch.linspace(0, 2*math.pi, M).view(1, -1)
t2 = torch.linspace(0, 2*math.pi, N).view(1, -1)

c1 = torch.stack([torch.sin(t1),torch.cos(t1)], dim=2) # (1 x M x 2)
c2 = 1 - 2* torch.sigmoid(torch.stack([torch.sin(t2)*3 + 1, torch.cos(t2)*3 + 2], dim=2)) # (1 x N x 2)

有了这个,我们可以使用torch.cdist 计算每对点之间的距离。这里我使用torch.argmin 来查找数组中每一列的最小值的位置。对于计算损失函数,重要的是距离本身,可以使用torch.amin 计算。

distances = torch.cdist(c1, c2); # (1 x M x N)
plt.imshow(distances[0]);
plt.xlabel('index in countor 1');
plt.ylabel('index in countor 2');
plt.plot(torch.argmin(distances[0], axis=0), '.r')

但是现在基本上,你积累的不是距离,而是距离的函数。这可以通过torch.min(f(distances)) 轻松获得,假设f(.) 是单调的可以简化为f(torch.min(distances))

为了近似积分,我们可以使用trapezoidal rule,它集成了采样函数的线性插值,在我们的例子中是在您给定的点处采样的轮廓。

这给了你一个损失函数

def contour_divergence(c1, c2, func = lambda x: x**2):
    c1 = torch.atleast_3d(c1);
    c2 = torch.atleast_3d(c2);
    f = func(torch.amin(torch.cdist(c1, c2), dim=2));
    # this computes the length of each segment connecting two consecutive points
    df = torch.sum((c1[:, 1:, :] - c1[:, :-1, :])**2, axis=2)**0.5;
    # here is the trapesoid rule
    return torch.sum((f[:, :-1] + f[:, 1:]) * df[:, :], axis=1) / 4.0;
def contour_dist(c1, c2, func = lambda x: x**2):
    return contour_divergence(c1, c2, func) + contour_divergence(c2, c1, func)

对于连接最近点的线总是垂直于轨迹contour_dist(c1, c2, lambda x: x)的情况,给出了面积。

这将给出半径为 1 的圆的面积(第二个圆的所有点都在原点上)。

print(contour_dist(c1, c1*0, lambda x: x) / math.pi) # should print 1

现在考虑半径为 1 的圆与半径为 1 的圆之间的距离(它将是 pi * (1 - 1/4) = 0.75*pi)

print(contour_dist(c1, c1*0.5, lambda x: x)  / math.pi) # should print 0.75

如果您想要累积平方距离的任何损失,您只需使用contour_dist(c1, c2),您可以将任意函数作为参数传递给函数。只要可以反向传播传递的函数,就可以反向传播损失。

【讨论】:

  • 嗨,是的,这就是我要找的!您将如何实施这种方法?
  • 非常感谢您的回答!我了解使用两个点集执行此操作的方式。我缺少的是如何在批处理中使用灰度图像执行此操作的方法。我可以遍历批次,用torch.nonzero(image) 提取点,然后计算每个点集之间的距离,并返回这个的平均值吗?
  • 是的,但您必须确保轮廓的顺序正确。如果不是,那么最好删除df,然后你会得到一个不由两个连续点之间的距离加权的总和,并且不能直接与该区域相关联,但它具有对排列不变的优势。
猜你喜欢
  • 1970-01-01
  • 2021-05-15
  • 1970-01-01
  • 2015-02-25
  • 2017-06-09
  • 1970-01-01
  • 2021-01-14
  • 2018-09-21
  • 2011-12-18
相关资源
最近更新 更多