找到连接的组件及其边界框是很容易的部分。更困难的部分是将边界框合并为岛屿。
边界框
首先是简单的部分。
function bBoxes = getIslandBoxes(lMap)
% find bounding box of each candidate island
% lMap is a logical matrix containing zero or more connected components
bw = bwlabel(lMap); % label connected components in logical matrix
bBoxes = struct2cell(regionprops(bw, 'BoundingBox')); % get bounding boxes
bBoxes = cellfun(@round, bBoxes, 'UniformOutput', false); % round values
end
这些值是四舍五入的,因为regionprops 返回的边界框位于在其各自组件之外的网格线上,而不是单元格中心,我们需要整数值用作矩阵的下标.例如,一个看起来像这样的组件:
0 0 0
0 1 0
0 0 0
会有一个边界框
[ 1.5000 1.5000 1.0000 1.0000 ]
我们四舍五入
[ 2 2 1 1]
合并
现在是困难的部分。一、合并条件:
- 如果
b2 和b1 的岛(包括边界层)有非零交点,我们将边界框b2 合并到边界框b1 中。
当一个组件全部或部分位于另一个组件的边界框内时,此条件确保边界框被合并,但当一个边界框位于另一个边界框的零边界内时,它也会捕获边缘情况。一旦所有的边界框都合并了,就保证它们有一个全为零的边界(或与矩阵的边缘接壤),否则其边界中的非零值将被合并。
由于合并涉及删除合并的边界框,因此循环是向后完成的,因此我们最终不会索引不存在的数组元素。
不幸的是,通过数组比较每个元素与所有其他元素是不足以捕捉所有情况的。为了表明所有可能的边界框都已合并到岛中,我们使用了一个名为 anyMerged 的标志并循环,直到我们完成一个完整的迭代而不合并任何内容。
function mBoxes = mergeBoxes(bBoxes)
% find bounding boxes that intersect, and merge them
mBoxes = bBoxes;
% merge bounding boxes that overlap
anyMerged = true; % flag to show when we've finished
while (anyMerged)
anyMerged = false; % no boxes merged on this iteration so far...
for box1 = numel(mBoxes):-1:2
for box2 = box1-1:-1:1
% if intersection between bounding boxes is > 0, merge
% the size of box1 is increased b y 1 on all sides...
% this is so that components that lie within the borders
% of another component, but not inside the bounding box,
% are merged
if (rectint(mBoxes{box1} + [-1 -1 2 2], mBoxes{box2}) > 0)
coords1 = rect2corners(mBoxes{box1});
coords2 = rect2corners(mBoxes{box2});
minX = min(coords1(1), coords2(1));
minY = min(coords1(2), coords2(2));
maxX = max(coords1(3), coords2(3));
maxY = max(coords1(4), coords2(4));
mBoxes{box2} = [minX, minY, maxX-minX+1, maxY-minY+1]; % merge
mBoxes(box1) = []; % delete redundant bounding box
anyMerged = true; % bounding boxes merged: loop again
break;
end
end
end
end
end
合并函数使用一个小型实用函数,将[x y width height] 格式的矩形转换为左上角、右下角[x1 y1 x2 y2] 的下标向量。 (这实际上在另一个函数中用于检查岛屿是否有零边界,但如上所述,此检查是不必要的。)
function corners = rect2corners(rect)
% change from rect = x, y, width, height
% to corners = x1, y1, x2, y2
corners = [rect(1), ...
rect(2), ...
rect(1) + rect(3) - 1, ...
rect(2) + rect(4) - 1];
end
输出格式和驱动函数
mergeBoxes 的返回值是矩形对象的元胞数组。如果您觉得这种格式有用,您可以在这里停下来,但很容易获得请求的格式,其中包含每个岛的行和列范围:
function rRanges = rect2range(bBoxes, mSize)
% convert rect = x, y, width, height to
% range = y:y+height-1; x:x+width-1
% and expand range by 1 in all 4 directions to include zero border,
% making sure to stay within borders of original matrix
rangeFun = @(rect) {max(rect(2)-1,1):min(rect(2)+rect(4),mSize(1));...
max(rect(1)-1,1):min(rect(1)+rect(3),mSize(2))};
rRanges = cellfun(rangeFun, bBoxes, 'UniformOutput', false);
end
剩下的就是将所有其他函数联系在一起的主要函数,我们就完成了。
function theIslands = getIslandRects(m)
% get rectangle around each component in map
lMap = logical(m);
% get the bounding boxes of candidate islands
bBoxes = getIslandBoxes(lMap);
% merge bounding boxes that overlap
bBoxes = mergeBoxes(bBoxes);
% convert bounding boxes to row/column ranges
theIslands = rect2range(bBoxes, size(lMap));
end
这是使用问题中给出的示例矩阵的运行:
M =
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1
0 0 0 1 1 1 0 1 1 0 0 0 1 1 1 1 0
0 0 0 0 0 0 1 0 1 0 0 0 0 1 1 1 1
0 0 0 1 0 1 0 1 1 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 1 0 1 1 1 0 0 0 0 0 0 0
0 0 1 0 1 1 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
>> getIslandRects(M)
ans =
{
[1,1] =
{
[1,1] =
9 10 11 12
[2,1] =
2 3 4 5 6 7 8 9 10 11
}
[1,2] =
{
[1,1] =
2 3 4 5 6 7
[2,1] =
3 4 5 6 7 8 9 10
}
[1,3] =
{
[1,1] =
2 3 4 5 6 7
[2,1] =
12 13 14 15 16 17
}
}