我需要在图像上选择一些点。在有大量颜色变化、过渡和变化的情况下,应更多地选择这些点。我可以使用哪些技术来确定图像中颜色变化和过渡最多的位置?
定位图像中的非同质区域
通常,要采取的方法是具有对图像中的此类区域具有高价值的局部特征。
有许多方法可以塑造这样的特征。
可能最简单的方法是局部方差。
我尝试了 3 种不同的方法:
- 过滤器的局部方差。
- 超级像素的局部方差。
- 使用来自单个图像的噪声级估计的弱纹理(作者:Masayuki Tanaka)。
我将它应用于 Lenna 图像并得到:
较高的值显示非均匀区域。
对于高 SNR 图像,您似乎可以使用局部方差。但超像素方法似乎更稳健。
在我看来,Super Pixel 结果是所有 3 中最好的。
它是通过以下方式实现的:
- 应用基于超像素的分割(基于 SLIC)。
- Per Label Mean
通过每个超像素的索引计算每个超像素的平均值。 - Per Label Variance
仅使用其像素计算每个超像素的方差。
在更一般的形式中,如果您寻找同质性特征并使用它们来找到同质区域,然后选择逆。这些在细分中非常流行。
另一种方法是使用更高级的功能,例如:
- 轻快的功能。
- 快速功能。
- 生猪功能。
- MSER 特征。
然后计算在每个超级像素中找到了多少。具有更多功能的超级像素将不那么同质化。
完整代码可在我的StackExchange Signal Processing Q75536 GitHub 存储库中找到。
更新:今天我遇到了用于图像同质性量化的鲁棒无分割算法。
二维小波变换非常适合。它是一维 CWT 的扩展,我们将不同中心频率和“尺度”(时域宽度)的小波关联起来。
可以校准小波以检测
- 快速或慢速变化
- 在小的、局部的或大的、分散的图像部分
输出是一个 3D 数组,索引为:
x
: 以图像为中心的小波 x 坐标y
: 以图像为中心的小波 y 坐标n
:小波(产生输出)、控制频率(变化率)、宽度(变化的空间范围)和角度(变化的方向)
argmax
然后,通过适当的单位转换索引并接管 2D 切片,可以找到例如“2cm x 2cm 区域上的最大变化” ;每个切片是每个变化的“强度热图” n
。通过低通输出模数,以牺牲空间定位为代价,使输出对噪声具有鲁棒性。
例子
图片(来源):

小波示例(j
= 比例指数(宽度 + 频率),theta
= 角度指数):

按比例分组

scale=0
捕捉小区间的快速变化,同时scale=3
捕捉大区域的缓慢变化
按角度分组

通过对所有尺度进行平均来混淆方向信息。假设我们希望检测这种转变:

然后我们想要一个陡峭定向的大规模小波。事实上:

高阶变异
通过对小波变换进行小波变换,捕捉变化的变化得到:

(scale=0
由于理论上的原因缺失)与一阶相比,我们看到左下角更强烈,这是由于颜色以变化的速度移动(更快地朝向分形奇点)。
优先快速
为了保持最快的变化, set J=1
,它省略了低频小波并使低通变窄(也减少了子采样):

输出太多?
只需使用您需要的那些。检查使用的小波,并保留一个子集(或仅一个);代码中的示例。
J=3
没有二次采样/低通
不能使用库代码,请参阅答案下方的评论。


莉娜示例
代码;可能会改善

