哪个损失函数计算两个轮廓之间的距离

Which loss function calculates the distance between two contours

在我的轮廓生成网络中,我使用 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])

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

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

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

这是你想要的方程吗

与下一个等式给出的曲线之间的区域相反,如果等高线彼此足够相似

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

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

如果这正是你想要的,我可以post一个解决方案。

解决方法

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

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. Here I used torch.argmin to find the position of the minimum of each column in the array. For computing the loss function what is important is the distance itself, and that can be computed with 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),你可以将任意函数作为参数传递给函数。只要能反向传播传递的函数,就可以反向传播损失。