I try to use cv2.kmeans to segment the left auricle DICOM image as mask.
I use the following code to do the k-means binary clustering in OpenCV.
import numpy as np
import cv2
import os
from matplotlib import pyplot as plt
img = cv2.imread('1_LA.jpg')
img2 = img.reshape((-1, 3))
img2 = np.float32(img2)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret, label, center = cv2.kmeans(img2, 2, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
center = np.uint8(center)
res = center[label.flatten()]
res2 = res.reshape((img.shape))
cv2.imwrite('1_LA_kmeans.jpg', res2)
Then, I can get this segmentation result well.
But how can I extract one of the segmentations as mask?
I have referred other similar questions, and I try to use the code from here.
import numpy as np
import cv2
img = cv2.imread('1_LA.jpg')
Z = np.float32(img.reshape((-1,3)))
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 2
_,labels,centers = cv2.kmeans(Z, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
labels = labels.reshape((img.shape[:-1]))
reduced = np.uint8(centers)[labels]
result = [np.hstack([img, reduced])]
for i, c in enumerate(centers):
mask = cv2.inRange(labels, i, i)
mask = np.dstack([mask]*3) # Make it 3 channel
ex_img = cv2.bitwise_and(img, mask)
ex_reduced = cv2.bitwise_and(reduced, mask)
result.append(np.hstack([ex_img, ex_reduced]))
cv2.imwrite('kmeans/' + str(i) + '_1.jpg', np.vstack(result))
cv2.imwrite('1_LA_kmeans.jpg', np.vstack(result))
Then, I can get this output.
Becase I want to calculate the area of the left auricle, I need to extract the mask like below.
So, how can I extract one of the binary segmentation results?
Thanks for #fmw42's help.
After I refer this answer, and use the following code.
import cv2
import numpy as np
# read input and convert to range 0-1
image = cv2.imread('1.jpg')
h, w, c = image.shape
# reshape to 1D array
image_2d = image.reshape(h*w, c).astype(np.float32)
# set number of colors
numcolors = 2
numiters = 10
epsilon = 1
attempts = 10
# do kmeans processing
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, numiters, epsilon)
ret, labels, centers = cv2.kmeans(image_2d, numcolors, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)
# reconstitute 2D image of results
centers = np.uint8(centers)
newimage = centers[labels.flatten()]
newimage = newimage.reshape(image.shape)
#cv2.imwrite("1_test.jpg", newimage)
#cv2.imshow('new image', newimage)
#cv2.waitKey(0)
k = 0
for center in centers:
# select color and create mask
#print(center)
layer = newimage.copy()
mask = cv2.inRange(layer, center, center)
# apply mask to layer
layer[mask == 0] = [0,0,0]
#cv2.imshow('layer', layer)
#cv2.waitKey(0)
# save kmeans clustered image and layer
if(k == 0):
cv2.imwrite("1_test{0}.jpg".format(k), layer)
k = k + 1
I can extract the mask I want, appreciate.
Related
This is the follow-up question from here.
I use cv2.kmeans to segment the left auricle DICOM image as mask.
The following code is how I deal with the k-means binary clustering in OpenCV.
import cv2
import numpy as np
# read input and convert to range 0-1
image = cv2.imread('1.jpg')
h, w, c = image.shape
# reshape to 1D array
image_2d = image.reshape(h*w, c).astype(np.float32)
# set number of colors
numcolors = 2
numiters = 10
epsilon = 1
attempts = 10
# do kmeans processing
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, numiters, epsilon)
ret, labels, centers = cv2.kmeans(image_2d, numcolors, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)
# reconstitute 2D image of results
centers = np.uint8(centers)
newimage = centers[labels.flatten()]
newimage = newimage.reshape(image.shape)
#cv2.imwrite("1_test.jpg", newimage)
#cv2.imshow('new image', newimage)
#cv2.waitKey(0)
k = 0
for center in centers:
# select color and create mask
#print(center)
layer = newimage.copy()
mask = cv2.inRange(layer, center, center)
# apply mask to layer
layer[mask == 0] = [0,0,0]
#cv2.imshow('layer', layer)
#cv2.waitKey(0)
# save kmeans clustered image and layer
if(k == 0):
cv2.imwrite("1_test{0}.jpg".format(k), layer)
k = k + 1
But after I try to cv2.findContours, I cannot draw the correct edge of the left auricle.
Here comes my edge detection code.
import cv2
import numpy as np
import os
img = cv2.imread('1_test0.jpg')
#gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
implt = (img * 255).astype(np.uint8)
implt = np.asarray(implt)
implt = implt[:, :, -1]
im2 = cv2.resize(implt, (350, 350), interpolation=cv2.INTER_CUBIC)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(11, 11))
im2 = cv2.erode(im2,kernel)
ret,thresh = cv2.threshold(im2,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#ret,binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
#_,contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)[-2:]
draw_img3 = cv2.drawContours(img.copy(), contours, -1, (0, 0, 255), 3)
cv2.imwrite('1_test0_edge.jpg', draw_img3)
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('jelly.jpg') //reading the img
cv2.imshow(' img',img) //initial image
cv2.waitKey(0)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) // converting image to RGB
pixel_vals =img.reshape((-1,3)) //reshaping coloured 3d image to 2d image
pixel_vals = np.float32(pixel_vals)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,100,0.85) //setting criteria for kmeans
k= 5 //number of clusters
retval, labels, centers = cv2.kmeans(pixel_vals,k,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
centers = np.uint8((centers))
segmented_data = centers[labels.flatten()]
segmented_img = segmented_data.reshape((img.shape)) //final image
cv2.imshow('K-means segmented img',segmented_img) // showing the final image after k means segmentation
cv2.waitKey(0)
cv2.destroyAllWindows() //destroying all window pop-up of images
I want to get only like violet part or brown part according to intensities. I have tried looking but i am not able to find any function. as there is a chance that the blue colour or any other colour is present in different shade. Is there a way to also get particular shade of different colours masking other areas ?
Original Image
K means segmented image
I am not sure what you want, but if you want to save each color as its own image from kmeans in Python/OpenCV, then this should do that.
Input:
import cv2
import numpy as np
# read input and convert to range 0-1
image = cv2.imread('jellyfish.png')
h, w, c = image.shape
# reshape to 1D array
image_2d = image.reshape(h*w, c).astype(np.float32)
# set number of colors
numcolors = 5
numiters = 10
epsilon = 1
attempts = 10
# do kmeans processing
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, numiters, epsilon)
ret, labels, centers = cv2.kmeans(image_2d, numcolors, None, criteria, attempts, cv2.KMEANS_RANDOM_CENTERS)
# reconstitute 2D image of results
centers = np.uint8(centers)
newimage = centers[labels.flatten()]
newimage = newimage.reshape(image.shape)
cv2.imwrite("jellyfish_kmeans.png", newimage)
cv2.imshow('new image', newimage)
cv2.waitKey(0)
k = 0
for center in centers:
# select color and create mask
#print(center)
layer = newimage.copy()
mask = cv2.inRange(layer, center, center)
# apply mask to layer
layer[mask == 0] = [0,0,0]
cv2.imshow('layer', layer)
cv2.waitKey(0)
# save kmeans clustered image and layer
cv2.imwrite("jellyfish_layer{0}.png".format(k), layer)
k = k + 1
Kmeans Result:
Individual Colors:
I am not sure what you want to do because by your description you seem to want one thing and then by the title a completely different one. But I have segmented the parts you wanted.
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('jelly.png')
plt.imshow(img)
plt.show()
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
pixel_vals =img.reshape((-1,3))
pixel_vals = np.float32(pixel_vals)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,100,0.85)
k= 5
retval, labels, centers = cv2.kmeans(pixel_vals,k,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
clustered_img = labels.reshape((img.shape[0], img.shape[1]))
clusters_to_0 = [1,2,4]
for c in clusters_to_0:
clustered_img[clustered_img == c] = -1
clustered_img[clustered_img!=-1] = 1
clustered_img[clustered_img==-1] = 0
clustered_img
plt.imshow(clustered_img)
plt.show()
I suggest another approach by transforming the image to the HSV channel and then thresholding the Hue channel since it contains the information about the tonality of the colours:
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('jelly.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
(_, th) = cv2.threshold(h, 0, 1, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
plt.subplot(131)
plt.imshow(img)
plt.title('Original image')
plt.subplot(132)
plt.imshow(h)
plt.title('Hue channels of the HSV color-space')
plt.subplot(133)
plt.imshow(th)
plt.title('Thresholded image')
plt.show()
I tried to run below code for segmenting lung alone, but resulting image is as attached[. Please help.
import numpy as np
import cv2
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
X= cv2.imread('0070.png',0)
pixel_values = np.float32(X)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
k = 3
ret, labels, (centers) = cv2.kmeans(pixel_values, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
centers = np.uint8(centers)
labelss = labels.flatten()
print(labelss)
segmented_image = centers[labelss]
segmented_image = segmented_image.reshape((X.shape))
plt.imshow(segmented_image)
plt.show()
Here is a revised version that gets only the lungs and makes all the rest black using Python/OpenCV. After the kmeans processing, we only have 3 gray levels and the one we want is the middle one. So we threshold to extract that. Then we get the contour and areas and keep only the largest two contours by area.
Input:
from skimage import io
from sklearn import cluster
import matplotlib.pyplot as plt
import cv2
import numpy as np
# read input and convert to range 0-1
image = io.imread('lung.png',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 = 3
# 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')
# threshold to keep only middle gray values
lower = (100)
upper = (200)
thresh = cv2.inRange(newimage, lower, upper)
# get contours and corresponding areas and indices
cntrs_info = []
contours = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
index=0
for cntr in contours:
area = cv2.contourArea(cntr)
cntrs_info.append((index,area))
index = index + 1
# sort contours by area
def takeSecond(elem):
return elem[1]
cntrs_info.sort(key=takeSecond, reverse=True)
# draw two largest contours as white filled on black background
result = np.zeros_like(newimage)
index_first = cntrs_info[0][0]
cv2.drawContours(result,[contours[index_first]],0,(255),-1)
index_second = cntrs_info[1][0]
cv2.drawContours(result,[contours[index_second]],0,(255),-1)
# display result
io.imshow(newimage)
io.show()
io.imshow(thresh)
io.show()
io.imshow(result)
io.show()
# save kmeans clustered image and layer 3
io.imsave('lung_kmeans.gif', newimage)
io.imsave('lung_kmeans_thresh.gif', thresh)
io.imsave('lung_kmeans_lungs.gif', result)
Kmeans result:
Threshold result:
Two largest areas:
This works for me in Python/OpenCV using Sklearn/Skimage.
Input:
from skimage import io
from sklearn import cluster
import matplotlib.pyplot as plt
# read input and convert to range 0-1
image = io.imread('lung.png',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 = 3
# 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')
# display result
io.imshow(newimage)
io.show()
# display result as color coded
plt.imshow(newimage)
plt.show()
# save kmeans clustered image and layer 3
io.imsave('lung_kmeans.gif', newimage)
Result:
Result (color coded):
I need to create masks for 100.000 images, this code runs on cpu and creates ~500 masks a hour. Is there a way I can speed this up either by parallelising or running code on gpu? I'm okay with solutions that make me heavily rewrite code as long as it speeds up the process.
I tried compiling opencv library myself with cuda support, however I couldn't get most of cv2 methods I use here to run on gpu.
This is my code
Edit #1
Added import list and comments to code.
Added input and output images.
import cv2
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import glob
import sys
import os
import skimage.color
import skimage.filters
import skimage.io
import skimage.viewer
grayScale = cv2.imread(filename,cv2.IMREAD_REDUCED_GRAYSCALE_4)#read image as grayscale with size reduction
kernel = cv2.getStructuringElement(1,(17,17))
blackhat = cv2.morphologyEx(grayScale, cv2.MORPH_BLACKHAT, kernel)
ret,thresh2 = cv2.threshold(blackhat,10,255,cv2.THRESH_BINARY)
dst = cv2.inpaint(newimg,thresh2,1,cv2.INPAINT_TELEA) #4 lines above are used to remove hair from image
mask = np.zeros(dst.shape[:2],np.uint8)
h,w,c = dst.shape
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (int(0.1*w),int(0.1*h),int(0.8*w),int(0.8*h))
cv2.grabCut(dst,mask,rect,bgdModel,fgdModel,1,cv2.GC_INIT_WITH_RECT) #removes some background from image
#code for k means clustering starts here
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
dst = dst*mask2[:,:,np.newaxis]
vectorized = dst.reshape((-1,3))
vectorized = np.float32(vectorized)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) #11 lines above are used to remove some background from image
K = 4
attempts=1
ret,label,center=cv2.kmeans(vectorized,K,None,criteria,attempts,cv2.KMEANS_PP_CENTERS)
center = np.uint8(center)
labels = label.flatten()
res = center[label.flatten()]
result_image = res.reshape((dst.shape)) #k means clustering ends here
gray = cv2.cvtColor(result_image, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 10, 20, cv2.THRESH_BINARY)
result_image[thresh == 0] = 255
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
erosion = cv2.erode(result_image, kernel, iterations = 1)
blur = skimage.color.rgb2gray(erosion)
blur = skimage.filters.gaussian(blur, sigma=float(1))
histogram, bin_edges = np.histogram(blur, bins=256, range=(0, 1))
index = next((i for i, x in enumerate(histogram) if x), None)
mask = blur > bin_edges[index+1] #10 lines above are used to create mask
mask = abs(mask-255) #inverts mask
array = np.array(mask, dtype='uint8')
finimg = cv2.resize(array,None,fx=4.0,fy=4.0) #returns image to original size
plt.imsave("Masks/"+filename, finimg, cmap = plt.cm.gray) #saves result image
input image - skin mole image
output image - mask of skin mole
You might try using kmeans processing in Python/Opencv as a first step. Then get the inner contour and use that for your mask. Draw the inner contour as white filled on a black background. You may need to use morphology to clean the kmeans results first
Input:
Kmeans 2:
Kmeans 3:
Kmeans 4:
I need to get a matrix with the coordinates (x,y) of the contour of the following image with python.
I try it with opencv canny detector and find contours but I get a lot of contours and I don't know how to get the one I want.
import numpy as np
from matplotlib import pyplot as plt
import cv2
#from skimage import measure, feature, io
#from skimage import img_as_ubyte
x1 = 330
xf = 690
y1 = 0
yf = 400
img = cv2.imread('test.tif')
img = img[y1:yf, x1:xf]
edge = cv2.Canny(img, 100, 200)
image, contours, hierarchy = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
I just need an array with the (x,y) coordinates of the contour. I think it is in the contours output of cv2.findContours() but I don't find the contour that I want…
I also tried with the matplotlib.pyplot.contour function :
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('test.tif', 0) # read image
img = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)[1] # threshold image
img = cv2.medianBlur(img, 15) # remove noise
# skeletonize
size = np.size(img) # get number of pixels
skel = np.zeros(img.shape, np.uint8) # create an array of zeros with the same shape as the image and 256 gray levels
element = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) # create a structurant element (cross)
done = False
while(not done):
eroded = cv2.erode(img, element)
temp = cv2.dilate(eroded, element)
temp = cv2.subtract(img, temp)
skel = cv2.bitwise_or(skel, temp)
img = eroded.copy()
zeros = size - cv2.countNonZero(img)
if zeros == size:
done = True
cs = plt.contour(skel, 1)
p = cs.collections[0].get_paths()[0]
v = p.vertices
x = v[:, 0]
y = v[:, 1]
But I just have closed contours and not the open contour which is going from the left to the right of the image.
Thanks a lot for your answers.
You almost found the answer to your question. First of all, there is a difference between edge detection and contour detection. Fundamentally, edge detection results in what you call (improperly) "open contour" (i.e. edge) and contour detection results in what you call "closed contour" (i.e. contour).
The Canny edge detection is a popular edge detection algorithm. Since you want to detect the edge in the form of an array with the (x,y) coordinates going from the left to the right of the image, the Canny edge detection is a good idea.
The answer is edge which is not in the wanted format.
import numpy as np
import matplotlib.pyplot as plt
import cv2
img = cv2.imread('test.tif')
edge = cv2.Canny(img, 100, 200)
ans = []
for y in range(0, edge.shape[0]):
for x in range(0, edge.shape[1]):
if edge[y, x] != 0:
ans = ans + [[x, y]]
ans = np.array(ans)
print(ans.shape)
print(ans[0:10, :])
The array ans (shape equal to (n, 2)) stores the (x, y)-coordinate of the n pixels which compose the detected edge. This is the result you are looking for.
Here is an image where I have plotted in white these n pixels:
I hope this will help you.