如何在matlab / octave中对这个丑陋的嵌套循环进行矢量化?

时间:2017-09-04 15:04:43

标签: matlab vectorization octave

我无法加速以下功能并阅读各种“如何在Matlab / Octave中进行矢量化”,这对于这个特定的主题并没有帮助我。

这是我想要实现的目标: 我有一组2d采样点(给定为x-y坐标对)和一组线段(也是x-y坐标对)。这些点大致靠近直线,我想得到每个采样点到最近的线段的距离,但是只有当我可以将样本垂直投影到线段上时才会这样。

因此,虽然我已经设法将算法放入嵌套的for循环中并且它为我提供了附加示例的正确结果,但它对于真实数据集(大约4000个采样点和6000个线段)来说非常慢(如预期的那样)而且,看起来真的很难看。

有人可以帮我创建这段代码的更复杂版本吗?

编辑:此算法中使用的数学可以在这里查找: http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html

clc;
clear all;
close all;

ptsx = [0.5 3 5];
ptsy= [1 -1.5 0.5];
points = [ptsx; ptsy];

linesx = [0 2 4 6];
linesy = [0 0 0 0];
lines = [linesx; linesy];

% for each point in the sample dataset
for k=1:1:length(points(1,:))
  clear 'distvec'
  % calculate the distance to each line segment in the model dataset
  for l=1:1:length(lines(1,:))-1

    % vector of the line segment
    a = [lines(1,l+1)-lines(1,l), lines(2,l+1)-lines(2,l)];
    % vector from the start of the line segment to the sample point
    b = [points(1,k) - lines(1,l), points(2,k) - lines(2,l)];

    % check if the sample point can be projected onto the line segment
    if norm(a) ~= 0
      lba = dot(a,b)/norm(a)^2;
    else
      lba = -1;
    end

    if (lba >= 0) && (lba <= 1)
      % calculate distance from sample point to single line segment
      x1 = [lines(1,l) lines(2,l)];
      x2 = [lines(1,l+1) lines(2,l+1)];
      x0 = [points(1,k) points(2,k)];
      dist = abs(det([x2-x1; x1-x0]))/norm(x2-x1);
      distvec(end+1) = dist;
    end
  end
  dist(end+1) = min(distvec);

end

figure;
hold on;
plot(ptsx, ptsy, 'bo');
plot(linesx, linesy, 'r-o');
xlim([-1 7]);
ylim([-2 2]);

2 个答案:

答案 0 :(得分:2)

@Edit Variant没有循环,但最真实的矢量化我可以管理。目前我无法测试它(它需要r2016b和更新),但根据documentation它应该工作

ptsx = [3 0.5 5];
ptsy= [-1.5 1 0.5];
%order row is [x,y]
points = [ptsx; ptsy]';

linesx = [0 2 4 6];
linesy = [0 0 0 0];
lines = kron([linesx;linesy],[1 1]);
lines = lines(:,2:size(lines,2)-1);
%each row is [x1 y1 x2 y2]
lines=reshape(lines,4,[])';

lenx = lines(:,3) - lines(:,1);
leny = lines(:,4) - lines(:,2);
%remove degenerated lines
sq_norm = [lenx.*lenx + leny.*leny]';
rem_idx = sq_norm < eps;
sq_norm(rem_idx) = 1;

%Starting from r2016b no need of dirty hacks with bsxfun. Finally!
diff_startx = points(:,1) - lines(:,1)';
diff_starty = points(:,2) - lines(:,2)';

pos = (diff_startx .* lenx + diff_starty .* leny) ./ sq_norm;
pos(pos < 0) = 0;
pos(pos > 1) = 1;
dist = hypot(pos .* lenx - diff_startx, pos .* leny - diff_starty);

我没有循环的变种

pts = [ 3   0.5 5;... %reordered for test purposes
       -1.5 1   0.5];
cpts = num2cell(pts,1);

lines = [0 2 4 6;...
         0 0 0 0];
%convert and split intercepts into two x-y pairs
lines = kron(lines,[1 1]);
lines = lines(:,2:size(lines,2)-1); %suggesting three intercepts
clines = mat2cell(lines, 2, repmat(2,1,size(lines,2)/2));

% [lines(1,2) - lines(1,1), lines(2,2) - lines(2,1)]
diff_lines = diff(lines,1,2); 
diff_lines = diff_lines(:,1:2:size(diff_lines,2));
diff_lines = num2cell(diff_lines,1);
% norm for each line
norms = cellfun(@(x) norm(x), diff_lines,'un',0);
idx = cellfun(@(x) x~=0, norms);

% [lines(1,2) - lines(1,1), lines(2,2) - lines(2,1)]
diff_start = cellfun(@(x,y) cellfun(@(z) {x(:,1)-z(:,1)},y),...
             cpts,num2cell(repmat(clines, numel(cpts),1),2)','un',0);
%choose corresponding to nonzero norm
lba = cellfun(@(x,y,n) cellfun(@(b) dot(x,b)/norm(n)^2, y, 'un', 0) ...
              ,diff_lines(idx),diff_start(idx),norms(idx),'un',0);
%find first matching lba for each point
idx_line = cellfun(@(x) find(cellfun(@(y) (y >= 0) & (y <= 1), x),1,'first'), lba);
%reorder intermediate results
clines = clines(idx_line);
norms = norms(idx_line);
diff_lines = diff_lines(idx_line);
% [lines(1,2) - pounts(1,1), lines(2,2) - pounts(2,1)]
diff_start = cellfun(@(x,y) y(:,1)-x(:,1),...
             cpts,clines,'un',0);
dists = cellfun(@(x,y,n) abs(det([x'; y']))/n, diff_lines, diff_start, norms)

编辑TS代码以使用MATLAB

lines = [linesx; linesy];
% Edit1: distance storage preallocation
distmat = nan(length(ptsx),length(linesx)-1);
for k=1:1:length(points(1,:))
...
dist = abs(det([x2-x1; x1-x0]))/norm(x2-x1);
% Edit 2: filling the storage
      distmat(k,l) = dist;
    end
...
% Edit 3: getting the distance
dists_vals = cellfun(@(x) min(x(~isnan(x))), num2cell(distmat,2))

答案 1 :(得分:2)

GNU Octave的解决方案,也应该在MATLAB中工作。如果没有,请报告。

ptsx = [0.5 3 5];
ptsy= [1 -1.5 0.5];
linesx = [0 2 4 6];
linesy = [0 0 0 0];

p = [ptsx;ptsy];
% start of lines
l = [linesx(1:end-1);linesy(1:end-1)];
% vector perpendicular on line
v = [diff(linesy);-diff(linesx)];
% make unit vector
v = v ./ hypot (v(1,:),v(2,:));
v = repmat (v, 1, 1, size (p, 2));
% vector from points (in third dimension) to start of lines (second dimension)
r = bsxfun (@minus, permute (p, [1 3 2]), l);
d = abs (dot (v, r));
dist = squeeze (min (d, [], 2))

给出

dist =

   1.00000
   1.50000
   0.50000