Generate bounding boxes from elevation map/watershed transform - image-processing

I am using scikit-image to perform image segmentation on some images I have, and I was referring to this tutorial (https://scikit-image.org/docs/dev/user_guide/tutorial_segmentation.html)
I am using the elevation map method described and although I am not able to get correct result after applying watershed algorithm, the elevation map itself seems good enough for my purpose.
I am getting this output when I plot the elevation map:
Now I want to get the coordinates for the bounding box for this object. How do I achieve this? I looked at http://scikit-image.org/docs/dev/auto_examples/edges/plot_contours.html but I am unable to figure out how do I choose the second argument for find_contours ?

Does this work for you?
from skimage import filters
from scipy import ndimage as ndi
threshold = filters.threshold_otsu(elevation_map)
binary = (elevation_map > threshold).astype(int)
bounding_box = ndi.find_objects(binary)[0]

Related

How to extract the object of interest from a background when there's low contrast?

I'm working on a project in which we require to extract the cans being transported by a conveyor belt. I develop an automatic threshold selection algorithm based on Kittler's approach which uses the histogram of the grayscale image to determine the optimum threshold to separate the object from the background (similar to Otsu's algorithm implemented in OpenCV).
Now, for the algorithm to be successful it requires proper contrast between the object being analyzed and the background, so I have had so trouble making it work with the images below. To enhance the contrast on the image I have tried different contrast stretching and adaptive equalization with poor results.
So, I would like to know any suggestions on how to improve the image contrast? Or if there's a different segmentation method that could work better on this images instead of thresholding? An important detail to consider is that the camera is working with a blue led light.
Half full conveyor belt:
Full conveyor belt:
I wrote some python code to start you off.
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import cv2
image = Image.open("TDP4f.jpg")
arr = np.asarray(image)
grey = np.mean(arr.astype(float)/255.0,axis=2)
grey = cv2.equalizeHist((255*grey).astype(np.uint8)).astype(float)/255.0
binary = cv2.adaptiveThreshold((255*grey).astype(np.uint8),255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,5,0.0)
#binary = cv2.morphologyEx(binary.astype(np.uint8),cv2.MORPH_ERODE,np.ones((3,3)),iterations=1)
# --- Adapted from https://docs.opencv.org/4.x/da/d53/tutorial_py_houghcircles.html
cimg=arr.copy()
R=20
err = 0.1
circles = cv2.HoughCircles(binary,cv2.HOUGH_GRADIENT,2,R,
param1=50,param2=30,minRadius=int(R-err*R),maxRadius=int(R+err*R))
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
# ---
plt.subplot(1,2,1)
plt.imshow(binary,cmap='gray')
plt.subplot(1,2,2)
plt.imshow(cimg)
plt.show()
Result:
You can tweak the parameters to get a better fit.
Resources:
https://docs.opencv.org/4.x/da/d53/tutorial_py_houghcircles.html
https://docs.opencv.org/3.4/d4/d1b/tutorial_histogram_equalization.html
https://docs.opencv.org/4.x/d7/d4d/tutorial_py_thresholding.html

How to find the center point of two objects in a binary image with openCV?

I need to get the coordinates (center points) of the two white objects in my binary image for further processing. This is an example image:
How can I achieve this?
I am using Python and openCV
It is not opencv, but you can give a try to the regionprops method of skimage example from the docs
from skimage.measure import regionprops
for region in regionprops(yourbinary_image):
centroid = region.centroid
#do stuff with that centroid ;)

Critical parameter behind skimage's watershed "over-segmentation"

I have the following mask of cell nuclei, and my goal is to segment them. However, using what seems to be a very standard approach,
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from skimage.segmentation import watershed
from skimage import measure
# load mask
mask = mpimg.imread('mask.png')
# find distance to nearest border
distance = scipy.ndimage.distance_transform_edt(mask)
# find local maxima based on distance to border
local_maxi = peak_local_max(distance, indices=False, footprint=np.ones((125, 125)), labels=mask)
# generate markers for regions
markers = measure.label(local_maxi)
# watershed segmentation
labeled = watershed(-distance, markers, mask=mask, watershed_line = True)
# plot figure
fig, axs = plt.subplots()
axs.imshow(labeled, cmap='flag')
some large, connected components are unsegmented while smaller unconnected components become oversegmented:
Throughly browosing answers on StackOverflow, I haven't been able to find is a discussion of which parameters drive 'under-segmentation' vs 'over-segmentation' in the skimage.segmentation.watershed algorithm.
Which parameter most strongly influences "oversegmentation" in the watershed algorithm? My intuition tells me it could be the footprint size? or the distance transform? What is the most critical parameter that determines the segmentation neighbourhood?
EDIT1: Below I have included the distance transform, the filtering of which others have pointed out is a critically important step. However, I am still unable to diagnose symptoms of a "bad" distance transform, and unaware of rules of thumbs for filtering said transform.
In your particular case, the origin of some of your over-segmentation is on the result of peak_local_max().
If you run the following code you will be able to find which local maximums are selected for your image. I'm using OpenCV for plotting dots, you might want to adapt it for another library.
import cv2
import numpy as np
import matplotlib.pyplot as plt
localMax_idx = np.where(local_maxi)
localMax_img = mask.copy()
localMax_img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
for i in range(localMax_idx[0].shape[0]):
x = localMax_idx[1][i]
y = localMax_idx[0][i]
localMax_img = cv2.circle(localMax_img, (x,y), radius=5, color=(255, 0, 0), thickness=-1)
plt.imshow(localMax_img)
plt.show()
You will see that there are multiple markers for over-segmented cells. There are some suggested approaches to deal with this issue (for example, this one).

Image as function: Does someone know which Matlab or equivalent openCV function did they use to plot an image?

REVISED:
Topic: Image as function:
Does someone know which Matlab or equivalent openCV function did they use to plot this image?
1) this is an actual image
2) for analysis plot an image on x,y plane
3) Smooth edges, guess what will it do original image?!!
4) original image after applying smoothing algo
I am taking Udacity, Intro to Computer Vision course. Their forums are old and very few people are taking this course. Response time is too slow. Please let me know if there is more info I can provide.
Thanks
The MATLAB surf function makes plots like this:
img = imread('cameraman');
h = surf(img);
h.LineStyle = 'none';
colormap('gray')
view(160,75)
axis tight
axis equal
The image look quite horrible. I can see a skull in the middle of the graph.
You could use matplotlib to draw 3d picture. There're several tutorial here : https://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html
Hope that help

