如何在 MATLAB 中跟踪多个对象何时接触?
How do I track when multiple objects touch in MATLAB?
我有从图像 (3744x5616) 跟踪的多个对象的 x、y 像素坐标。坐标存储在称为对象的结构中,例如
objects(1).centre = [1868 1236]
每个对象都由数字代码唯一标识,例如
objects(i).code = 33
我希望能够记录任何两个物体每次进入对方 300 像素半径范围内的时间。检查是否有任何对象正在接触,然后记录参与交互的两个对象的身份的最佳方法是什么,例如对象 33 与对象 34 交互。
谢谢!
我现在能想到的最好的方法是蛮力方法。只需检查一个对象与其他对象的中心的距离,然后手动检查距离是否小于 300 像素。
如果你想要这么快,我们或许应该在没有任何工具箱的情况下做到这一点。您可以使用 bsxfun
使用 vanilla MATLAB 智能地执行此操作。首先,为每个对象的 X
和 Y
坐标创建单独的数组:
points = reshape([objects.centre], 2, []);
X = points(1,:);
Y = points(2,:);
[objects.centre
] 访问结构中每个 centre
字段的单独坐标,并将它们解压缩到逗号分隔的列表中。我重塑这个数组,使其成为 2 行,其中第一行是 X
坐标,第二行是 Y
坐标。我提取出行并将它们放入单独的数组中。
接下来,为每个 X
和 Y
创建两个差异矩阵,其中行表示一个唯一坐标,列表示另一个唯一坐标。此矩阵内的值是第 i
行的点 i
与第 j
列的点 j
之间的差异:
Xdiff = bsxfun(@minus, X.', X);
Ydiff = bsxfun(@minus, Y.', Y);
bsxfun
代表Binary Singleton EXpansion 有趣动作。如果您熟悉 repmat
函数,它本质上是在幕后复制矩阵和向量,以便您操作的两个输入具有相同的大小。在这种情况下,我所做的是将 X
或 Y
指定为两个输入。一个是另一个的 transposed 版本。通过这样做 bsxfun
自动 广播 每个输入,以便输入在维度上匹配。具体来说,第一个输入是 X
的列向量,因此它会被重复并水平堆叠,次数与 X
中的值一样多。
同样,这是针对 Y
值完成的。执行此操作后,对两个输出执行按元素减法,并在 X
和 Y
的一个点和另一个点之间进行分量减法,其中该行为您提供第一个点,并且该专栏为您提供了第二点。作为玩具示例,假设我们有 X = [1 2 3]
。使用上述代码执行 bsxfun
调用会得到:
>> Xdiff = bsxfun(@minus, [1 2 3].', [1 2 3])
Xdiff =
## | 1 2 3
----------------------
1 | 0 -1 -2
2 | 1 0 -1
3 | 2 1 0
我在输出中放置了一些额外的字符,但这些仅用于说明并为您提供参考。通过从 ##
列中获取行值并从 ##
行中的列值中减去,可以得到所需的减法。例如,第一行第二列说明 1 - 2 = -1。第二行第三列说明 2 - 3 = -1。如果对 X
和 Y
点都执行此操作,则会得到一个点相对于对称矩阵中所有其他点的分量距离。
你会注意到这是一个对角线全为0的反对称矩阵...这是有道理的,因为一个点相对于自身的一维距离应该为0。左下角的三角形部分矩阵的符号是右边的相反符号……因为减法的顺序。如果你用点 2 减去点 1,做相反的减法会得到相反的符号。但是,我们假设行表示第一个对象,列表示第二个对象,因此您希望专注于 lower 一半。
现在,计算距离,并确保将三角形的上半部分或下半部分设置为 NaN
,因为在计算距离时,符号会被忽略。如果您不忽略这一点,我们会发现交互的重复对象,因此对象 3 和对象 1 的交互与对象 1 和对象 3 的交互不同。您显然不关心顺序,因此设置上或将三角形的一半降低到 NaN
以进行下一步。假设欧氏距离:
dists = sqrt(Xdiff.^2 + Ydiff.^2);
dists(tril(ones(numel(objects))==1)) = NaN;
第一行计算所有点对的欧氏距离,我们使用tril
提取由所有逻辑1
组成的矩阵的下三角部分。提取这个矩阵,我们用它来将矩阵的下半部分设置为NaN
。这允许我们跳过我们不感兴趣的条目。请注意,我还将对角线设置为 0,因为我们对一个对象与其自身的距离不感兴趣。
既然您终于来到了这里,请搜索那些小于 300 像素的对象:
[I,J] = find(dists < 300);
I
和 J
是 row/column 对,它们确定矩阵中哪些行和列的值 < 300,因此在我们的例子中,每对 I
数组中的 J
为您提供彼此靠近的对象位置。
要最终找出正确的目标代码,您可以这样做:
codes = [[objects(I).code].' [objects(J).code].'];
这使用 I
和 J
访问逗号分隔列表中相似对象的相应代码,并将它们并排放入 N x 2
矩阵中。因此,codes
的每一行都会为您提供满足距离要求的唯一对象对。
用于复制和粘贴:
points = reshape([objects.centre], 2, []);
X = points(1,:);
Y = points(2,:);
Xdiff = bsxfun(@minus, X.', X);
Ydiff = bsxfun(@minus, Y.', Y);
dists = sqrt(Xdiff.^2 + Ydiff.^2);
dists(tril(ones(numel(objects))==1)) = NaN;
[I,J] = find(dists < 300);
codes = [[objects(I).code].' [objects(J).code].'];
玩具示例
这里有一个例子,我们可以用它来验证我们所拥有的是否正确:
objects(1).centre = [1868 1236];
objects(2).centre = [2000 1000];
objects(3).centre = [1900 1300];
objects(4).centre = [3000 2000];
objects(1).code = 33;
objects(2).code = 34;
objects(3).code = 35;
objects(4).code = 99;
我用不同的质心和不同的代码初始化了 4 个对象。让我们看看 dists
数组在计算后给我们的是什么:
>> format long g
>> dists
dists =
NaN 270.407100498489 71.5541752799933 1365.69396278961
NaN NaN 316.227766016838 1414.2135623731
NaN NaN NaN 1303.84048104053
NaN NaN NaN NaN
我故意让最后一个点比其他三个点都远,以确保我们可以展示不靠近其他点的情况。
如您所见,点 (1,2) 和 (1,3) 都彼此靠近,这是我们完成其余代码后得到的结果。这对应于具有 (33,34) 和 (33,35) 配对的对象 33、34 和 35。代码为34和35的点我做了稍微小一点,但还是大于300像素的阈值,所以也不算:
>> codes
codes =
33 34
33 35
现在,如果您想以美化的格式显示它,也许可以使用 for
循环:
for vec = codes.'
fprintf('Object with code %d interacted with object with code %d\n', vec(1), vec(2));
end
这个 for
循环有点棘手。一个鲜为人知的事实是 for
loops can also accept matrices 和索引变量从左到右一次为您提供每个矩阵的一列。因此,我转置了 codes
数组,使每对唯一代码成为一列。我只是访问每列的第一个和第二个元素并将其打印出来。
我们得到:
Object with code 33 interacted with object with code 34
Object with code 33 interacted with object with code 35
我有从图像 (3744x5616) 跟踪的多个对象的 x、y 像素坐标。坐标存储在称为对象的结构中,例如
objects(1).centre = [1868 1236]
每个对象都由数字代码唯一标识,例如
objects(i).code = 33
我希望能够记录任何两个物体每次进入对方 300 像素半径范围内的时间。检查是否有任何对象正在接触,然后记录参与交互的两个对象的身份的最佳方法是什么,例如对象 33 与对象 34 交互。
谢谢!
我现在能想到的最好的方法是蛮力方法。只需检查一个对象与其他对象的中心的距离,然后手动检查距离是否小于 300 像素。
如果你想要这么快,我们或许应该在没有任何工具箱的情况下做到这一点。您可以使用 bsxfun
使用 vanilla MATLAB 智能地执行此操作。首先,为每个对象的 X
和 Y
坐标创建单独的数组:
points = reshape([objects.centre], 2, []);
X = points(1,:);
Y = points(2,:);
[objects.centre
] 访问结构中每个 centre
字段的单独坐标,并将它们解压缩到逗号分隔的列表中。我重塑这个数组,使其成为 2 行,其中第一行是 X
坐标,第二行是 Y
坐标。我提取出行并将它们放入单独的数组中。
接下来,为每个 X
和 Y
创建两个差异矩阵,其中行表示一个唯一坐标,列表示另一个唯一坐标。此矩阵内的值是第 i
行的点 i
与第 j
列的点 j
之间的差异:
Xdiff = bsxfun(@minus, X.', X);
Ydiff = bsxfun(@minus, Y.', Y);
bsxfun
代表Binary Singleton EXpansion 有趣动作。如果您熟悉 repmat
函数,它本质上是在幕后复制矩阵和向量,以便您操作的两个输入具有相同的大小。在这种情况下,我所做的是将 X
或 Y
指定为两个输入。一个是另一个的 transposed 版本。通过这样做 bsxfun
自动 广播 每个输入,以便输入在维度上匹配。具体来说,第一个输入是 X
的列向量,因此它会被重复并水平堆叠,次数与 X
中的值一样多。
同样,这是针对 Y
值完成的。执行此操作后,对两个输出执行按元素减法,并在 X
和 Y
的一个点和另一个点之间进行分量减法,其中该行为您提供第一个点,并且该专栏为您提供了第二点。作为玩具示例,假设我们有 X = [1 2 3]
。使用上述代码执行 bsxfun
调用会得到:
>> Xdiff = bsxfun(@minus, [1 2 3].', [1 2 3])
Xdiff =
## | 1 2 3
----------------------
1 | 0 -1 -2
2 | 1 0 -1
3 | 2 1 0
我在输出中放置了一些额外的字符,但这些仅用于说明并为您提供参考。通过从 ##
列中获取行值并从 ##
行中的列值中减去,可以得到所需的减法。例如,第一行第二列说明 1 - 2 = -1。第二行第三列说明 2 - 3 = -1。如果对 X
和 Y
点都执行此操作,则会得到一个点相对于对称矩阵中所有其他点的分量距离。
你会注意到这是一个对角线全为0的反对称矩阵...这是有道理的,因为一个点相对于自身的一维距离应该为0。左下角的三角形部分矩阵的符号是右边的相反符号……因为减法的顺序。如果你用点 2 减去点 1,做相反的减法会得到相反的符号。但是,我们假设行表示第一个对象,列表示第二个对象,因此您希望专注于 lower 一半。
现在,计算距离,并确保将三角形的上半部分或下半部分设置为 NaN
,因为在计算距离时,符号会被忽略。如果您不忽略这一点,我们会发现交互的重复对象,因此对象 3 和对象 1 的交互与对象 1 和对象 3 的交互不同。您显然不关心顺序,因此设置上或将三角形的一半降低到 NaN
以进行下一步。假设欧氏距离:
dists = sqrt(Xdiff.^2 + Ydiff.^2);
dists(tril(ones(numel(objects))==1)) = NaN;
第一行计算所有点对的欧氏距离,我们使用tril
提取由所有逻辑1
组成的矩阵的下三角部分。提取这个矩阵,我们用它来将矩阵的下半部分设置为NaN
。这允许我们跳过我们不感兴趣的条目。请注意,我还将对角线设置为 0,因为我们对一个对象与其自身的距离不感兴趣。
既然您终于来到了这里,请搜索那些小于 300 像素的对象:
[I,J] = find(dists < 300);
I
和 J
是 row/column 对,它们确定矩阵中哪些行和列的值 < 300,因此在我们的例子中,每对 I
数组中的 J
为您提供彼此靠近的对象位置。
要最终找出正确的目标代码,您可以这样做:
codes = [[objects(I).code].' [objects(J).code].'];
这使用 I
和 J
访问逗号分隔列表中相似对象的相应代码,并将它们并排放入 N x 2
矩阵中。因此,codes
的每一行都会为您提供满足距离要求的唯一对象对。
用于复制和粘贴:
points = reshape([objects.centre], 2, []);
X = points(1,:);
Y = points(2,:);
Xdiff = bsxfun(@minus, X.', X);
Ydiff = bsxfun(@minus, Y.', Y);
dists = sqrt(Xdiff.^2 + Ydiff.^2);
dists(tril(ones(numel(objects))==1)) = NaN;
[I,J] = find(dists < 300);
codes = [[objects(I).code].' [objects(J).code].'];
玩具示例
这里有一个例子,我们可以用它来验证我们所拥有的是否正确:
objects(1).centre = [1868 1236];
objects(2).centre = [2000 1000];
objects(3).centre = [1900 1300];
objects(4).centre = [3000 2000];
objects(1).code = 33;
objects(2).code = 34;
objects(3).code = 35;
objects(4).code = 99;
我用不同的质心和不同的代码初始化了 4 个对象。让我们看看 dists
数组在计算后给我们的是什么:
>> format long g
>> dists
dists =
NaN 270.407100498489 71.5541752799933 1365.69396278961
NaN NaN 316.227766016838 1414.2135623731
NaN NaN NaN 1303.84048104053
NaN NaN NaN NaN
我故意让最后一个点比其他三个点都远,以确保我们可以展示不靠近其他点的情况。
如您所见,点 (1,2) 和 (1,3) 都彼此靠近,这是我们完成其余代码后得到的结果。这对应于具有 (33,34) 和 (33,35) 配对的对象 33、34 和 35。代码为34和35的点我做了稍微小一点,但还是大于300像素的阈值,所以也不算:
>> codes
codes =
33 34
33 35
现在,如果您想以美化的格式显示它,也许可以使用 for
循环:
for vec = codes.'
fprintf('Object with code %d interacted with object with code %d\n', vec(1), vec(2));
end
这个 for
循环有点棘手。一个鲜为人知的事实是 for
loops can also accept matrices 和索引变量从左到右一次为您提供每个矩阵的一列。因此,我转置了 codes
数组,使每对唯一代码成为一列。我只是访问每列的第一个和第二个元素并将其打印出来。
我们得到:
Object with code 33 interacted with object with code 34
Object with code 33 interacted with object with code 35