Python edge detection of low contrast image - image-processing

I want to draw find the depth of the drawed region and draw the boundaries I tried to use Canny edge detection, but it is not good to get the edges. Are there ways to get the pixel of the boundary of interest? The pixel intensity (in grayscale) in the boundary line is not nuch different from the surrounding area.
The region of interest is melting pool on a metal. The purpose is to find the depth of the melting pool. I tried Canny edge detection but it seems not work to solve the problem.
Are there other ways using python to coordinates of boundary of melting pool boundary which I colored in red in picture 2?Original image Region of interest (in red)
Canny edge detection The melting pool is moving. I want to use python to get the depth change of the melting pool. I have bunch of images
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('image.tif',0)
edges = cv2.Canny(img,100,20)
plt.subplots(),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplots(),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
plt.show()

I think the best way to obtain the desired information about the metal pool is to segment it. Since the image is noisy, I think graph-cut is the better option.
I estimate the pool boundary with the vertical Scharr filtering and use them to compute the graph arc-weight.
From this, I use the upper and lower bourders of the image as source and sink for the graph-cut algorithm (these pixels will belong to different labels).
Second segmentation is performed to obtain the horizontal line without the pool and compute their difference to obtain the final result.
The beta parameter must be tuned, as it increases it will adhere more to your weights (noisy boundary). I found that 50 gets good results, but you should play with it.
import numpy as np
from skimage import io, filters, measure
import skimage.morphology as morph
import matplotlib.pyplot as plt
import maxflow
def normalize(im):
im -= im.min()
return im / im.max()
def graph_cut(weights):
g = maxflow.GraphFloat()
nodeids = g.add_grid_nodes(weights.shape)
structure = maxflow.vonNeumann_structure(ndim=2, directed=True)
g.add_grid_edges(nodeids, weights, structure=structure, symmetric=True)
g.add_grid_tedges(nodeids[1, :], 0, 1e16)
g.add_grid_tedges(nodeids[-1, :], 1e16, 0)
g.maxflow()
return g.get_grid_segments(nodeids)
def get_largest(label):
label = measure.label(label)
largest = label == np.argmax(np.bincount(label.flat)[1:])+1
return largest
def main():
im = io.imread("example.png")
im = filters.median(im, morph.disk(5))
# pool segmentation
beta = 50 # parameter
aux = filters.scharr_v(im)
aux = normalize(np.abs(aux))
weights = np.exp(-beta * aux)
pool = graph_cut(weights)
# end
# surface segmentation
aux = np.abs(filters.scharr(im))
aux = normalize(aux)
weights = np.exp(-aux)
surf = graph_cut(weights)
# end
# result
res = pool ^ surf # xor
res = get_largest(res)
contours = measure.find_contours(res, 0.5)
fig, ax = plt.subplots()
ax.imshow(im, cmap='gray')
for contour in contours:
ax.plot(contour[:, 1], contour[:, 0], linewidth=1, c = 'red')
plt.show()
if __name__ == "__main__":
main()
Results:

Strong horizontal lowpass filtering will improve the signal-to-noise ratio and make the top edge easy to detect.
Note that straight binarization of the raw image performs even better.
Adaptive thresholding is interesting as well, though requires some tuning.

Related

Extract stripes from low contrast grayscale images

I want to extract stripes from this sample file sample file, and the result should look like this one similar result image. Then, I need to count the number of stripes on the right, and calculate the distance from the end of each left stripe to the end of each adjacent right stripe.
I tried with the following code, but my result my result fileis still a little bit away from my target. Here is what I do:
import numpy as np
import cv2
from matplotlib import pyplot as plt
gray = cv2.imread('input_file.png',cv2.IMREAD_UNCHANGED)
sobelY = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3)
sobelY2 = cv2.Sobel(sobelY, cv2.CV_32F, 0, 1, ksize=3)
sobelY2[sobelY2<0]=0
mask = np.where(sobelY2==0,0,1)
sobelY2 = cv2.normalize(sobelY2, dst=None, alpha=0, beta=65535, norm_type=cv2.NORM_MINMAX).astype(np.uint16)
clahe=cv2.createCLAHE(clipLimit=6, tileGridSize=(8,8))
sobelY2_clahe = clahe.apply(sobelY2)
sobelY2_clahe = clahe.apply(sobelY2_clahe)
result = np.where(mask!=0,sobelY2_clahe,0)
fig = plt.figure(figsize=(10, 10))
ax = plt.subplot(121)
plt.imshow(gray, cmap='gray')
ax = plt.subplot(122)
plt.imshow(result, cmap='gray')
plt.show()
The input file is in 16 bits format, so I keep it unchanged for accuracy. I do second order Sobel operation in Y direction to high light those stripes, and then I do two times Clahe operations to balance the contrast. To keep the background pixels as 0, I use a mask to set the values back after the Clahe operations.
Any advice is appreciated!
For completeness, I am attaching another more challenged input file for referencemore challenged input file.
Edit:
The sobelY2 image pretty much reflects the stripes, but could we make it look better?
I just opened a new question about how to trim each of these stripes based on gray scale values.trim image based on grayscale values