走得更远
可以在具有分割目标的散射特征之上训练卷积网络;小波散射在有限数据设置的许多基准测试中达到了 SOTA。
图书馆
目前处于开发阶段,您可以通过以下方式获取最新版本(从此处):
pip install git+https://github.com/kymatio/kymatio.git@refs/pull/674/head
或克隆dev
没有kymatio.visuals
. 内置模数 + 低通 + 二次采样,中心频率与比例相关。
代码
import numpy as np
import PIL
from kymatio.numpy import Scattering2D
from kymatio.visuals import imshow
def load_rgb(img):
img = np.array(PIL.Image.open(path).convert("RGB")).astype('float64')
img /= np.abs(img).max(axis=(0, 1))
return img
def group_by_angle(out, J, L):
n_S1 = J * L # number of first-order coeffs
S1_all = out[1:n_S1 + 1]
theta_all = list(range(L))
S1_slices = {k: [] for k in theta_all}
i = 0
for p1 in S.psi:
S1_slices[p1['theta']].append(S1_all[i][None])
i += 1
for theta in S1_slices:
S1_slices[theta] = np.vstack(S1_slices[theta]).mean(axis=0)[None]
S1_theta = np.vstack(list(S1_slices.values()))
return S1_theta
def group_by_scale(out, J, L):
n_S1 = J * L # number of first-order coeffs
S1_all = out[1:n_S1 + 1]
S1_scale = S1_all.reshape(L, -1, *out.shape[-2:], order='F').mean(axis=0)
return S1_scale
def viz_by_angle(S1_theta, L, ticks=0):
for theta in range(L):
th = int(L - L/2 - 1) - theta
angle = 90 * th / L
imshow(S1_theta[theta], title=f"angle={angle}",
w=.5, h=.5, abs=1, ticks=ticks)
def viz_by_scale(S1_scale, J, ticks=0, second_order=False):
J = J - 1 if second_order else J
for j in range(J):
j_title = j + 1 if second_order else j
imshow(S1_scale[j], title=f"scale={j_title}",
w=.5, h=.5, abs=1, ticks=ticks)
def viz_all_first_order(out, J, L, ticks=0):
n_S1 = J * L # number of first-order coeffs
S1_all = out[1:n_S1 + 1]
for i, o in enumerate(S1_all):
j, theta = S.psi[i]['j'], S.psi[i]['theta']
th = int(L - L/2 - 1) - theta
angle = 90 * th / L
imshow(o, title=f"angle={angle}, scale={j}",
abs=1, w=.5, h=.5, ticks=ticks)
def group_by_angle_second_order(out, J, L):
n_S1 = J * L
S2_all = out[n_S1 + 1:]
theta_all = list(range(L))
S2_slices = {k: [] for k in theta_all}
i = 0
for p1 in S.psi:
j1 = p1['j']
for n2, p2 in enumerate(S.psi):
j2 = p2['j']
if j2 <= j1:
continue
S2_slices[p2['theta']].append(S2_all[i][None])
i += 1
for theta in S2_slices:
if S2_slices[theta] != []:
S2_slices[theta] = np.vstack(S2_slices[theta]).mean(axis=0)[None]
S2 = np.vstack([arr for arr in S2_slices.values() if len(arr) != 0])
return S2
def group_by_scale_second_order(out, J, L):
n_S1 = J * L
S2_all = out[n_S1 + 1:]
j_all = list(range(J))
S2_slices = {k: [] for k in j_all}
i = 0
for p1 in S.psi:
j1 = p1['j']
for n2, p2 in enumerate(S.psi):
j2 = p2['j']
if j2 <= j1:
continue
S2_slices[p2['j']].append(S2_all[i][None])
i += 1
for j in S2_slices:
if S2_slices[j] != []:
S2_slices[j] = np.vstack(S2_slices[j]).mean(axis=0)[None]
S2 = np.vstack([arr for arr in S2_slices.values() if len(arr) != 0])
return S2
#%%
path = r"C:\Desktop\colors.png"
img = load_rgb(path)
#%%
imshow(img, w=.6, h=.6, title="%s x %s image" % img.shape[:2])
#%%
J, L = 4, 8 # largest scale; number of angles
S = Scattering2D(shape=img.shape[:2], L=L, J=J)
#%%
# take 3 wavelet transforms on each channel and average
out = np.mean([S(im) for im in img.transpose(-1, 0, 1)], axis=0)
#%%
S1_scale = group_by_scale(out, J, L)
viz_by_scale(S1_scale, J)
#%%
S1_theta = group_by_angle(out, J, L)
viz_by_angle(S1_theta, L)
#%%
S2_theta = group_by_angle_second_order(out, J, L)
viz_by_angle(S2_theta, L)
#%%
S2_scale = group_by_scale_second_order(out, J, L)
viz_by_scale(S2_scale, J, second_order=True)
#%%
viz_all_first_order(out, J, L)