I am using k means clustering on an image. I am finding 2 or 3 regions via k-means. Now I want to create a mask for each region so that I can separate them in my original image.
Following is the code I am using currently.
import numpy as np
import matplotlib.pyplot as plt
import cv2
%matplotlib inline
# Read in the image
image = cv2.imread('/content/istockphoto-1201224719-612x612.jpg')
# Change color to RGB (from BGR)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
pixel_vals = image.reshape((-1,3))
# Convert to float type
pixel_vals = np.float32(pixel_vals)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.85)
# then perform k-means clustering wit h number of clusters defined as 3
#also random centres are initially choosed for k-means clustering
k = 2
retval, labels, centers = cv2.kmeans(pixel_vals, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# convert data into 8-bit values
centers = np.uint8(centers)
segmented_data = centers[labels.flatten()]
# reshape data into the original image dimensions
segmented_image = segmented_data.reshape((image.shape))
plt.imshow(segmented_image)
Following is the image
Following is the output of the above image using k-means clustering (k=2)
How can I create a mask for that yellow part to segment it from original?
Related
I use opencv for an image segmentation in 5 dimensional feature space {r,g,b,x,y} to get better result. The problem is after segmentation is done I can't reconstruct segmented clusters back into image. I don't understand how to do it. What I did is just deleted last 2 columns in segmentedData array to match original image shape. But this doesn't work. The opencv example shows how to do this in just 3-dimensional feature space {r,g,b}. Do you have any ideas?
Thanks.
Below is the code.
import numpy as np
import matplotlib.pyplot as plt
import cv2
img = cv2.imread("../images/segmentation/peppers_BlueHills.png")
x = np.arange(0, img.shape[0])
x = np.tile(x, img.shape[1]).reshape((-1,1))
x = np.float32(x)
y = np.arange(0, img.shape[1])
y = np.repeat(y, img.shape[0]).reshape((-1,1))
y = np.float32(y)
# Reshaping the image into a 2D array of pixels and 3 color values (RGB)
pixelVals = img.reshape((-1,3))
# Convert to float type only for supporting cv2.kmean
pixelVals = np.float32(pixelVals)
features = np.hstack((pixelVals,x,y))
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.85) #criteria 0.85
k = 16 # Number of cluster
ret, labels, centers = cv2.kmeans(features, k, None, criteria, 10, cv2.KMEANS_PP_CENTERS)
centers = np.uint8(centers) # convert data into 8-bit values
segmentedData = centers[labels.flatten()] # Mapping labels to center points( RGB Value)
#segmentedImg = segmentedData.reshape((img.shape)) # reshape data into the original image dimensions
segmentedImg = segmentedData.reshape((img.shape[0],img.shape[1],img.shape[2]+2))
segmentedImg = np.delete(segmentedImg, [3,4], axis=2)
cv2.imshow('segmentedImg', segmentedImg)
cv2.waitKey(0)
I have greyscale images with features of interest displayed as grey and white, and background as black.
I am trying to draw polygons around the features of interest.
My problem is that polygons are drawn e.g. around the edge of the images as well (input image). In the code below I have tried to filter out these "false positive" features of interest using gaussian blur and morphological operations (see code below),
import cv2
import matplotlib.pyplot as plt
import numpy as np
from imantics import Polygons, Mask
import imantics as imcs
import skimage
from shapely.geometry import Polygon as Pollygon
import matplotlib.image as mpimg
import PIL
mask = cv2.imread('mask.jpg',64)
print(mask.max())
print(mask.min())
# Apply gaussian blur filter
mask = cv2.GaussianBlur(mask,(9,9),0)
mask = cv2.GaussianBlur(mask,(9,9),0)
mask = cv2.GaussianBlur(mask,(9,9),0)
mask = cv2.GaussianBlur(mask,(9,9),0)
mask = cv2.GaussianBlur(mask,(9,9),0)
ellipseFootprint = skimage.morphology.footprints.ellipse(1, 1)
squareFootprint = skimage.morphology.footprints.square(8)
maskMorph = mask
for i in range(10):
maskMorph = skimage.morphology.erosion(maskMorph, footprint=ellipseFootprint, out=None)
print(i)
for k in range(2):
maskMorph = skimage.morphology.dilation(maskMorph, footprint=None, out=None)
print(k)
polygons = Mask(maskMorph).polygons()
print(len(polygons.segmentation))
print(type(polygons))
print(polygons.segmentation)
newPoly = polygons.draw(mask, color=[255, 255, 0],
thickness=3)
cv2.imshow("title", newPoly)
cv2.waitKey()
Indeed, I have tried to "filter" out smaller features/polygons and "false positive" features of interest in images using gaussian blur filter and morphological operations, but I am struggling with getting rid of all (see output image).
My thinking is therefore to add a minimum (size) threshold for the features/polygons in the image to be kept.
I have started on the following, but am not sure how to progress.
lengthPolySeg = len(polygons.segmentation)
for l in range(lengthPolySeg-1):
if len(polygons.segmentation[l]) < 50:
Any advise would be most appreciated.
I am using python and openCv for a brain segmentation project. I have segmented the brain MRI image using K means segmentation. I want to get each segment resulted through k means segmentation in seperate images. please help me in this.
#k_means segmentation
epsilon = 0.01
number_of_iterations = 50
number_of_clusters = 4
print(criteria, 'Criteria K_means parameters')
#plt.imshow(criteria)
#k means segmentation
_, labels, centers =cv2.kmeans(kmeans_input, number_of_clusters, None,
flags)
print(labels.shape, 'k-means segmentation')
#plt.imshow(labels)
#Adopting the labels
labels = labels.flatten('F')
for x in range (number_of_clusters): labels[labels == x] = centers [x]
print(labels.shape, 'adopting the tables value')
#plt.imshow(labels)
I would do it using sklearn kmeans segmentation as follows. I show how to create the segmented image and then select one color to present. I create a mask from thresholding the one color and then apply the mask to blacken out the other colors in the segmented image. You can write a loop over each color to get them all. It is also possible to use the mask to make the non-color be transparent rather than black. But I do not show that here. Or you can just save the binary masks.
Input:
#!/bin/python3.7
from skimage import io
from sklearn import cluster
import sys
import cv2
# read input and convert to range 0-1
image = io.imread('barn.jpg')/255.0
h, w, c = image.shape
# reshape to 1D array
image_2d = image.reshape(h*w, c)
# set number of colors
numcolors = 6
# do kmeans processing
kmeans_cluster = cluster.KMeans(n_clusters=int(numcolors))
kmeans_cluster.fit(image_2d)
cluster_centers = kmeans_cluster.cluster_centers_
cluster_labels = kmeans_cluster.labels_
# need to scale result back to range 0-255
newimage = cluster_centers[cluster_labels].reshape(h, w, c)*255.0
newimage = newimage.astype('uint8')
io.imshow(newimage)
io.show()
# select cluster 3 (in range 1 to numcolors) and create mask
lower = cluster_centers[3]*255
upper = cluster_centers[3]*255
lower = lower.astype('uint8')
upper = upper.astype('uint8')
mask = cv2.inRange(newimage, lower, upper)
# apply mask to get layer 3
layer3 = newimage.copy()
layer3[mask == 0] = [0,0,0]
io.imshow(layer3)
io.show()
# save kmeans clustered image and layer 3
io.imsave('barn_kmeans.gif', newimage)
io.imsave('barn_kmeans_layer3.gif', layer3)
Clustered Image:
Result for color 3:
ADDITION:
For a grayscale image, the following works for me.
#!/bin/python3.7
from skimage import io
from sklearn import cluster
import sys
import cv2
# read input and convert to range 0-1
image = io.imread('barn_gray.jpg',as_gray=True)/255.0
h, w = image.shape
# reshape to 1D array
image_2d = image.reshape(h*w,1)
# set number of colors
numcolors = 6
# do kmeans processing
kmeans_cluster = cluster.KMeans(n_clusters=int(numcolors))
kmeans_cluster.fit(image_2d)
cluster_centers = kmeans_cluster.cluster_centers_
cluster_labels = kmeans_cluster.labels_
# need to scale result back to range 0-255
newimage = cluster_centers[cluster_labels].reshape(h, w)*255.0
newimage = newimage.astype('uint8')
io.imshow(newimage)
io.show()
# select cluster 3 (in range 1 to numcolors) and create mask
# note the cluster numbers and corresponding colors are not constant from run to run
lower = cluster_centers[3]*255
upper = cluster_centers[3]*255
lower = lower.astype('uint8')
upper = upper.astype('uint8')
print(lower)
print(upper)
mask = cv2.inRange(newimage, lower, upper)
# apply mask to get layer 3
layer3 = newimage.copy()
layer3[mask == 0] = [0]
io.imshow(layer3)
io.show()
# save kmeans clustered image and layer 3
io.imsave('barn_gray_kmeans.gif', newimage)
io.imsave('barn_gray_kmeans_layer3.gif', layer3)
I am trying to perform image segmentation using scikit mean shift algorithm. I use opencv to display the segmented image.
My problem is the following: I use the code as given in different examples, and when I display the image after segmentation, I get a black image. I was wondering if someone could see what my mistake is...
Thanks a lot for the help !
Here is my code:
import numpy as np
import cv2
from sklearn.cluster import MeanShift, estimate_bandwidth
#Loading original image
originImg = cv2.imread('Swimming_Pool.jpg')
# Shape of original image
originShape = originImg.shape
# Converting image into array of dimension [nb of pixels in originImage, 3]
# based on r g b intensities
flatImg=np.reshape(originImg, [-1, 3])
# Estimate bandwidth for meanshift algorithm
bandwidth = estimate_bandwidth(flatImg, quantile=0.1, n_samples=100)
ms = MeanShift(bandwidth = bandwidth, bin_seeding=True)
# Performing meanshift on flatImg
ms.fit(flatImg)
# (r,g,b) vectors corresponding to the different clusters after meanshift
labels=ms.labels_
# Remaining colors after meanshift
cluster_centers = ms.cluster_centers_
# Finding and diplaying the number of clusters
labels_unique = np.unique(labels)
n_clusters_ = len(labels_unique)
print("number of estimated clusters : %d" % n_clusters_)
# Displaying segmented image
segmentedImg = np.reshape(labels, originShape[:2])
cv2.imshow('Image',segmentedImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
For Displaying the image, the correct code would be
segmentedImg = cluster_centers[np.reshape(labels, originShape[:2])]
cv2.imshow('Image',segmentedImg.astype(np.uint8)
cv2.waitKey(0)
cv2.destroyAllWindows()
I tried your method of segmentation on a random sample photo, and the segmentation looked bad, probably because since your mean-shift is working only on the color space, it looses the locality info. The python package skimage comes with a segmentation module, and it offers a few super-pixel segmentation methods. The quickshift method is based on the 'mode seeking' mechanism that meanshift is based on. None of these methods would segment out an entire object in an image. They provide extremely localized segmentation.
You can convert to some other color-space (e.g., Lab colorspace, using the following code) and segment on the colors (discarding intensity).
from skimage.color import rgb2lab
image = rgb2lab(image)
Then use your above code to tune the parameters (quantile and n_samples) of the function estimate_bandwidth() and finally use matplotlib's subplot to plot the segmented image as shown below:
plt.figure()
plt.subplot(121), plt.imshow(image), plt.axis('off'), plt.title('original image', size=20)
plt.subplot(122), plt.imshow(np.reshape(labels, image.shape[:2])), plt.axis('off'), plt.title('segmented image with Meanshift', size=20)
plt.show()
to get the following output with the pepper image.
The issue is that you are trying to display labels, you should use label map to convert image into superpixels.
import numpy as np
import cv2
from sklearn.cluster import MeanShift, estimate_bandwidth
#Loading original image
originImg = cv2.imread('Swimming_Pool.jpg')
# Shape of original image
originShape = originImg.shape
# Converting image into array of dimension [nb of pixels in originImage, 3]
# based on r g b intensities
flatImg=np.reshape(originImg, [-1, 3])
# Estimate bandwidth for meanshift algorithm
bandwidth = estimate_bandwidth(flatImg, quantile=0.1, n_samples=100)
ms = MeanShift(bandwidth = bandwidth, bin_seeding=True)
# Performing meanshift on flatImg
ms.fit(flatImg)
# (r,g,b) vectors corresponding to the different clusters after meanshift
labels=ms.labels_
# Remaining colors after meanshift
cluster_centers = ms.cluster_centers_
# Finding and diplaying the number of clusters
labels_unique = np.unique(labels)
n_clusters_ = len(labels_unique)
print("number of estimated clusters : %d" % n_clusters_)
# Displaying segmented image
segmentedImg = np.reshape(labels, originShape[:2])
superpixels=label2rgb(segmentedImg,originImg,kind="'avg'")
cv2.imshow('Image',superpixels)
cv2.waitKey(0)
cv2.destroyAllWindows()
I'm trying to find the 3 dominant colours of an several images using K-means clustering. The problem I'm facing is that K-means also clusters the background of the image. I am using Python 2.7 and OpenCV 3
All images have the same grey background of the following RGB colour: 150,150,150. To avoid that K-means also clusters the background color, I created a masked array which masks all '150' pixel values from the original image array, theoretically leaving only the non-background pixels in the array for K-Means to work with. However, when I run my script, it still returns the grey as one of the dominant colours.
My question: is a masked array the way to go (and did I do something wrong) or are there better alternatives to somehow exclude pixels from K-means clustering?
Please find my code below:
from sklearn.cluster import KMeans
from sklearn import metrics
import cv2
import numpy as np
def centroid_histogram(clt):
numLabels = np.arange(0, len(np.unique(clt.labels_)) + 1)
(hist, _) = np.histogram(clt.labels_, bins=numLabels)
hist = hist.astype("float")
hist /= hist.sum()
return hist
image = cv2.imread("test1.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
h, w, _ = image.shape
w_new = int(100 * w / max(w, h))
h_new = int(100 * h / max(w, h))
image = cv2.resize(image, (w_new, h_new))
image_array = image.reshape((image.shape[0] * image.shape[1], 3))
image_array = np.ma.masked_values(image_array,150)
clt = KMeans(n_clusters=3)
clt.fit(image_array)
hist = centroid_histogram(clt)
zipped = zip(hist, clt.cluster_centers_)
zipped.sort(reverse=True, key=lambda x: x[0])
hist, clt.cluster_centers = zip(*zipped)
print(clt.cluster_centers_)
If you want to extract the values of pixels other than your background, you can use numpy indexation :
img2=image_array[image_array!=[150,150,150]]
img2=img2.reshape((len(img2)/3,3))
This will yield the list of pixels which are not [150,150,150].
However, it does not preserve the structure of the image, just gives you the list of pixels values. I can't really remember, but maybe for K-means you need to give the whole image, i.e. you also need to feed it the position of the pixels ? But in that case, no masking will ever help because masking is just replacing values of certain pixels by another, not getting rid of pixels all together.