Identify and Measure Noise Type present in an Image

I want to identify if the image contains noise. Example: Salt and Pepper Noise or Gaussian Noise. I also want to measure the amount of noise present in the image. How shall I do it? Can I do it by analyzing the histogram of the images? Below is my initial code;
import matplotlib.pyplot as plt
import numpy as np
import os.path
if __name__ == '__main__':
image_counter = 1
while True:
if not os.path.isfile('crop_images/' + str (image_counter) + '.png'):
break
image_path = 'crop_images/' + str(image_counter) + '.png'
image = plt.imread(image_path)
#Display Histogram
print(image)
print(image.ravel())
n, bins, patches = plt.hist(image.ravel(), bins = 256)
plt.title('Image Patch # ' + str(image_counter))
plt.xlabel('Grey Value')
plt.ylabel('Frequency')
window = plt.get_current_fig_manager()
window.canvas.set_window_title('Histogram')
plt.show()
image_counter = image_counter + 1
S&P noise: It means some random pixels of your image are set to black or white (or some constant value per channel). If you have spikes at 0 and 255 values (or some constant value per channel) in the histogram of the image, you likely have salt and pepper noise. You can apply a median filter to get rid of the noise, and the size of your kernel that minimizes the spikes in the histogram can inform you about the noise level.
Gaussian noise: It means there is some blur in the image. Laplacian kernels are best both to generate and detect blur. If you apply a Laplacian kernel to your image and take its variance, the answer will give you how "edgy" the image is. If the number is high, it means the variance is high, it means there are sudden changes (i.e. edges!) in the image, which means less blur.

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()

Find orientation of object using PCA

