Shaun
Shaun

Reputation: 63

How to measure the size of the room based off the color

Colored image Filtered image used to run the room segmentation code Original image

So this code is able to segment a variety of rooms it identifies into different colors as seen below. The question is, how do i obtain the area of the rooms that are colored (Like those blue rooms). Rooms are in 1m:150m ratio.

The first image is the output i need to measure, the second room is the image i used to run the code with, the third image is an original image for reference. Thanks in advance.

import numpy as np

def find_rooms(img, noise_reduction=10, corners_threshold=0.0000001,
               room_close=2, gap_in_wall_threshold=0.000001):

    # :param img: grey scale image of rooms, already eroded and doors removed etc.
    # :param noise_reduction: Amount of noise removed.
    # :param corners_threshold: Corners to retained, higher value = more of house removed.
    # :param room_close: Maximum line length to add to close off open doors.
    # :param gap_in_wall_threshold: Minimum number of pixels to identify component as room instead of hole in the wall.
    # :return: rooms: list of numpy arrays containing boolean masks for each detected room
    # colored_house: Give room a color.

    assert 0 <= corners_threshold <= 1
    # Remove noise left from door removal

    img[img < 128] = 0
    img[img > 128] = 255
    contours, _ = cv2.findContours(~img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    mask = np.zeros_like(img)
    for contour in contours:
        area = cv2.contourArea(contour)
        if area > noise_reduction:
            cv2.fillPoly(mask, [contour], 255)

    img = ~mask

    # Detect corners (you can play with the parameters here)
    #harris corner detection
    dst = cv2.cornerHarris(img, 4,3,0.000001)
    dst = cv2.dilate(dst,None)
    corners = dst > corners_threshold * dst.max()

    # Draw lines to close the rooms off by adding a line between corners on the same x or y coordinate
    # This gets some false positives.
    # Can try disallowing drawing through other existing lines, need to test.
    for y,row in enumerate(corners):
        x_same_y = np.argwhere(row)
        for x1, x2 in zip(x_same_y[:-1], x_same_y[1:]):

            if x2[0] - x1[0] < room_close:
                color = 0
                cv2.line(img, (x1, y), (x2, y), color, 1)

    for x,col in enumerate(corners.T):
        y_same_x = np.argwhere(col)
        for y1, y2 in zip(y_same_x[:-1], y_same_x[1:]):
            if y2[0] - y1[0] < room_close:
                color = 0
                cv2.line(img, (x, y1), (x, y2), color, 1)


    # Mark the outside of the house as black
    contours, _ = cv2.findContours(~img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contour_sizes = [(cv2.contourArea(contour), contour) for contour in contours]
    biggest_contour = max(contour_sizes, key=lambda x: x[0])[1]
    mask = np.zeros_like(mask)
    cv2.fillPoly(mask, [biggest_contour], 255)
    img[mask == 0] = 0

    # Find the connected components in the house
    ret, labels = cv2.connectedComponents(img)
    img = cv2.cvtColor(img,cv2.COLOR_GRAY2RGB)
    unique = np.unique(labels)
    rooms = []
    for label in unique:
        component = labels == label
        if img[component].sum() == 0 or np.count_nonzero(component) < gap_in_wall_threshold:
            color = 0
        else:
            rooms.append(component)
            color = np.random.randint(0, 255, size=3)
        img[component] = color

    return rooms, img

#Read gray image
img = cv2.imread('output16.png', 0)
rooms, colored_house = find_rooms(img.copy())
cv2.imshow('result', colored_house)
cv2.waitKey()
cv2.destroyAllWindows()

Upvotes: 1

Views: 313

Answers (2)

O Yahya
O Yahya

Reputation: 376

So the measurement will be based on pixels, and you will need to know the maximum and minimum range of the RGB value of the color you want to "measure". I ran this code on your image to find the percentage of the green colored area to the whole area of the house and I got the following result:

The number of filtered pixels is: 331213 Which counts for %5 of the house

import cv2
import numpy as np
import math

img = cv2.imread('22I7X.png')
#Defining wanted color range
filteredColorMin = np.array([36,0,0], np.uint8) #Min range
filteredColorMax = np.array([70, 255,255], np.uint8) #High range
#Find all the pixels in the wanted color range
dst = cv2.inRange(img, filteredColorMin, filteredColorMax)
#count non-zero values from filtered range
numFilteredColor = cv2.countNonZero(dst)
#Getting total number of pixels in image to get the percentage of the filtered pixels from the total pixels
numTotalPixels=img.shape[0] *img.shape[1]

print('The number of filtered pixels is: ' + str(numFilteredColor) + " Which counts for %" + str(math.ceil((numFilteredColor/numTotalPixels)*100)) + " of the house")
cv2.imshow("original image",img)
cv2.waitKey(0)

Upvotes: -1

bglbrt
bglbrt

Reputation: 2098

Ok so let's say that you read the segmented picture using OpenCV:

import cv2
import numpy as np

# reading the segmented picture in coloured mode
image = cv2.imread("path/to/segmented/coloured/picture.jpg", cv2.IMREAD_COLOR)

Now, suppose that you know the size in squared meters of the entire picture, so if for instance the picture reflects a total of 150m x 70m, you have a total size of 150x70 = 10500m². Let's declare this as a variable:

total_size = 10500

You also want to know the total number of pixels in the picture. If, for instance, your picture is 750*350 pixels, you have: 262500 pixels. You can just do that with:

total_number_of_pixels = image.shape[0]*image.shape[1]

Now, as I said in a comment, you also want to know the number of pixels for each unique colour in the segmented picture, which you can do using:

# count all occurrences of unique colours in your picture
unique, counts = np.unique(image.reshape(-1, image.shape[2]), axis=0, return_counts=True)

coloured_pixel_counts = sorted(zip(unique, counts), key=lambda x: x[1]))

Now, all you have left to do is just a cross-multiplication, which can be done with something like this:

rooms = []
for colour, pixel_count in coloured_pixel_counts:
     rooms.append((colour, (pixel_count/total_number_of_pixels)*total_size))

You should now have a list of all colours and the respective approximated size in squared meters of the rooms of this colour.

Now, please note that, however, you would probably have to subset this list to the colours that strike your interest, as some colours seem to not really be linked to a room in your segmented pictures...

Again, please ask if anything is unclear!

Upvotes: 3

Related Questions