在 2D 阵列上查找子像素最大值
Find sub-pixel maximum on a 2D array
假设我有一个图像,我想找到一个形状为 3x3 的子数组,该子数组与其他子数组相比包含最大和。
如何在 python 中高效地执行此操作(运行 尽可能快)?如果能提供一个示例代码就好了。
我的具体问题:
我想在此热图中提取斑点中心的位置
我不想只得到最大点,因为那样会导致坐标不是很精确。斑点的真正中心实际上可能在 2 个像素之间。因此,最好在许多点之间进行加权平均以获得亚像素精度。例如,如果有 2 个点 (x1,y1) 和 (x2,y2),其值为 200 和 100。那么平均坐标将为 x=(200*x1+100*x2)/300
y=(200*y1+100*y2)/300
我的一个解决方案是做一个卷积运算。但我认为它不够有效,因为它需要乘法到内核(只包含一个)。我正在寻找一个快速的实现,所以我不能自己循环,因为我不确定它是否会很快。
我想每隔几毫秒对 50 张图像执行此算法。 (图片成批进来)。具体来说,将这些图像视为输出热图的机器学习模型的输出。为了从这些热图中获取坐标,我需要在高强度坐标之间进行某种加权平均。我的想法是对图像上的 3x3 区域进行加权平均。我也对其他更快或更优雅的方法持开放态度。
很抱歉,我没有完全理解你最后一段的意思,所以我刚刚停在我得到所有坐标都具有最大值的地方。我使用 cv2.filter2D
对阈值图像进行卷积,然后使用 np.amax
和 np.where
找到具有最大值的坐标。
import cv2
import numpy as np
from timeit import default_timer as timer
img = cv2.imread('blob.png', 0)
start = timer()
_, thresh = cv2.threshold(img, 240, 1, cv2.THRESH_BINARY)
mask = np.ones((3, 3), np.uint8)
res = cv2.filter2D(thresh, -1, mask)
result = np.where(res == np.amax(res))
end = timer()
print(end - start)
我不知道它是否像你想要的那样有效,但输出是 0.0013461999999435648 s
P.S。您提供的图像有一个白色边框,我必须为此方法裁剪掉它。
一种方法是对图像进行子采样 并找到所需点的邻域。您可以通过不在所有像素上而是在例如每 5 个像素(循环中的 row=row+5
和 col=col+5
)。找到附近位置后,考虑该位置周围的特定邻域,并对该特定作物的整个像素进行循环以找到确切位置。
寻找 "subarray of shape 3x3 with the maximum sum" 与在使用非标准化 3x3 盒式过滤器过滤后寻找图像的最大值相同。所以它归结为有效地找到图像的最大值,你假设它是一个(可能 "noisy")底层连续和平滑信号的离散样本 - 因此你希望找到一个子像素位置。
您确实需要将问题分为两部分:
- 找到图像最大值的像素位置
m=(xm, ym)
。这只需要访问图像中的每个像素,并且每个像素进行一次比较,因此它是 O(N),因此只要您以原始图像分辨率操作,它就是最佳的。在 OpenCv 中,它是使用
minMaxLoc 函数。
- 应用您正在使用的图像的任何模型来找到它在 m 附近的(亚像素插值)最大值。
澄清第 (2) 点:你写
I don't want to just get the maximum point because that would cause the coordinate to not be very precise. The true center of the blob could actually be between 2 pixels
虽然从直觉上讲是合理的,但需要使此断言更加精确才能进行计算。也就是说,您需要以数学方式表达您对图像所做的假设,这些假设会让您搜索像素采样位置之间的 "true" 最大值。
此类假设的一个简单示例是二次平滑度。在这种情况下,您假设在 "true" 最大位置的一个小邻域(例如 3x3、5x5)中,图像信号 z
可以很好地近似于二次方:
z = A00 dx^2 + A01 dx dy + A11 dy^2 + A02 dx + A12 dy + A22
where:
dx = x - xm; dy = y - ym
根据泰勒级数定理,如果预期基础信号至少是三阶连续且可微的,则此假设是有意义的。从几何上讲,这意味着您假设(希望?)信号在其最大值附近看起来像二次曲面(抛物面或椭圆体)。
然后,您可以对 m
邻域中的每个像素计算上述方程,替换 z
的实际图像值,从而获得未知 Aij 中的线性系统,具有与相邻像素一样多的方程(因此即使是 3x3 邻域也会产生过度约束的系统)。在最小二乘意义上求解系统会得到 "optimal" 系数 Aij。该模型预测的理论最大值是一阶偏导数消失的位置:
del z / del dx = 2 A00 dx + A01 dy = 0
del z / del dy = A01 dx + 2 A11 dy = 0
这是一个在两个未知数(dx, dy)
中的线性系统,对其求解得到最大值的估计位置,并通过上面的等式z
,得到最大值处的预测图像值.
就计算成本而言,与遍历中等大小的图像相比,所有此类模型估计都非常快。
根据我对图像处理的了解,要获得适用于任何 一个斑点的可靠结果,请执行以下步骤:
- 将图像设为灰度 如果还没有(像素值 0-255)
- 标准化图像,使像素强度覆盖 0-255
的整个范围
- 将图像转换为二进制(像素为 0 或 1)- 这可以通过阈值处理来实现,例如应用小于或等于 127 的任何像素的规则in intensity 的强度为 0,其他任何强度的强度为 1
- 求出所有值为“1”的像素的加权平均值
或
- 对图像进行苹果侵蚀,直到剩下 2 个像素或 1 个像素。
案例一
如果你有两个像素,那么你需要找到两个像素的 u 和 v 坐标。斑点的中心将是像素的 u 坐标和 v 坐标之间的中点。
案例二
如果还剩一个像素,那么该像素的坐标就是中心点。
——————
您在 Python 中提到要快速实现此目标:
Python 被设计为一种解释型语言,因此它逐行执行,因此不太适合图像处理等高度迭代的任务。但是,您可以使用像 OpenCV (https://docs.opencv.org/2.4/index.html) 这样用 C 语言编写的库来缓解这种情况,同时让手头的任务对您来说变得容易得多。
OpenCV 也为我在上面列出的所有步骤提供了解决方案,因此你应该能够相当快地获得可靠的解决方案,虽然我不能确定它是否会命中您的目标是每几毫秒 50 张图像。其他要考虑的因素是您正在处理的图像的大小。这将以指数方式增加处理负载。
更新
我刚找到一篇很好的文章,几乎与我的步骤过程相呼应:
https://www.learnopencv.com/find-center-of-blob-centroid-using-opencv-cpp-python/
更重要的是,它还表示在数学上找到质心的公式为:
c = (1/n)西格玛(n, i = 1, x_i)
但是这篇文章写的比我在这里写的要好。
假设我有一个图像,我想找到一个形状为 3x3 的子数组,该子数组与其他子数组相比包含最大和。
如何在 python 中高效地执行此操作(运行 尽可能快)?如果能提供一个示例代码就好了。
我的具体问题: 我想在此热图中提取斑点中心的位置
我不想只得到最大点,因为那样会导致坐标不是很精确。斑点的真正中心实际上可能在 2 个像素之间。因此,最好在许多点之间进行加权平均以获得亚像素精度。例如,如果有 2 个点 (x1,y1) 和 (x2,y2),其值为 200 和 100。那么平均坐标将为 x=(200*x1+100*x2)/300
y=(200*y1+100*y2)/300
我的一个解决方案是做一个卷积运算。但我认为它不够有效,因为它需要乘法到内核(只包含一个)。我正在寻找一个快速的实现,所以我不能自己循环,因为我不确定它是否会很快。
我想每隔几毫秒对 50 张图像执行此算法。 (图片成批进来)。具体来说,将这些图像视为输出热图的机器学习模型的输出。为了从这些热图中获取坐标,我需要在高强度坐标之间进行某种加权平均。我的想法是对图像上的 3x3 区域进行加权平均。我也对其他更快或更优雅的方法持开放态度。
很抱歉,我没有完全理解你最后一段的意思,所以我刚刚停在我得到所有坐标都具有最大值的地方。我使用 cv2.filter2D
对阈值图像进行卷积,然后使用 np.amax
和 np.where
找到具有最大值的坐标。
import cv2
import numpy as np
from timeit import default_timer as timer
img = cv2.imread('blob.png', 0)
start = timer()
_, thresh = cv2.threshold(img, 240, 1, cv2.THRESH_BINARY)
mask = np.ones((3, 3), np.uint8)
res = cv2.filter2D(thresh, -1, mask)
result = np.where(res == np.amax(res))
end = timer()
print(end - start)
我不知道它是否像你想要的那样有效,但输出是 0.0013461999999435648 s
P.S。您提供的图像有一个白色边框,我必须为此方法裁剪掉它。
一种方法是对图像进行子采样 并找到所需点的邻域。您可以通过不在所有像素上而是在例如每 5 个像素(循环中的 row=row+5
和 col=col+5
)。找到附近位置后,考虑该位置周围的特定邻域,并对该特定作物的整个像素进行循环以找到确切位置。
寻找 "subarray of shape 3x3 with the maximum sum" 与在使用非标准化 3x3 盒式过滤器过滤后寻找图像的最大值相同。所以它归结为有效地找到图像的最大值,你假设它是一个(可能 "noisy")底层连续和平滑信号的离散样本 - 因此你希望找到一个子像素位置。
您确实需要将问题分为两部分:
- 找到图像最大值的像素位置
m=(xm, ym)
。这只需要访问图像中的每个像素,并且每个像素进行一次比较,因此它是 O(N),因此只要您以原始图像分辨率操作,它就是最佳的。在 OpenCv 中,它是使用 minMaxLoc 函数。 - 应用您正在使用的图像的任何模型来找到它在 m 附近的(亚像素插值)最大值。
澄清第 (2) 点:你写
I don't want to just get the maximum point because that would cause the coordinate to not be very precise. The true center of the blob could actually be between 2 pixels
虽然从直觉上讲是合理的,但需要使此断言更加精确才能进行计算。也就是说,您需要以数学方式表达您对图像所做的假设,这些假设会让您搜索像素采样位置之间的 "true" 最大值。
此类假设的一个简单示例是二次平滑度。在这种情况下,您假设在 "true" 最大位置的一个小邻域(例如 3x3、5x5)中,图像信号 z
可以很好地近似于二次方:
z = A00 dx^2 + A01 dx dy + A11 dy^2 + A02 dx + A12 dy + A22
where:
dx = x - xm; dy = y - ym
根据泰勒级数定理,如果预期基础信号至少是三阶连续且可微的,则此假设是有意义的。从几何上讲,这意味着您假设(希望?)信号在其最大值附近看起来像二次曲面(抛物面或椭圆体)。
然后,您可以对 m
邻域中的每个像素计算上述方程,替换 z
的实际图像值,从而获得未知 Aij 中的线性系统,具有与相邻像素一样多的方程(因此即使是 3x3 邻域也会产生过度约束的系统)。在最小二乘意义上求解系统会得到 "optimal" 系数 Aij。该模型预测的理论最大值是一阶偏导数消失的位置:
del z / del dx = 2 A00 dx + A01 dy = 0
del z / del dy = A01 dx + 2 A11 dy = 0
这是一个在两个未知数(dx, dy)
中的线性系统,对其求解得到最大值的估计位置,并通过上面的等式z
,得到最大值处的预测图像值.
就计算成本而言,与遍历中等大小的图像相比,所有此类模型估计都非常快。
根据我对图像处理的了解,要获得适用于任何 一个斑点的可靠结果,请执行以下步骤:
- 将图像设为灰度 如果还没有(像素值 0-255)
- 标准化图像,使像素强度覆盖 0-255 的整个范围
- 将图像转换为二进制(像素为 0 或 1)- 这可以通过阈值处理来实现,例如应用小于或等于 127 的任何像素的规则in intensity 的强度为 0,其他任何强度的强度为 1
- 求出所有值为“1”的像素的加权平均值
或
- 对图像进行苹果侵蚀,直到剩下 2 个像素或 1 个像素。
案例一
如果你有两个像素,那么你需要找到两个像素的 u 和 v 坐标。斑点的中心将是像素的 u 坐标和 v 坐标之间的中点。
案例二
如果还剩一个像素,那么该像素的坐标就是中心点。
——————
您在 Python 中提到要快速实现此目标:
Python 被设计为一种解释型语言,因此它逐行执行,因此不太适合图像处理等高度迭代的任务。但是,您可以使用像 OpenCV (https://docs.opencv.org/2.4/index.html) 这样用 C 语言编写的库来缓解这种情况,同时让手头的任务对您来说变得容易得多。 OpenCV 也为我在上面列出的所有步骤提供了解决方案,因此你应该能够相当快地获得可靠的解决方案,虽然我不能确定它是否会命中您的目标是每几毫秒 50 张图像。其他要考虑的因素是您正在处理的图像的大小。这将以指数方式增加处理负载。
更新
我刚找到一篇很好的文章,几乎与我的步骤过程相呼应:
https://www.learnopencv.com/find-center-of-blob-centroid-using-opencv-cpp-python/
更重要的是,它还表示在数学上找到质心的公式为:
c = (1/n)西格玛(n, i = 1, x_i)
但是这篇文章写的比我在这里写的要好。