I want to find the orientation of the bright object in the images attached. For this purpose, I used Principal Component Analysis(PCA).
In case of image 1, PCA finds correct orientation as the first principal component is alligned in that direction. However, in case of image 2, the principal components are disoriented.
Can anyone please explain why the PCA is showing different results in the two images? Also, please suggest if there is some other method to find the orientation of the object.
import os
import gdal
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import skimage
from skimage.filters import threshold_otsu
from skimage.filters import try_all_threshold
import cv2
import math
from skimage import img_as_ubyte
from skimage.morphology import convex_hull_image
import pandas as pd
file="path to image file"
(fileRoot, fileExt)= os.path.splitext(file)
ds = gdal.Open(file)
band = ds.GetRasterBand(1)
arr = band.ReadAsArray()
geotransform = ds.GetGeoTransform()
[cols, rows] = arr.shape
thresh = threshold_otsu(arr)
binary = arr > thresh
points = binary>0
y,x = np.nonzero(points)
x = x - np.mean(x)
y = y - np.mean(y)
coords = np.vstack([x, y])
cov = np.cov(coords)
evals, evecs = np.linalg.eig(cov)
sort_indices = np.argsort(evals)[::-1]
evec1, evec2 = evecs[:, sort_indices]
x_v1, y_v1 = evec1
x_v2, y_v2 = evec2
scale = 40
plt.plot([x_v1*-scale*2, x_v1*scale*2],
[y_v1*-scale*2, y_v1*scale*2], color='red')
plt.plot([x_v2*-scale, x_v2*scale],
[y_v2*-scale, y_v2*scale], color='blue')
plt.plot(x,y, 'k.')
plt.axis('equal')
plt.gca().invert_yaxis()
plt.show()
theta = np.tanh((x_v1)/(y_v1)) * 180 /(math.pi)
You claim you are using just white pixels. Did you check which ones are selected by some overlay render? Anyway I do not think it is enough especially for your second image as it does not contain any fully saturated white pixels. I would use more processing before the PCA.
enhance dynamic range
your current images does not need this step as they contain both black and almost fully saturated white. This step allow to unify threshold values among more sample input images. For more info see:
Enhancing dynamic range and normalizing illumination
smooth a bit
this step will significantly lover the intensity of noise points and smooth the edges of bigger objects (but shrink them a bit). This can be done by any FIR filter or convolution or Gaussian filtering. Some also use morphology operators for this.
threshold by intensity
this will remove darker pixels (clear to black) so noise is fully removed
enlarge remaining objects by morphology operators back to former size
You can avoid this by enlarging the resulting OBB by few pixels (number is bound to smooth strength from #2).
now apply OBB search
You are using PCA so use it. I am using this instead:
How to Compute OBB of Multiple Curves?
When I tried your images with above approach (without the #4) I got these results:
Another problem I noticed with your second image is that there are not many white pixels in it. That may bias the PCA significantly especially without preprocessing. I would try to enlarge the image by bicubic filtering and use that as input. May be that is the only problem you got with it.

Noise Estimation / Noise Measurement in Image

I want to estimate the noise in an image.
Let's assume the model of an Image + White Noise.
Now I want to estimate the Noise Variance.
My method is to calculate the Local Variance (3*3 up to 21*21 Blocks) of the image and then find areas where the Local Variance is fairly constant (By calculating the Local Variance of the Local Variance Matrix).
I assume those areas are "Flat" hence the Variance is almost "Pure" noise.
Yet I don't get constant results.
Is there a better way?
Thanks.
P.S.
I can't assume anything about the Image but the independent noise (Which isn't true for real image yet let's assume it).
You can use the following method to estimate the noise variance (this implementation works for grayscale images only):
def estimate_noise(I):
H, W = I.shape
M = [[1, -2, 1],
[-2, 4, -2],
[1, -2, 1]]
sigma = np.sum(np.sum(np.absolute(convolve2d(I, M))))
sigma = sigma * math.sqrt(0.5 * math.pi) / (6 * (W-2) * (H-2))
return sigma
Reference: J. Immerkær, “Fast Noise Variance Estimation”, Computer Vision and Image Understanding, Vol. 64, No. 2, pp. 300-302, Sep. 1996 [PDF]
The problem of characterizing signal from noise is not easy. From your question, a first try would be to characterize second order statistics: natural images are known to have pixel to pixel correlations that are -by definition- not present in white noise.
In Fourier space the correlation corresponds to the energy spectrum. It is known that for natural images, it decreases as 1/f^2 . To quantify noise, I would therefore recommend to compute the correlation coefficient of the spectrum of your image with both hypothesis (flat and 1/f^2), so that you extract the coefficient.
Some functions to start you up:
import numpy
def get_grids(N_X, N_Y):
from numpy import mgrid
return mgrid[-1:1:1j*N_X, -1:1:1j*N_Y]
def frequency_radius(fx, fy):
R2 = fx**2 + fy**2
(N_X, N_Y) = fx.shape
R2[N_X/2, N_Y/2]= numpy.inf
return numpy.sqrt(R2)
def enveloppe_color(fx, fy, alpha=1.0):
# 0.0, 0.5, 1.0, 2.0 are resp. white, pink, red, brown noise
# (see http://en.wikipedia.org/wiki/1/f_noise )
# enveloppe
return 1. / frequency_radius(fx, fy)**alpha #
import scipy
image = scipy.lena()
N_X, N_Y = image.shape
fx, fy = get_grids(N_X, N_Y)
pink_spectrum = enveloppe_color(fx, fy)
from scipy.fftpack import fft2
power_spectrum = numpy.abs(fft2(image))**2
I recommend this wonderful paper for more details.
Scikit Image has an estimate sigma function that works pretty well:
http://scikit-image.org/docs/dev/api/skimage.restoration.html#skimage.restoration.estimate_sigma
it also works with color images, you just need to set multichannel=True and average_sigmas=True:
import cv2
from skimage.restoration import estimate_sigma
def estimate_noise(image_path):
img = cv2.imread(image_path)
return estimate_sigma(img, multichannel=True, average_sigmas=True)
High numbers mean low noise.

Resources