watershed segmentation always return black image

I've been recently working at a segmentation process for corneal
endothelial cells, and I've found a pretty decent paper that describes ways to perform it with nice results. I have been trying to follow that paper and implement it all using scikit-image and openCV, but I've gotten stucked at the watershed segmentation.
I will briefly describe how is the process supposed to be:
First of all, you have the original endothelial cells image
original image
Then, they instruct you to perform a morphological grayscale reconstruction, in order to level a little bit the grayscale of the image (however, they do not explain how to get the markers for the grayscale, so I've been fooling around and tried to get some on my own way)
This is what the reconstructed image was supposed to look like:
desired reconstruction
This is what my reconstructed image (lets label it as r) looks like:
my reconstruction
The purpose is to use the reconstructed image to get the markers for the watershed segmentation, how do we do that?! We get the original image (lets label it as f), and perform a threshold in (f - r) to extract the h-domes of the cell, i.e., our markers.
This is what the hdomes image was supposed to look like:
desired hdomes
This is what my hdomes image looks like:
my hdomes
I believe that the hdomes I've got are as good as theirs, so, the final step is to finally perform the watershed segmentation on the original image, using the hdomes we've been working so hard to get!
As input image, we will use the inverted original image, and as markers, our markers.
This is the derised output:
desired output
However, I am only getting a black image, EVERY PIXEL IS BLACK and I have no idea of what's happening... I've also tried using their markers and inverted image, however, also getting black image. The paper I've been using is Luc M. Vincent, Barry R. Masters, "Morphological image processing and network analysis of cornea endothelial cell images", Proc. SPIE 1769
I apologize for the long text, however I really wanted to explain everything in detail of what is my understanding so far, btw, I've tried watershed segmentation from both scikit-image and opencv, both gave me the black image.
Here is the following code that I have been using
img = cv2.imread('input.png',0)
mask = img
marker = cv2.erode(mask, cv2.getStructuringElement(cv2.MORPH_ERODE,(3,3)), iterations = 3)
reconstructedImage = reconstruction(marker, mask)
hdomes = img - reconstructedImage
cell_markers = cv2.threshold(hdomes, 0, 255, cv2.THRESH_BINARY)[1]
inverted = (255 - img)
labels = watershed(inverted, cell_markers)
cv2.imwrite('test.png', labels)
plt.figure()
plt.imshow(labels)
plt.show()
Thank you!
Here's a rough example for the watershed segmentation of your image with scikit-image.
What is missing in your script is calculating the Euclidean distance (see here and here) and extracting the local maxima from it.
Note that the watershed algorithm outputs a piece-wise constant image where pixels in the same regions are assigned the same value. What is shown in your 'desired output' panel (e) are the edges between the regions instead.
import numpy as np
import cv2
import matplotlib.pyplot as plt
from skimage.morphology import watershed
from scipy import ndimage as ndi
from skimage.feature import peak_local_max
from skimage.filters import threshold_local
img = cv2.imread('input.jpg',0)
'''Adaptive thersholding
calculates thresholds in regions of size block_size surrounding each pixel
to handle the non-uniform background'''
block_size = 41
adaptive_thresh = threshold_local(img, block_size)#, offset=10)
binary_adaptive = img > adaptive_thresh
# Calculate Euclidean distance
distance = ndi.distance_transform_edt(binary_adaptive)
# Find local maxima of the distance map
local_maxi = peak_local_max(distance, labels=binary_adaptive, footprint=np.ones((3, 3)), indices=False)
# Label the maxima
markers = ndi.label(local_maxi)[0]
''' Watershed algorithm
The option watershed_line=True leave a one-pixel wide line
with label 0 separating the regions obtained by the watershed algorithm '''
labels = watershed(-distance, markers, watershed_line=True)
# Plot the result
plt.imshow(img, cmap='gray')
plt.imshow(labels==0,alpha=.3, cmap='Reds')
plt.show()

Resources