Image processing: Floor plan - detecting rooms' borders (area) and room names' texts
EDIT: Updated the algorithm, new part below
Fun problem!
Ok, my first would be to find the room centers. This is relatively easy using a distance transform and geodesic erosion.
distTransform = DistanceTransform[ColorNegate@m];
ImageAdjust[distTransform]
The distance transform image contains for every pixel the distance to the closest wall. We're looking for the bright "peak" in the distance transform, i.e. the center of each room.
centerAreas = ImageDifference[
GeodesicDilation[Image[ImageData[distTransform] - 10],
distTransform], distTransform]
EDIT: The next part is new
With this, we can use a watershed transform to find the rooms. The watershed transform (intuitively speaking) finds "basins" in a 3d landscape. We'll invert the distance transform image to turn the "peaks" into "basins" and use the room centers as markers:
watershed =
DeleteSmallComponents[
DeleteBorderComponents[
Binarize[
Image[WatershedComponents[ColorNegate[distTransform],
centerAreas]]]], 1000]
This segments the rooms quite well. Unfortunately, the watershed transform ignores the walls - the components we found are too big. But they're close enough that this simple "grow the room rectangle until it hits the wall"-algorithm finds the actual room areas:
rooms = ComponentMeasurements[watershed, "BoundingBox"];
Clear[growRect]
growRect[{{x1_, y1_}, {x2_, y2_}}] :=
Module[{checkRectEmpty, growSingleDirection, growSingleStep, cx, cy,
left, top, right, bottom, sizeEstimate, size},
(
{cx, cy} = Round[{x1 + x2, y1 + y2}/2];
checkRectEmpty[{left_, top_, right_, bottom_}] :=
Max[ImageValue[
m, {cx - left ;; cx + right, cy - top ;; cy + bottom}]] == 0;
growSingleDirection[size_, grow_] :=
If[checkRectEmpty[size + grow], size + grow, size];
growSingleStep[size_] :=
Fold[growSingleDirection, size, IdentityMatrix[4]];
sizeEstimate =
Abs[Round[{x2 - x1, y2 - y1, x2 - x1, y2 - y1}/2 - 20]];
{left, top, right, bottom} =
FixedPoint[growSingleStep, sizeEstimate, 20];
Rectangle[{cx - left, cy - top}, {cx + right, cy + bottom}]
)]
Using this, all that's left is to display the results:
finalRectangles = growRect /@ rooms[[All, 2]];
feetAndInch[n_] := ToString[Round[n/12]] <> "'" <> ToString[Mod[n, 12]]
Show[m,
Graphics[
{
finalRectangles[[ ;; ]] /.
rect : Rectangle[{x1_, y1_}, {x2_, y2_}] :>
{
{EdgeForm[Red], Transparent, rect},
{Red,
Text[StringForm["`` x ``\n``", feetAndInch@(x2 - x1),
feetAndInch@(y2 - y1), (x2 - x1)*(y2 - y1)/(144.)], {x1 + x2,
y1 + y2}/2]}
}
}]]
or, using the original floor plan as background:
img = Import["http://i.stack.imgur.com/qDhl7.jpg"];
nsc = DeleteSmallComponents[Binarize[img, {0, .2}]];
iD = ImageDimensions[img];
mWS = 70; (*Max window span*)
sLT = 5;(*Straight line tolerance*)
i4 = Thinning@(i1 = Closing[MorphologicalTransform[nsc, {"Min", "Max"}], 3])
Now let's find the endpoints for each door and window by using HitMissTransform[]
.
We define some masks first:
k1 = ConstantArray[-1, 8];
v = {k1, {-1, -1, 0, 0, 1, 0, -1, -1}};
h = {k1[[;; 4]], {-1, -1, 1, 1}, {-1, -1, 0, 0}};
Now we find the candidate endpoints:
p = Position[ImageData@HitMissTransform[i4, #], 1] & /@ {v, Reverse@v, h, Reverse /@ h};
ListPlot[(Union @@ p) /. {a_, b_} -> {b, iD[[2]] - a}, Axes -> False, Frame -> True]
Now we select only paired endpoints:
lin = Union[
Select[Tuples[p[[1;;2]]], 0 < #[[1,1]] - #[[2,1]] < mWS && Abs[#[[1,2]] - #[[2,2]]] < sLT &],
Select[Tuples[p[[3;;4]]], 0 < #[[1,2]] - #[[2,2]] < mWS && Abs[#[[1,1]] - #[[2,1]]] < sLT &]] /.
{{a_, b_}, {c_, d_}} -> {{b, iD[[2]] - a}, {d, iD[[2]] - c}};
Graphics[{Point@(Union @@ lin), Line@lin},
PlotRange -> iD /. {x_, y_} -> {{1, x}, {1, y}}, Axes -> False, Frame -> True]
We finally "close" the doors and windows and use MorphologicalComponents[]
to identify the rooms:
ColorNegate@ImageSubtract[img, Colorize@MorphologicalComponents@ Binarize@
Erosion[Show[ColorNegate@i1,
Graphics[Line@lin, PlotRange -> iD /. {x_, y_} -> {{1, x}, {1, y}}]], 1]]
first of all, this is a very interesting problem. I cannot provide you with a complete answer, however, if you apply
m = MorphologicalTransform[nsc, {"Max", "Min", "Max"}]
you can use
(mc = MorphologicalComponents[ColorNegate@m]) // Colorize
to find a fair number of the rooms. The expressions
lines = ImageLines[m, Method -> "RANSAC", MaxFeatures -> 15];
Show[m, Graphics[{Thickness[0.01], Blue, Line /@ lines}]]
actually let you find the walls. ImageLines
has more options, so
lines = ImageLines[m, Method -> "Hough", "Segmented" -> True];
Show[m, Graphics[{Thickness[0.01], Blue, Line /@ lines}]]
can restrict the search to walls inside the house.