Labeling image regions - python

I have an image containing coloured regions (some of them using the same colour) and I would like that each region have a different colour.
The objective is to colour/label each region using a different colours/labels.
Sample image:

You can achieve this by looping over the unique values in your image, creating a mask of the objects with that value, and performing bwlabel for each such mask. This will give you unique labels for each connected component in that mask, and you can collect the labels from all the masks by adding the number of labels already found previously:
img = imread('i5WLA.png');
index = zeros(size(img));
for iGray = unique(img(:)).' %'
mask = (img == iGray);
L = bwlabel(mask, 4);
index(mask) = L(mask)+max(index(:));
end
subplot(2,1,1);
imshow(img, []);
title('Original');
subplot(2,1,2);
imshow(index, []);
title('Each region labeled uniquely');
And here's the plot this makes:
You can now see that each connected object has its own unique gray value. You can then create a color image from this new indexed image using either ind2rgb or label2rgb and selecting a colormap to use (here I'm using hsv):
rgbImage = ind2rgb(index, hsv(max(index(:))));
imshow(rgbImage);
% Or...
rgbImage = label2rgb(index, #hsv);
imshow(rgbImage);

Unless there's already a function floating around that does what you want, you can always write it yourself.
If I had to do this, I'd consider something like a union-find algorithm to group all equal-color adjacent/connected pixels into sets, then assign labels to those sets.
A naive (less efficient but doesn't require union-find) implementation using pseudo-code:
# assume pixels are stored in numpy array. Use your imagination to adjust as required.
put each pixel into its own set.
for pixel in pixels:
neighbors = adjacent_same_color_pixels(pixel)
find the sets that contain pixel, and the sets that contain the neighbors
join all those sets together, delete the original sets
now there's one set for each connected same-color shape.
assign labels as desired.

Related

How to convert rgb to labels for image segmentation

I have around 4000 rgb label images which are masks for some other images. I can use this image label pair in the deep learning encoder-decoder structure (eg:UNet) architecture with the help of regression approach. But I would like to do segmentation approach. For that how can I convert these images?
Sample image:
(Above sample image should contain 3 classes. one oval shape part, the remaining red part, and the background white part. This can go upto 7 classes in some other image pairs)
There is supposed to be 7 classes including background for the entire dataset. But when I tried to find the unique values in an RGB label, there are more than 30 unique value pairs coming. Otherwise I would have select the unique rgb pair and do the processing. How to overcome this
Here's one potential way to handle this (in MATLAB, but similar in other situations)
The image you have shared is rather pixelated, and hence quite difficult to handle. If your dataset contains similarly pixelated images, I'd explore some kind of pre-processing to get rid of spurious edge discolorations, as they mess up the clustering. For the sake of demonstration here, I've created a test image with exactly three colors.
% Create a test image - one shared is very pixelated.
I = uint8(zeros(100, 100, 3));
I(10:20, 10:20, 1) = 255;
I(40:50, 40:50, 2) = 255;
If the number of colors here is unknown, but up to 7, here's a quick way to use imsegkmeans and it's 'C' output to find the number of unique centers.
% Specify max clusters
maxNumClusters = 7;
% Run clustering using the max value
[~, C] = imsegkmeans(I, maxNumClusters);
nUniqueClusters = size(unique(C, 'rows'), 1);
'nUniqueClusters' should now contain the 'true' number of clusters in the image. In a way, this is almost like finding the number of unique entries of pixel RGB triplets in the image itself - I think what's affecting your work is noise due to pixelation - which is a separate problem.
[L, C] = imsegkmeans(I, nUniqueClusters);
% Display the labeled image for further verification.
B = labeloverlay(I, L);
figure, imshow(B)
One way to attempt to fix the pixelation problem is to plot a histogram of your image pixels (for one of the three color planes), and then managing the low values somehow - possibly marking all of them with a distinct new color that you know doesn't exist in your dataset otherwise (0, 0, 0), for example - and marking it's label to be 'unknown'. This is slightly outside the scope of your original question - hence just a text description of it here.

Looping through pixel images to find the closest value that pixel is to arbitrary no of values

Problem Statement: after successfully getting the boundary box around the object in yolo, i wanted to separate the background from the object itself.
My Solution: i have an RGB-D camera that returns a depth map as well as the image (image is given to yolo obv) , using the depth map , i made a simple function to get the depths (rounded) and how many pixels have that same value
def GetAllDepthsSortedMeters(depth_image_ocv):
_depth = depth_image_ocv[np.isfinite(depth_image_ocv)]
_depth= -np.sort(-depth_image_ocv)[:int(len(_depth)/2)]
_depth= np.round(_depth,1)
unique, counts = np.unique(_depth, return_counts=True)
return dict(zip(counts, unique))
and plotting them, i noticed that there are dominant peaks and the rest lay around them, after some filtering i was able to successfully get those peaks each time.
#get the values of depths and their number of occurences
counts,values = GetKeysAndValues(_depths)
#find the peaks of depths in those values
peaks = find_peaks_cwt(counts, widths=np.ones(counts.shape)*2)-1
using those peaks, i was able to segment the required object from the background by checking what peaks is this value close to, and make a mask for each peak(and pixels around it).
def GetAcceptedMasks(h,w,depth_map,depths_of_accepted_peaks,accepted_masks):
prev=None
prev_index=None
for pos in product(range(h), range(w)):
pixel = depth_map.item(pos)
if ( (prev is not None) and (round(prev,1) == round(pixel,1)) ):
accepted_masks[prev_index][pos[0],pos[1]]= 255
else:
_temp_array = abs(depths_of_accepted_peaks-pixel)
_min = np.amin(_temp_array)
_ind = np.where( _temp_array == _min )[0][0]
accepted_masks[_ind][pos[0],pos[1]]= 255
prev_index = _ind
prev = pixel
return accepted_masks
after passing the image through YOLOv3 and applying the filtering and depth segmentation, it takes 0.8s which is far from optimal,
it's mostly result of above funcution, any help would be amazing. thank you
this is masks i get at the end
Mask1-Of-Closest-Depth
Mask2-Of-2nd-Closest-Depth
Mask3-Of-3rd-Closest-Depth
Edit:
Example of distance:
[0.60000002 1.29999995 1.89999998]
Example of DepthMap when show with imshow:
Example of Depth Map
Here's a way to do it.
Make an array of floats the same height and width as your image, and with the final dimension equal to the number of unique depths you want to identify
At each pixel location, calculate the distance to each of the three desired depths and store in the final dimension
Use np.argmin(..., axis=2) to select the nearest depth of the three
I am not at a computer to test, and your image is not your actual image but rather a picture of it with window decorations and title bar and different values, but something like this:
import cv2
# Load the image as greyscale float - so we can store positive and negative distances
im = cv2.imread('depth.png', cv2.IMREAD_GRAYSCALE).astype(np.float)
# Make list of the desired depths
depths = [255, 181, 125]
# Make array with distance to each depth
d2each = np.zeros(((im.shape[0],im.shape[1],len(depths)), dtype=np.float)
for i in range(len(depths)):
d2each[...,i] = np.abs(im - depths[i])
# Now let Numpy choose nearest of three distances
mask = np.argmin(d2each, axis=2)
Another way, is to range test the distances. Load the image as above:
# Make mask of pixels matching first distance
d0 = np.logical_and(im>100, im<150)
# Make mask of pixels matching second distance
d1 = np.logical_and(im>180, im<210)
# Make mask of pixels matching third distance
d2 = im >= 210
Those masks will be logical (i.e. True/False), but if you want to make them black and white, just multiply them by 255 and cast with mask0 = d0.astype(np.uint8)
Another approach could be to use K-means clustering.

How to access individual labels in OpenCV connected component labelling

I am trying to access individual labels of objects with OpenCV's connectedComponentsWithStats in Python. However, when I run the connectedComponentsWithStats function, a labelled array is returned that has each object with different pixel values. How do I efficiently access each labelled object as a separate array? I am using very large images here with about 12000 x 10000 pixel dimensions.
I have an image here that has been labelled with cv.connectedComponentsWithStats:
The colormap used starts with purple(1) and ends with yellow (last label). How do I reference each labelled object independently as a separate array?
source = <some_image>
labels = <connected components result>
for label in np.unique(labels):
m = (labels == label) # boolean array/mask of pixels with this label
obj = source[m] # orignal pixel values for the labeled object
This will give back a flat result, it's unclear from your question whether this is acceptable

ndimage.label: Displaying patches of a certain size

Context: I utilize an SVM to analyze an image and find pixels of interest. After filtering out pixels of low interest (full code below), the resulting binary mask is stored to be displayed later. At the same time, I use ndimage.label to go through the binary mask and produce a list of found patches as well as keep track of how many patches were found (number of features)
Problem/Question: The images I am working with are super high res, meaning that there are "noise" groups of pixels (smaller than 100) that are counted by ndimage.label. This means an image that might have only 10 patches is counting 1000+ because of all these noise groups and individual pixels. Given the original binary mask and a labeled array, is there a way to modify the binary mask such that only patches of a certain size are included?
#SVM analyzes image
scoreMap = self.svmMachine.predict(inData)
scoreMap = scoreMap.reshape(origDims)
#An array with a score for each pixel is produced
self.allPixelScoreMaps[self.imageKeys[i]]['PxlScores'] = scoreMap
#print(scoreMap)
topPrct = np.percentile(scoreMap, 95)
#A binary mask is then created from the pixels of high interest
binaryMap = (scoreMap > topPrct).astype(np.int)
#The mask is then stored to be displayed later
self.allPixelScoreMaps[self.imageKeys[i]]['BinScores'] = binaryMap
labeled_array, num_features = ndimage.label(binaryMap, structure = self.labelCal)
self.allPixelScoreMaps[self.imageKeys[i]]['LabelMap'] = labeled_array
self.allPixelScoreMaps[self.imageKeys[i]]['NumFeatures'] = num_features
I found the exact answer I was looking for! It can be found in this tutorial: http://www.scipy-lectures.org/intro/summary-exercises/answers_image_processing.html

Python Pillow: how to overlay one binary image on top of another to produce a composite?

I am doing some image processing, and I need to check if a binary image is identical to another.
Processing speed isn't an issue, and the simple thing I thought to do was count the white pixels remaining after adding the inverse of image A to image B (these images are very nearly identical, but not quite--some sort of distance metric is the goal).
Note: take the logarithm to linearize the distance
However, in order to create the composite image, I need to include a "mask" that is the same size as the two images.
I am having trouble finding an example of creating the mask online and using it for the Image.composite function.
Here is my code:
compA = ImageOps.invert(imgA)
imgAB = Image.composite(compA,imgB,??? mask)
Right now, I have created a mask of all zeros--however, the composite image does not appear correctly (both A and B are exactly the same images; a mask of all zeros--or all ones for that matter--does not work).
mask = Image.fromarray(np.zeros(imgA.size,dtype=int),mode='L')
imgAB = Image.composite(compA,imgB,mask)
How do I just add these two binary images on top of eachother?
Clearly you're using numpy, so why not just work with numpy arrays and explicitly do whatever arithmetic you want to do in that domain—such as subtracting one image from the other:
arrayA = numpy.asarray( imgA, dtype=int )
arrayB = numpy.asarray( imgB, dtype=int )
arrayDelta = arrayA - arrayB
print( (arrayDelta !=0 ).sum() ) # print the number of non-identical pixels (why count them by hand?)
# NB: this number may be inflated by a factor of 3 if there are 3 identical channels R, G, B
imgDelta = Image.fromarray((numpy.sign(arrayDelta)*127+127).astype('uint8')) # display this image if you want to visualize where the differences are
You could do this even more simply, e.g.
print((numpy.asarray(imgA) != numpy.asarray(imgB)).sum())
but I thought casting to a signed integer type first and then subtracting would allow you to visualize more information (A white and B black -> white pixel in delta; A black and B white -> black pixel in delta)

Categories