在 C++ 中计算直方图的梯度
Posted
技术标签:
【中文标题】在 C++ 中计算直方图的梯度【英文标题】:Calculate the gradient for an histogram in c++ 【发布时间】:2009-12-29 10:47:57 【问题描述】:我计算了 3D 灰度图像的直方图(一个简单的 1d 数组)。 现在我想计算这个直方图在每个点的梯度。所以这实际上意味着我必须在某些点计算一维函数的梯度。但是我没有功能。那么如何用具体的 x 和 y 值来计算呢?
为简单起见,您能否在示例直方图上向我解释这一点 - 例如使用以下值(x 是强度,y 是这种强度的频率):
x1 = 1; y1 = 3
x2 = 2; y2 = 6
x3 = 3; y3 = 8
x4 = 4; y4 = 5
x5 = 5; y5 = 9
x6 = 6; y6 = 12
x7 = 7; y7 = 5
x8 = 8; y8 = 3
x9 = 9; y9 = 5
x10 = 10; y10 = 2
我知道这也是一道数学题,但由于我需要用 C++ 来解决它,我想你可以在这里帮助我。
感谢您的建议 马克
【问题讨论】:
由于您没有函数,并且这些点可以拟合到通过这些点的任意数量的函数,因此梯度是不可知的。您需要更好地约束隐藏函数的参数才能发现梯度。也许相反,您想知道通过所有这些点的最佳拟合线的梯度(线性回归),或者指定您想知道最接近这些点的二次(例如)函数的梯度?跨度> 嗨,正如我在下面写的,我需要梯度来确定我的直方图斜率是下降还是上升。总的来说,我认为安德烈亚斯从下面的方法应该没问题。但是我不知道斜坡上的异常值是否存在问题。你的建议(线性回归或二次梯度)能解决这个问题吗?此外,我想在各种不同的直方图上使用这些计算。线性回归是否适用于所有地方,因为直方图可能有许多峰值,并且在整个强度范围内并不总是上升或下降。感谢您的帮助,问候 @Marc 只有一件事(与问题无关)。如果你不接受答案,甚至不投票给你认为可能对你有帮助的答案,人们就不会很愿意帮助你 感谢您的提示 - 我还没有认识到评估系统。 没关系:)。关于高斯蒙版(我认为您想在直方图中执行平滑操作)看看homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm,里面有一个一维蒙版。或者你可以使用这个卷积掩码 [1/3 1/3 1/3] 来执行简单的平均计算。 【参考方案1】:我认为您可以使用与图像边界检测相同的方法(即梯度演算)来计算梯度。如果您的直方图在一个向量中,您可以计算梯度的近似值*:
for each point in the histogram compute
gradient[x] = (hist[x+1] - hist[x])
这是一种非常简单的方法,但我不确定是否最准确。
近似值,因为您使用的是离散数据而不是连续数据已编辑:
其他运算符可能会强调小的差异(小梯度会变得更加强调)。 Roberts算法源自导数:
lim delta -> 0 = f(x + delta) - f(x) / delta
delta 无限趋向于 0(为了避免 0 除法)但绝不为零。在计算机的内存中这是不可能的,我们可以得到的最小 delta 是 1(因为 1 是图像(或直方图)中点到点的最小距离)。
替换
lim delta -> 0 to lim delta -> 1
我们得到
f(x + 1) - f(x) / 1 = f(x + 1) - f(x) => vet[x+1] - vet[x]
【讨论】:
嗨,对于 2D 图像中的边界检测,他们经常使用如下掩码:[-1 0 1 ] [-2 0 2 ] [-1 0 1 ](这应该是 3x3 掩码 - 每个部分在 [] 是一行)。像这样的某种面具也适用于一维吗?例如像这样:-1 0 1 或 -2 0 2(所以对于我的第一个掩码:-1*hist[x-1] + 0*hist[x] + 1*hist[x+1])可能会增强您的方法(基本上是带有 1 -1 的掩码)还是结果会更糟?问候 其实我这里回答的算法是Roberts边界检测算子(最简单的边缘检测算法)。如果需要 [1 -1] 可以使用卷积掩码来完成,但最终会得到相同的结果,我认为仅计算差异会更快。您可以在这里使用图像算法的任何变体,例如拉普拉斯 [-1 2 -1] - 拉普拉斯运算符可以写为 [0 -1 0][-1 4 -1][0 -1 0] 但在这里我将其调整为一维。 @Andres:一个[1 -1]卷积掩码就是的区别!不仅结果,而且计算是相同的......虽然如果你“从左到右”考虑它,[-1 1] 是相应的符号。 [-1 2 -1] 可以认为是两边差值的总和。 您好,感谢您到目前为止的回复。我还有另一个问题 - 我希望不要太傻 - 但由于我仍然有点不知所措,我只是尝试:实际上是否还有一个用于计算梯度的标准值的 1D 高斯掩码?或者是否有特定的掩码是梯度计算的首选?【参考方案2】:这里通常有两种方法:
-
导数的离散近似
取拟合函数的实数导数
在第一种情况尝试:
g = (y_(i+1) - y_(i-1))/2*dx
在除末端之外的所有点,或其中之一
g_left-end = (y_(i+1) - y_i)/dx
g_right-end = (y_i - y_(i-1))/dx
dx
是 x 点之间的间距。 (与同样正确的定义 Andres suggested 不同,这个定义是对称的。是否重要取决于您的用例。)
在第二种情况中,将spline 拟合到您的数据[*],然后向样条库询问您想要的点的导数。
[*] 使用库!除非这是一个学习项目,否则不要自己实施。我会使用ROOT,因为我已经在我的机器上安装了它,但它是一个非常重的包,只是为了获得一个样条......
最后,如果您的数据有噪声,您可能希望在进行斜率检测之前对其进行平滑处理。那是您避免追逐噪音,而只看大型斜坡。
【讨论】:
你能解释一下对称是什么意思吗? +1 @Andres:当然。想象一下直方图中的一个对称峰,带有长的平尾。因为您的函数(当然,如果您正在考虑微分极限,则很自然)涉及i
和i+1
,但不涉及i-1
,所以双峰梯度分布的中心不会位于顶峰。就像我说的,这可能会或可能不会重要。我的有两种特殊情况,而你的只有一种。决定,决定......
太好了。在图像处理中存在类似的问题(不是双峰函数)。检测到的边缘与原始图像的边界不完全吻合(边缘可以看作是一种 sigmoid 函数)。理论上,过零应该解决这个问题(双导数),但由于图像的离散性,这不是很精确。干杯
@dmckee:您可能还可以帮助我解决以下问题:首先,我可以对异常值做些什么?例如,如果我有如下斜率: y(i) = 20; y(i+1) = 18; y(i+2) = 15; y(i+3) = 12; y(i+4) = 17; y(i+5) = 11; y(i+6) = 10; y(i+7) = 8;.. 例如,如果我在 i+1 处计算 y_(i) 就可以了。但是如果我在 i+5 处计算,尽管整体斜率在下降,但梯度将为正。这对我来说很重要,因为我想找出总斜率的终点——梯度再次开始升高的地方。其次...看下一条评论
...如果斜率(梯度)是下降还是上升(而且我不知道间距)对我来说很重要 - 我可以简单地将 dx 设置为 1 吗?!跨度>
【参考方案3】:
拿一些方格纸,在上面画出你的直方图。还可以通过直方图的 0,0 点绘制垂直轴和水平轴。
取一条直尺,并在您感兴趣的每个点旋转直尺,直到它与您对该点的渐变的概念一致。这样做是最重要的,你对渐变的定义就是你想要的。
一旦直边处于您想要的角度,就在该角度画一条线。
从刚刚绘制的直线上的任意 2 个点垂下垂线。如果您选择的 2 个点之间的水平距离约为直方图宽度的 25% 或更多,则执行以下步骤会更容易。从相同的 2 个点绘制水平线与直方图的垂直轴相交。
您的线现在定义了 x 距离和 y 距离,即水平/垂直(分别)轴的长度,由它们与垂线/水平线的交点标出。您想要的渐变是 y 距离除以 x 距离。
现在,除了第 2 步之外,将其转换为代码非常简单。您必须定义用于确定直方图上任意点的梯度的标准。简单的选择包括:
a) 在每一点,放下你的直尺以穿过该点并在其右侧穿过下一个;
b) 在每一点,放下你的直尺以穿过该点和其左边的下一个;
c) 在每一点,放下你的直尺,穿过左边的点和右边的点。
您可能想要研究更复杂的选择,例如通过直方图上的多个点拟合曲线(例如二次或高阶多项式),并使用该曲线的导数来表示梯度。
在您理解纸上的问题之前,请避免使用 C++ 或其他任何方式进行编码。一旦你理解了它,编码应该是微不足道的。
【讨论】:
基本上你已经告诉他要弥补他喜欢的任何渐变,无论是美学上还是其他方面。你似乎没有一些通用的方法来计算这些角度,尽管我赞赏你的方法把中心问题带回家(你想要什么梯度?)。 是的,没错。定义渐变应该是什么是他的工作,这不是一个小问题。如果他想要一个美观的渐变,他有权拥有一个,尽管我怀疑这不是他在这种情况下真正需要的。我还认为,测量直线的斜率正是计算梯度的一种通用方法。但这是 OP 的选择。 您好,感谢您的回复。实际上我需要梯度来确定我的直方图斜率是上升还是下降。现在我想从直方图的某个峰值开始,然后找出斜率下降的峰值左侧和右侧的强度范围。从什么时候开始,一个新的上升坡向另一个高峰开始。一般来说,下面建议的近似值(由 Andreas 提出)是可以的,但我认为下降斜率上可能存在异常值。如果我能避免这样的误解就好了。问候以上是关于在 C++ 中计算直方图的梯度的主要内容,如果未能解决你的问题,请参考以下文章