Based on a solution that I read at How to define the markers for Watershed in OpenCV?, I am trying apply watershed to grayscale data (not very visible but not all black), extracted from netcdf (precipitation data).
Here is a black and white version of the data (threshold at 0) so that you can see more easily, and the markers I want to use to define the different basins (basically just another threshold where precipitation is more intense).
The code I'm running is as follows:
import os,sys,string
from netCDF4 import Dataset as nc
import cv2
import numpy as np
import matplotlib.pyplot as mpl
import scipy.ndimage as ndimage
import scipy.spatial as spatial
from skimage import filter
from skimage.morphology import watershed
from scipy import ndimage
filename=["Cmorph-1999_01_03.nc"]
nc_data=nc(filename[0])
data=nc_data.variables["CMORPH"][23,0:250,250:750]
new_data=np.flipud(data)
ma_data=np.ma.masked_where(new_data<=0,new_data)
ma_conv=np.ma.masked_where(new_data<=2,new_data)
## Borders
tmp_data=ma_data.filled(0)
tmp_data[np.where(tmp_data!=0)]=255
bw_data=tmp_data.astype(np.uint8)
border = cv2.dilate(bw_data, None, iterations=5)
border = border - cv2.erode(border, None)
## Markers
tmp_conv=ma_conv.filled(0)
tmp_conv[np.where(tmp_conv!=0)]=255
bw_conv=tmp_conv.astype(np.uint8)
lbl, ncc = ndimage.label(bw_conv)
lbl = lbl * (255/ncc)
lbl[border == 255] = 255
lbl = lbl.astype(np.int32)
## Apply watershed
cv2.watershed(ma_data, lbl)
lbl[lbl == -1] = 0
lbl = lbl.astype(np.uint8)
result = 255 - lbl
I have the following error for the watershed in opencv-2.4.11/modules/imgproc/src/segmentation.cpp:
error: (-210) Only 8-bit, 3-channel input images are supported in function cvWatershed
For what I saw on the internet, this is due to the fact that the grayscale data is a 2D image and watershed needs a 3D image (from RGB). Indeed, I tried the script with a jpg image and I worked perfectly.
This problem is mentionned here but the answer given was finally rejected. And I can't find any more recent link answering the question.
To try to solve this, I created a 3D array from the 2D new_data:
new_data = new_data[..., np.newaxis]
test=np.append(new_data, new_data, axis=2)
test=np.append(new_data, test, axis=2)
But, as expected, it didn't solve the problem (same error message).
I also tried to save the plot from matplotlib to get RGB data:
fig = mpl.figure()
fig.add_subplot(111)
fig.tight_layout(pad=0)
mpl.contourf(ma_data,levels=np.arange(0,255.1,0.1))
fig.canvas.draw()
test_data = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
test_data = test_data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
But the size of the test_data created is different from ma_data (+ I can't get rid of the labels).
So, I am stuck here. Ideally, I want to apply the watershed on the 2D grayscale image directly and/or limit the number of operations as much as possible.
As yapws87 mentioned, there was indeed a problem with the format I was presenting to the watershed function.
Doing try_data=ma_data.astype(np.uint8) removed the error message.
Here is a minimal example that works now:
import os,sys
from netCDF4 import Dataset as nc
import cv2
import numpy as np
import scipy.ndimage as ndimage
from skimage.morphology import watershed
from scipy import ndimage
basename="/home/dcop696/Data/CMORPH/precip/CMORPH_V1.0/CRT/8km-30min/1999/"
filename=["Cmorph-1999_01_03.nc"]
fileslm=["/home/dcop696/Data/LSM/Cmorph_slm_8km.nc"]
nc_data=nc(basename+filename[0])
data=nc_data.variables["CMORPH"][23,0:250,250:750]
new_data=np.flipud(data)
ma_data=np.ma.masked_where(new_data<=0,new_data)
try_data=ma_data.astype(np.uint8)
## Building threshold
tmp_data=ma_data.filled(0)
tmp_data[np.where(tmp_data!=0)]=255
bw_data=tmp_data.astype(np.uint8)
## Building markers
ma_conv=np.ma.masked_where(new_data<=2,new_data)
tmp_conv=ma_conv.filled(0)
tmp_conv[np.where(tmp_conv!=0)]=255
bw_conv=tmp_conv.astype(np.uint8)
markers = ndimage.label(bw_conv)[0]
## Watershed
labels = watershed(-try_data, markers, mask=bw_data)
you can try changing your image fram gray to a BGR color space using
cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
before passing your image to watershed algorithm
Related
In Python, I am using a background removal tool and scikit's Felzenszwalb algorithm to segment my image via
from rembg.bg import remove
import numpy as np
import io
from PIL import Image
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
input_path = 'in.jpg'
output_path = 'out.png'
f = np.fromfile(input_path)
result = remove(f)
img = Image.open(io.BytesIO(result)).convert("RGBA")
#importing needed libraries
import skimage.segmentation
from matplotlib import pyplot as plt
#performing segmentation
res1 = skimage.segmentation.felzenszwalb(img, scale=500)
##uncomment to print results if desired
#fig = plt.figure(figsize=(12, 5))
#ax1 = fig.add_subplot(121)
#ax1.imshow(res1); ax1.set_xlabel("k=500")
#fig.suptitle("Graph based image segmentation")
#plt.tight_layout()
which gives me a plot of the segmented image in a few different colors. The output res1 is an ndarray "integer mask indicating segment labels" (from the scikit website). I now want to perform some color analysis on each of these regions. How would I use res1 to access each mask in turn (with either a homogeneous or transparent background) and perform my color analysis?
As you are using Numpy, you can access it with np.where
Among the regions you obtained with res1, choose the one you want to analyse (you can list the regions with np.unique(res1)). For example for mask '2', do :
masked_img_2 = np.where(res1==2,img,0)
plt.imshow(masked_img_2) # if you want to visualize the result with matplotlib
this means : "keep img where res1 is equal to '2', elsewhere set it to Zero"
https://numpy.org/doc/stable/reference/generated/numpy.where.html
I'm a newbie to tensorflow and keras, and I'm trying to create a CNN model for The Street View House Numbers (SVHN) dataset. The dataset contains color images, and I want to turn them in grayscale. I found some code on the web that claims they're turning image to grayscale, but it just changes colors.
People are reading the second image with a gray colormap. Is there any way to actually turn this image to grayscale?
(I do not know how to process an image in this kind of programming languages. If this is a dumb question, please forgive me and provide a brief explain.)
I provided images and code below, I'll be grateful for any help.
Code:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
#Read picture:
picture = plt.imread('google.jpg')
print("google logo's shape is: ",picture.shape) #(500, 500, 3)
#saving picture as an np array:
pic_array = np.array(picture)
#Turning image to grayscale
grayscale_pic = np.expand_dims(np.dot(pic_array[...,:3],[0.299, 0.587, 0.144]),axis = 0)
#Dimensions shifted, (probly my mistake):
grayscale_pic = np.moveaxis(grayscale_pic, 0, -1)
print("shape of grayscale pic = ", grayscale_pic.shape) # (500, 500, 1)
plt.imshow(picture) #Figure_1
plt.show()
plt.imshow(grayscale_pic) #Figure_2
plt.show()
U can convert a normal image to grayscale using opencv like this:
import cv2
gray = cv2.cvtColor(picture,cv2.COLOR_RGB2GRAY)
If u prefer numpy over opencv, then u can use this:
gray = np.dot(picture[...,:3], [0.2989, 0.5870, 0.1140])
You can use matplotlib with weights:
import numpy as np
import matplotlib.pyplot as plt
an_image = plt.imread('google.png')
rgb_weights = [0.2989, 0.5870, 0.1140]
grayscale_image = np.dot(an_image[..., :3], rgb_weights)
plt.axis('off')
plt.imshow(grayscale_image, cmap=plt.get_cmap("gray"), aspect='auto')
plt.show()
Output:
If you remove aspect='auto' parameter:
or you can use opencv
import cv2
an_image = cv2.imread("google.png")
grey_image = cv2.cvtColor(an_image, cv2.COLOR_BGR2GRAY)
or you can use PIL library
from PIL import Image
img = Image.open('google.png').convert('LA')
LA mode is L (8-bit pixels, black and white) with ALPHA desinged for .gif and .png. If your images are .jpeg use L.
Output:
There can be several ways to do this. One potential way is to utilize PIL(Pillow) library:
from PIL import Image
import matplotlib.pyplot as plt
picture = Image.open('google.jpg')
grayscale_pic = picture.convert('LA')
grayscale_pic.save('grayscale.png')
fig,ax = plt.subplots(nrows=1, ncols=2)
plt.subplot(1,2,1)
plt.imshow(picture)
plt.subplot(1,2,2)
plt.imshow(grayscale_pic)
plt.show()
Output:
I'm working with Python and trying to do Otsu thresholding on an image but only inside the mask (yes, I have an image and a mask image). It means less pixel on the image will be included in the histogram for calculating the Otsu threshold.
I'm currently using the cv2.threshold function without the mask image and have no idea how to do this kind of job.
ret, OtsuMat = cv2.threshold(GaborMat, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
Since this function also incorporates the pixels outside the mask, I think it will give a less accurate threshold.
This is the example of the image and its mask:
https://drive.google.com/drive/folders/1p8JMhncJs19oOWO9RdkWuEADVGqE-gzQ?usp=sharing
Hope there is a OpenCV or other lib function to do it easily (and also with fast computing), but any kind of help will be appreciated.
I had a try at this using the threshold_otsu() method from skimage and a Numpy masked array. I don't know if there are faster ways - the skimage is normally pretty well optimised. If anyone else wants to take my sample data and try other ideas on it, please feel free - although there is a service charge of one upvote ;-)
#!/usr/bin/env python3
import cv2
import numpy as np
import numpy.ma as ma
from skimage.filters import threshold_otsu
# Set up some repeatable test data, 4 blocks 100x100 pixels each of random normal np.uint8s centred on 32, 64, 160,192
np.random.seed(42)
a=np.random.normal(size = (100,100), loc = 32,scale=10).astype(np.uint8)
b=np.random.normal(size = (100,100), loc = 64,scale=10).astype(np.uint8)
c=np.random.normal(size = (100,100), loc = 160,scale=10).astype(np.uint8)
d=np.random.normal(size = (100,100), loc = 192,scale=10).astype(np.uint8)
# Stack (concatenate) the 4 squares horizontally across the page
im = np.hstack((a,b,c,d))
# Next line is just for debug
cv2.imwrite('start.png',im)
That gives us this:
# Now make a mask revealing only left half of image, centred on 32 and 64
mask=np.zeros((100,400))
mask[:,200:]=1
masked = ma.masked_array(im,mask)
print(threshold_otsu(masked.compressed())) # Prints 47
# Now do same revealing only right half of image, centred on 160 and 192
masked = ma.masked_array(im,1-mask)
print(threshold_otsu(masked.compressed())) # Prints 175
The histogram of the test data looks like this, x-axis is 0..255
Adapting to your own sample data, I get this:
#!/usr/bin/env python3
import cv2
import numpy as np
import numpy.ma as ma
from skimage.filters import threshold_otsu
# Load images
im = cv2.imread('eye.tif', cv2.IMREAD_UNCHANGED)
mask = cv2.imread('mask.tif', cv2.IMREAD_UNCHANGED)
# Calculate Otsu threshold on entire image
print(threshold_otsu(im)) # prints 130
# Now do same for masked image
masked = ma.masked_array(im,mask>0)
print(threshold_otsu(masked.compressed())). # prints 124
I'm trying to blur an image using fft by passing a low pass filter that I created but the output yields to be an image full of gray noise. I'm just trying to follow the basics here but it seems like there is something wrong with my implementation:
from scipy import fftpack
import numpy as np
import imageio
from PIL import Image, ImageDraw
image1 = imageio.imread('image.jpg',as_gray=True)
#convert image to numpy array
image1_np=np.array(image)
#fft of image
fft1 = fftpack.fftshift(fftpack.fft2(image1_np))
#Create a low pass filter image
x,y = image1_np.shape[0],image1_np.shape[1]
#size of circle
e_x,e_y=50,50
#create a box
bbox=((x/2)-(e_x/2),(y/2)-(e_y/2),(x/2)+(e_x/2),(y/2)+(e_y/2))
low_pass=Image.new("L",(image1_np.shape[0],image1_np.shape[1]),color=0)
draw1=ImageDraw.Draw(low_pass)
draw1.ellipse(bbox, fill=255)
low_pass_np=np.array(low_pass)
low_pass_fft=fftpack.fftshift(fftpack.fft2(low_pass))
#multiply both the images
filtered=np.multiply(fft1,low_pass_fft)
#inverse fft
ifft2 = abs(fftpack.ifft2(fftpack.ifftshift(filtered)))
#save the image
imageio.imsave('fft-then-ifft.png', ifft2.astype(np .uint8))
As mentioned in comments by Cris Luengo, there are a few things that need to be corrected:
The provided elliptical shape for the low-pass filter makes sense in the frequency-domain, so you shouldn't be computing its FFT.
The filter magnitude of 255 scales the results by the same amount. As you store such large values, the uint8 type wraps around to keep only the 8 least significant bits, resulting in something that looks like noise. This can be fixed by simply changing the value of the filter:
draw1.ellipse(bbox, fill=1)
After readjusting the scaling, there computed filtered may still get slightly out of the desired 0-255 range in some areas of the image. This creates wrap-around spots (black areas in regions surrounded by white pixels, white areas in regions surrounded by black pixels, or even gradient bands where the image goes from white to black to white). To avoid this is common to clip the values to the 0-255 range with the following:
ifft2 = np.real(fftpack.ifft2(fftpack.ifftshift(filtered)))
ifft2 = np.maximum(0, np.minimum(ifft2, 255))
After making these corrections, you should have the following code:
from scipy import fftpack
import numpy as np
import imageio
from PIL import Image, ImageDraw
image1 = imageio.imread('image.jpg',as_gray=True)
#convert image to numpy array
image1_np=np.array(image1)
#fft of image
fft1 = fftpack.fftshift(fftpack.fft2(image1_np))
#Create a low pass filter image
x,y = image1_np.shape[0],image1_np.shape[1]
#size of circle
e_x,e_y=50,50
#create a box
bbox=((x/2)-(e_x/2),(y/2)-(e_y/2),(x/2)+(e_x/2),(y/2)+(e_y/2))
low_pass=Image.new("L",(image1_np.shape[0],image1_np.shape[1]),color=0)
draw1=ImageDraw.Draw(low_pass)
draw1.ellipse(bbox, fill=1)
low_pass_np=np.array(low_pass)
#multiply both the images
filtered=np.multiply(fft1,low_pass_np)
#inverse fft
ifft2 = np.real(fftpack.ifft2(fftpack.ifftshift(filtered)))
ifft2 = np.maximum(0, np.minimum(ifft2, 255))
#save the image
imageio.imsave('fft-then-ifft.png', ifft2.astype(np .uint8))
And the following filtered image:
I have segmented my image using the quickshift method found in the scikit image libary. How can I calculate the average color and the area of a superpixel? How can I interpret the return value of the quickshift() method? The documentation says the return value is "Integer mask indicating segment labels" but this is not clear for me. How can I make a boolean array in the shape of the original image, filled with ones, where the particular superpixel is present, in this presentation my life would be easier(I used to work with this kind of masks in OpenCV). Can you help me with this? My code (simplified example from scikit-image website):
from skimage.data import astronaut
from skimage.segmentation import felzenszwalb, slic, quickshift
from skimage.segmentation import mark_boundaries
from skimage.util import img_as_float
img = img_as_float(astronaut()[::2, ::2])
segments_quick = quickshift(img, kernel_size=3, max_dist=6, ratio=0.5)
print("Quickshift number of segments: %d" % len(np.unique(segments_quick)))
plt.imshow(mark_boundaries(img, segments_quick))
plt.show()
The skimage.measure.regionprops function returns the properties of labeled regions, including the ones you are interested in: average and area. You use it as follows:
from skimage import measure
regions = measure.regionprops(segments_quick, intensity_image=img)
regions is a list, with each entry a RegionProperty object representing a superpixel. You can query the region properties as follows:
print([r.area for r in regions])
print([r.mean_intensity for r in regions])
Here is a simple, straight forward and generic code to represnt each pixel with the mean of its superpixel.
Here the label array is obtained using SLIC, any other approach can be used to generate the labels.
import numpy as np
import cv2
from skimage import segmentation
from skimage.data import astronaut
img=cv2.cvtColor(astronaut(),cv2.COLOR_BGR2RGB)
label=segmentation.slic(img,compactness=10, n_segments=1000)
def mean_image(image,label):
im_rp=image.reshape((image.shape[0]*image.shape[1],image.shape[2]))
sli_1d=np.reshape(label,-1)
uni=np.unique(sli_1d)
uu=np.zeros(im_rp.shape)
for i in uni:
loc=np.where(sli_1d==i)[0]
#print(loc)
mm=np.mean(im_rp[loc,:],axis=0)
uu[loc,:]=mm
oo=np.reshape(uu,[image.shape[0],image.shape[1],image.shape[2]]).astype('uint8')
cv2.imshow('img',oo)
output=mean_image(img,label) # displays the output image.
The easiest way for me was this:
from skimage import segmentation, color
from skimage.io import imread
from skimage.future import graph
from matplotlib import pyplot as plt
img = imread('test.jpeg')
img_segments = segmentation.slic(img, compactness=20, n_segments=500)
superpixels = color.label2rgb(img_segments, img, kind='avg')