寻找径向线段的最近邻
Posted
技术标签:
【中文标题】寻找径向线段的最近邻【英文标题】:Finding nearest neighbours of radial segments 【发布时间】:2016-01-08 07:27:59 【问题描述】:首先,不要被这个问题的样子吓到;)
我正在尝试在 matlab 中实现一个称为圆形模糊形状模型的形状描述符,其中一部分是获取每个径向段的最近邻居列表,如图 1d 所示)
我在 MATLAB 中进行了直接而简单的实现,但我被困在算法的第 5 步和第 6 步,主要是因为我无法理解定义:
Xbc,s = b1, ..., bc*s as the sorted set of the elements in B*
so that d(b*c,s, bi*) <= d(b*c,s, bj*), i<j
对我来说,这听起来像是级联排序,首先按升序距离排序,然后按升序索引排序,但我找到的最近邻居不是根据论文。
作为一个例子,我向你展示了我为片段 b4,1 获得的最近邻居,这是图 1d 中标记为“EX”的那个)
我得到以下 b4,1 的最近邻居列表:b3,2, b3,1, b3,8, b2,1, b2,8
根据论文正确的是:b4,2, b4,8, b3,2, b3,1, b3,8
但是我的点实际上是最接近欧几里得距离测量的选定段的集合!距离b4,1 <=> b2,1
小于b4,1 <=> b4,2
或b4,1 <=> b4,8
...
这是我的(丑陋但直截了当的)MATLAB 代码:
width = 734;
height = 734;
assert(width == height, 'Image must be square in size!');
% Radius of the correlogram
R = width;
% Number of circles in correlogram
C = 4;
% Number of sections in correlogram
S = 8;
% "width" of ring segments
d = R/C;
% angle of one segment in degrees
g = 360/S;
% set of bins for the circular description of I
B = zeros(C, S);
% centroid coordinates for bins
B_star = zeros(C,S,2);
% calculate centroids of bins
for c=1:C
for s=1:S
alpha = deg2rad(max(s-1, 0)*g + g/2);
r = d*max((c-1),0) + d/2;
B_star(c,s,1) = r*cos(alpha);
B_star(c,s,2) = r*sin(alpha);
end
end
% create sorted list of bin numbers which fullfill
% d(bc,s*, bi*) <= d(bc,s, bj*) where i<j
% B_star_dists is a simple square distance matrix for getting
% the distance between two centroids c_i,s_i and c_j,s_j
B_star_dists = zeros(C*S, C*S);
for i=1:C*S
[c_i, s_i] = ind2sub([C,S], i);
% x,y centroid coordinates for point i
b_star_i = [B_star(c_i, s_i, 1), B_star(c_i, s_i, 2)];
for j=1:C*S
[c_j, s_j] = ind2sub([C,S], j);
% x,y centroid coordinates for point j
b_star_j = [B_star(c_j, s_j, 1), B_star(c_j, s_j, 2)];
% store the euclidean distance between these two centroids
% in the distance matrix.
B_star_dists(i,j) = norm(b_star_i - b_star_j);
end
end
% calculate nearest neighbour "centroids" for each centroid
% B_NN is a cell array, Bidx gives an array of indexes to the
% nearest neighbour centroids.
B_NN = cell(C*S, 1);
for i=1:C*S
[c_i, s_i] = ind2sub([C,S], i);
% get a (C*S)x2 matrix of all distances, the first column are the array
% indexes and the second column are the distances e.g
% 1 d1
% 2 d2
% .. ..
% CS dc,s
dists = [transpose(1:C*S), B_star_dists(:, i)];
% sort ascending by the distances first (e.g second column) then
% sort ascending by the array index (e.g first column)
dists = sortrows(dists, [2,1]);
% middle section has nine neighbours, set as default
neighbour_count = 9;
if c_i == 1
% inner region has S+3 neighbours
neighbour_count = S+3;
elseif c_i == C
% outer most ring has 6 neighbours
neighbour_count = 6;
end
B_NNi = dists(1:neighbour_count,1);
end
% FROM HERE ON JUST VISUALIZATION CODE
figure(1);
hold on;
for c=1:C
% plot circles
r = c*d;
plot(r*cos(0:pi/50:2*pi), r*sin(0:pi/50:2*pi), 'k:');
end
for s=1:S
% plot lines
line_len = C*d;
alpha = deg2rad(s*g);
start_pt = [0, 0];
end_pt = start_pt + line_len.*[cos(alpha), sin(alpha)];
plot([start_pt(1), end_pt(1)], [start_pt(2), end_pt(2)], 'k-');
end
for c=1:C
% plot centroids of segments
for s=1:S
segment_centroid = B_star(c,s, :);
plot(segment_centroid(1), segment_centroid(2), '.k');
end
end
% plot some nearest neighbours
% list of [C;S]
plot_nn = [4;1];
for i = 1:size(plot_nn,2)
start_c = plot_nn(1,i);
start_s = plot_nn(2,i);
start_pt = [B_star(start_c, start_s,1), B_star(start_c, start_s,2)];
start_idx = sub2ind([C, S], start_c, start_s);
plot(start_pt(1), start_pt(2), 'xb');
nn_idx_list = B_NNstart_idx;
for j = 1:length(nn_idx_list)
nn_idx = nn_idx_list(j);
[nn_c, nn_s] = ind2sub([C, S], nn_idx);
nn_pt = [B_star(nn_c, nn_s,1), B_star(nn_c, nn_s,2)];
plot(nn_pt(1), nn_pt(2), 'xr');
end
end
论文全文可见here
【问题讨论】:
论文在哪里说b4,1
最近的邻居是什么?
其中一个地块里有一只蝙蝠。
在步骤 2 和插图 (a) 中,g
是连续扇区之间的度数应该是 360/S
而不是 S/360
。
您是否考虑过不使用欧几里得距离,而是使用其他度量?就像保持你的极坐标(这是自然的),并使用标准化的距离,其中 1 个角段与 1 个径向段的宽度相同。甚至更好:将角度*径向离散化保持为 2d 索引 [1:n1,1:n2],并将邻居识别为每个索引相差不超过 1 的段(考虑角度变量的周期性边界) .
看起来图1D显示的不是最近的段,而是相邻的段,这完全是另一回事。
【参考方案1】:
论文谈到“区域邻居”;将这些是欧几里得距离意义上的“最近邻”的解释是不正确的。它们只是与某个区域相邻的区域,找到它们的方法很简单:
区域有 2 个坐标:(c,s) 其中 c 表示它们属于哪个同心圆,从中心的 1 到边缘的 C,s 表示它们属于哪个扇区,从 1从 0° 角开始到 S 角,以 360° 角结束。
c 和 s 坐标与区域坐标最多相差 1 的每个区域都是相邻区域(段号从 S 环绕到 1。)根据区域的位置,有 3 种情况: (如图1d所示)
区域是中间区域(标记为 MI)例如区域 b(2,4)
有 2 个相邻的圆圈和 2 个相邻的扇区,所以总共 9 个区域:
第 1、2 或 3 圈以及第 3、4 或 5 区的每个区域:b(1,3), b(2,3), b(3,3), b(1,4), b(2,4), b(3,4), b(1,5), b(2,5), b(3,5)
区域是内部区域(标记为 IN),例如区域 b(1,8)
只有1个相邻圈和2个相邻扇区,但所有内部区域都是邻居,所以总共S + 3个区域:
第 2 圈和第 7、8 或 1 区的每个区域:b(2,7), b(2,8), b(2,1)
以及内圈的每个区域:b(1,1), b(1,2), b(1,3), b(1,4), b(1,5), b(1,6), b(1,7), b(1,8)
该区域是外部区域(标记为 EX),例如区域 b(3,1)
只有一个相邻的圆圈和两个相邻的扇区,所以总共有6个区域:
第 2 或 3 圈以及第 8、1 或 2 区的每个区域:b(2,8), b(2,1), b(2,2), b(3,8), b(3,1), b(3,2)
【讨论】:
【参考方案2】:要向@m69 的great answer 添加一点matlab,您可以像这样自动索引邻居:
%assume C and S defined according to the answer of @m69
iif=@(varargin) varargin2*find([varargin1:2:end], 1, 'first')();
ncfun=@(c) iif(c==1,c+1,c==C,c-1,true,[c-1, c+1]);
nsfun=@(s)mod([s-1, s+1]-1,S)+1;
neighbs=@(c,s) [b(c,nsfun(s)), b(ncfun(c),s)', reshape(b(ncfun(c),nsfun(s)),1,[])];
第一个定义 an inline if 以用于匿名函数,从而不必在单独的文件中定义函数(c
情况下需要这样做)。这具有语法iif(condition1,result1,condition2,result2,...)
,其中每个条件一个接一个地进行测试,并返回与产生true
的第一个条件相对应的结果。
第二个使用 if: return [c-1, c+1] 定义径向相邻索引,除非其中任何一个未定义(这将导致违反数组绑定)
第三个定义了角度扇区的周期性索引,例如 S=4
nsfun(2)==[1, 3]
和 nsfun(4)==[3, 1]
。
我刚刚添加了一个示例,其中对于给定的一对有效 c,s
neighbs(c,s)
将返回 b(1:C,1:S)
的子数组,它们是邻居:首先是上下/左右邻居,然后是 (最多)四个角落。
【讨论】:
感谢您添加此内容;我不知道关于 Matlab 的第一件事。 非常感谢!我绝对需要更多地研究 Matlab!【参考方案3】:在花了太多时间在这之后,我想通了,计算元素 bc,s 的相邻区域的技巧是:
取c的相邻环的所有段,包括c本身,即c-1, c, c+1
,如果是最内圈,则只取c,c+1
,如果是最外圈,则取c-1, c
计算从 bc,s 到上一步中所有选定质心的欧几里德距离,包括点 bc,s 本身
对距离进行升序排序,取前N段。
描述符工作得很好,但是它的旋转不变性取决于在某些情况下密度最高的轴,这对噪声/遮挡非常敏感,即描述符可能是 +/- 1 旋转关闭。
这是 MATLAB 中完整的 CBSM 描述符实现,未进行任何优化(我听说 MATLAB 讨厌 for 循环):
csbm.m
function [descriptor] = csbm(I, R, C, S)
% Input:
% ------
% I = The image, binary, must be square in sice
% R = The radius of the correlogram, could be half the image width
% C = Number of circles
% S = Number of segments per circle
% Output:
% -------
% A C*S-by-1 row vector, the descriptor
[width, height] = size(I);
assert(width == height, 'Image must be square in size!');
% "width" of ring segments
d = R/C;
% angle of one segment in degrees
g = 2*pi/S;
% centroid coordinates for bins
B_star = zeros(C*S,2);
% initialize the descriptor
descriptor = zeros(C*S,1);
% calculate centroids of bins
for c=1:C
for s=1:S
alpha = max(s-1, 0)*g + g/2;
r = d*max((c-1),0) + d/2;
ind = (c-1)*S+s; %sub2ind(cs_size, c,s);
B_star(ind, :) = [r*cos(alpha), r*sin(alpha)];
end
end
% calculate nearest neighbor regions
B_NN = cell(C*S,1);
for c=1:C
min_circle = max(1,c-1);
max_circle = min(C,c+1);
start_sel = (min_circle-1)*S+1;
end_sel = (max_circle)*S;
centroids = B_star(start_sel:end_sel, :);
for s=1:S
current_ind = (c-1)*S+s;
centroid = B_star(current_ind, :);
num_centroids = length(centroids);
distances = sqrt(sum((centroids-repmat(centroid, num_centroids,1)).^2,2));
distances = horzcat(distances, transpose((start_sel:end_sel)));
distances = sortrows(distances, [1,2]);
neighbour_count = 9;
if c == 1
% inner region has S+3 neighbours
neighbour_count = S+3;
elseif c == C
% outer most ring has 6 neighbours
neighbour_count = 6;
end
B_NNcurrent_ind = distances(1:neighbour_count, 2);
end
end
for x=1:width
x_centered = x-width/2;
for y=1:width
if I(x,y) == 0
continue;
end
y_centered = y-width/2;
% bin the image point
r = sqrt(x_centered^2 + y_centered^2);
a = wrapTo2Pi(atan2(y_centered, x_centered));
% get bin
c_pixel = max(1, floor(r/d));
s_pixel = max(1, floor(a/g));
if c_pixel > C
continue;
end
ind_pixel = (c_pixel-1)*S+s_pixel;
pt_pixel = [x_centered, y_centered];
% get neighbours of this bin
neighbours = B_NNind_pixel;
% calculate distance to neighbours
nn_dists = sqrt(sum((B_star(neighbours, :) - repmat(pt_pixel, length(neighbours), 1)).^2,2));
D = sum(1./nn_dists);
% update the probabilities vector
descriptor(neighbours) = descriptor(neighbours) + 1./(nn_dists.*D);
end
end
% normalize the vector v
descriptor = descriptor./sum(descriptor);
% make it rotationally invariant
G = zeros(S/2, 2*C);
for s=1:S/2
for c=1:C
G(s,c) = descriptor((c-1)*S+s);
G(s,c+C) = descriptor((c-1)*S+s+S/2);
end
end
[~, max_G_idx] = max(sum(G,2));
L_G = 0;
R_G = 0;
for j=1:C
for k=1:S
if k > (max_G_idx) && k < (max_G_idx+S/2)
L_G = L_G + descriptor((j-1)*S+k);
elseif k ~= max_G_idx && k ~= (max_G_idx+S/2)
R_G = R_G + descriptor((j-1)*S+k);
end
end
end
if L_G > R_G
% B is rotated k=i+S/2-1 positions to the left:
fprintf('rotate %d to the left\n', max_G_idx+S/2-1);
rotate_by = -(max_G_idx+S/2-1);
else
% B is rotated k=i-1 positions to the right:
fprintf('rotate %d to the right\n', max_G_idx-1);
rotate_by = -(max_G_idx-1);
end
% segments are grouped by circle
% so for every circle we get all segments and circular shift them
for c=1:C
range_sel = ((c-1)*S+1):(c*S);
segments = descriptor(range_sel);
descriptor(range_sel) = circshift(segments, [rotate_by,0]);
end
end
plot_descriptor.m
function plot_descriptor(R,C,S, descriptor)
% Input:
% ------
% R Radius for the correlogram in pixels, can be arbitrary
% C Number of circles
% S Number of segments per circle
% descriptor The C*S-by-1 descriptor vector
% "width" of ring segments
d = R/C;
% angle of one segment in degrees
g = 2*pi/S;
% full image
[x,y] = meshgrid(-R:R);
[theta, rho] = cart2pol(x,y);
theta = wrapTo2Pi(theta);
brightness = zeros(size(rho));
min_val = min(descriptor);
max_val = max(descriptor);
scale_fact = 1/(max_val-min_val);
for c=1:C
rInner = (c-1)*d;
rOuter = c*d;
for s=1:S
idx = (c-1)*S+s;
minTheta = (s-1)*g;
maxTheta = (s*g);
matching_theta = find(theta > minTheta & theta < maxTheta);
matching_rho = find(rho > rInner & rho < rOuter);
matching_idx = intersect(matching_theta, matching_rho);
intensity = descriptor(idx)*scale_fact;
brightness(matching_idx) = intensity;
end
end
figure; imshow(mat2gray(brightness));
如何运行:
I = imread('bat-18.gif');
I = transpose(im2bw(I, graythresh(I)));
I = edge(I);
[width, height] = size(I);
R = width/2;
C = 24;
S = 24;
descriptor = csbm(I, R, C, S);
plot_descriptor(R,C,S,descriptor);
输出:
只是为了好玩,在编码时出现了一些可怕的图片:
【讨论】:
for
循环在 matlab 中并不邪恶,但通常有更好的矢量化替代方案。近年来,如果使用得当,for
循环可以与内置循环一样高效。这当然取决于应用程序。您也可以制作其他速记:例如删除 find
s 并使用逻辑索引:matching_theta = theta > minTheta & theta < maxTheta; matching_rho = rho > rInner & rho < rOuter; matching_idx = matching_theta & matching_rho; brightness(matching_idx) = intensity;
。以上是关于寻找径向线段的最近邻的主要内容,如果未能解决你的问题,请参考以下文章