How to detect the centerline from edges of a shape? - python

The unfiltered imageI have a 2D image of a vessel in grayscale and I want to convert it to a 3D model for further analysis.
I’ve imported it using opencv (cv2) library and I’ve written a code for detecting the edges of the shape in the image.
But, I have some problem with detecting the center-line by using the edges coordinates.I want the center-line for revolving the shape and convert it to a 3D model)
I appreciate any help.
Here is the image but as you can see the line hasn't been fitted to the middle of the shape.
import cv2
import numpy as np
from matplotlib import pyplot as plt
#Applying the bilateral blur to the image
img = cv2.imread('vessel.png')
blur = cv2.bilateralFilter(img,9,75,75)
#Applying the Canny edge detection to the blurred image to find the edges
edges = cv2.Canny(blur,100,200)
#Getting the coordinateof the pixels
x , y = np.where(edges > 0.5)
#Getting the regression line (slope and intercept)
m, b = np.polyfit(x, y, deg=1)
#Drowing the line in the image
edges_img = cv2.line(edges, (int(0 * m + b), 0), (int(500*m+b), 500) , color = (255,0,0) ,
thickness = 2)
#plotting the result in comparison with the raw image
plt.subplot(121),plt.imshow(blur,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges_img,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])
plt.show()`

Related

Is there a way to get color gradients without blurring the entire image?

I want to create a color gradient without the need to blur out the entire resulting image. Basically I want to create a gradient along the line where the colors meet. I have seen codes doing linear or radial gradients, but I don't know which one would work best or if there is another way to create the gradients.
import cv2 as cv
import matplotlib.pyplot as plt
## load the image in RGB color space
original_image = cv.imread("BEACH.jpeg")
## convert our image from RGB Colours Space to HSV
img = cv.cvtColor(original_image, cv.COLOR_BGR2RGB)
## convert our image to a matrix
vectorized = img.reshape((-1, 3))
## We convert the unit8 values to float as it is a requirement of the k-means method of OpenCV.
vectorized = np.float32(vectorized)
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 2
attempts = 100
ret, label, center = cv.kmeans(vectorized, K, None, criteria, attempts, cv.KMEANS_PP_CENTERS)
center = np.uint8(center)
res = center[label.flatten()]
result_image = res.reshape(img.shape)
figure_size = 5
plt.figure(figsize=(figure_size, figure_size))
plt.subplot(1, 2, 1), plt.imshow(img)
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(1, 2, 2), plt.imshow(result_image)
plt.title('Segmented Image when K = %i' % K), plt.xticks([]), plt.yticks([])
plt.show()

Skeletonize fails to create vertical and horizontal lines

I want to skeletonize an image with the function skeletonize from skimage.morphology but the result is really disappointing. Meaning it fails to recognize the horizontal and vertical lines connecting the different instruments of the drawing.
Any tips? What can I improve?
import cv2 as cv
from matplotlib import pyplot as plt
from skimage import filters
from skimage.morphology import skeletonize
# read image
img = cv.imread('realpars_p_id_diag_mod.png',0)
binary = img > filters.threshold_triangle(img)
# true false to one
binary_cp = binary.copy()
binary_cp[binary_cp == True] = 1
binary_cp[binary_cp == False] = 0
# skeletonize image
skeleton = skeletonize(binary_cp)
# print images
plt.figure(figsize=(20,20))
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(skeleton,cmap = 'gray')
plt.title('skeleton Image'), plt.xticks([]), plt.yticks([])
plt.savefig("binary and bin_skeleton.png")
plt.show()
That is disappointing! 😂 But there's clues in the curvature of the lines, and the fact that the diagonals go in the opposite direction as they do in the original image: you are skeletonizing the wrong image! You want to skeletonize the black parts, not the white parts, which means you need to invert the binary image. Or, more simply, change the sign in the threshold expression. Here's my updated code:
from skimage import io
from matplotlib import pyplot as plt
from skimage import filters
from skimage.morphology import skeletonize
# read image
img = io.imread('/Users/jni/Downloads/P4tB1.jpg')[..., 0]
# Note: we want the black bits to be True, so use <
binary = img < filters.threshold_triangle(img)
# skeletonize image
skeleton = skeletonize(binary)
# print images
fig, ax = plt.subplots(1, 2)
ax[0].imshow(img, cmap='gray')
ax[0].set_title('original')
ax[0].set_axis_off()
# note the reversed colormap, gray_r
ax[1].imshow(skeleton, cmap='gray_r')
ax[1].set_title('skeleton (inverse)')
ax[1].set_axis_off()
plt.show()
Result:

Detect circle with python opencv - Hough Transform

I tried to detect the external circle of that image
However, no matter how I set the params of the Hough Transform, I can't detect the external circle.
My code is the next:
###############################
#Circle detection
###############################
height, width = image.shape
circles = cv2.HoughCircles(image,cv2.HOUGH_GRADIENT,.3,20,param1=100,param2=100,minRadius=int(min(width,height)/3),maxRadius=int(min(width,height)))
circles = np.uint16(np.around(circles))
cimg=origin
for i in circles[0,:]:
cv2.circle(cimg,(i[0],i[1]),i[2],(255,0,0),1) #DRAW ALL CIRCLES IN BLUE
cv2.circle(cimg,(i[0],i[1]),2,(255,0,0),1)
###############################
#FIND HIGHER CIRCLE
###############################
#I go through all the circles and
#take the one with the greatest radio
max_index=0
max_i=circles[0,max_index,2]
for indx, i in enumerate(circles[0,:]):
if i[2]>max_i:
max_i=i[2]
max_index=indx #indx of higher circle
circle_max=max_i
x_max=circles[0,max_index,0]
y_max=circles[0,max_index,1]
r_max=circles[0,max_index,2]
cv2.circle(cimg,(x_max,y_max),r_max,(0,0,255),1) #DRAW HIGHER CIRCLE IN RED
cv2.circle(cimg,(x_max,y_max),2,(0,0,255),3)
This code detect a lot of circles, but the external circle never appears.
If you want to detect only one circle, this can help you:
import cv2
import numpy as np
from matplotlib import pyplot as plt
name_image = "ImageTest.png"
bgr_img = cv2.imread(name_image)
b,g,r = cv2.split(bgr_img) # get b,g,r
rgb_img = cv2.merge([r,g,b]) # switch it to rgb
gray_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY)
img = cv2.medianBlur(gray_img, 5)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,7,20,
param1=90,param2=2400,minRadius=0,maxRadius=0)
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(121),plt.imshow(rgb_img)
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cimg)
plt.title('Hough Transform'), plt.xticks([]), plt.yticks([])
plt.show()
cv2.imwrite(name_image.split(".png")[0] +'_HoughTransform.png', cimg)

How to change the thickness of the edge (contour) of an Image?

I am trying to extract the edge of an image (its contour) and change its thickness. I want to give it like the stroke effect of Photoshop layer style. Photoshop stroke effect example:
http://projectwoman.com/2012/11/smart-objects-and-strokes-in-photoshop.html
I was able to extract the edge from an image. Using canny edge or the pillow function.
1.using canny edge detection
img = cv2.imread(img_path,0)
edges = cv2.Canny(img,300,700)
2.using pillow filler
image = Image.open(img_path).convert('RGB')
image = image.filter(ImageFilter.FIND_EDGES())
but, I could not adjust the contour thickness.
Here a solution:
import cv2
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
image = cv2.imread('mickey.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2YCR_CB)[...,0]
def show_img(im, figsize=None, ax=None, alpha=None):
if not ax: fig,ax = plt.subplots(figsize=figsize)
ax.imshow(im, alpha=alpha)
ax.set_axis_off()
return ax
def getBordered(image, width):
bg = np.zeros(image.shape)
_, contours, _ = cv2.findContours(image.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
biggest = 0
bigcontour = None
for contour in contours:
area = cv2.contourArea(contour)
if area > biggest:
biggest = area
bigcontour = contour
return cv2.drawContours(bg, [bigcontour], 0, (255, 255, 255), width).astype(bool)
im2 = getBordered(image, 10)
show_img(im2, figsize=(10,10))
You can change thickness by changing param width in getBordered.

Why performing an histogram equalization by scikit image to a binary image i got a black image after mahotas conversion?

I used histogram equalization and adaptation for erase illumination from the grayscale images, but after the histogram equalization (i used scikit image python library) was good, during image conversion in mahotas something goes wrong. I got a picture total black. How can i fix it?
Source image:
Histogram equalization and adaptation;
Result after mahotas conversion.
conversion code from scikit to mahotas:
binimg = np.array(img_adapteq, dtype=np.bool)
Source code:
import scipy
import numpy as np
import pymorph as pm
import mahotas as mh
from skimage import morphology
from skimage import io
from matplotlib import pyplot as plt
from skimage import data, img_as_float
from skimage import exposure
def plot_img_and_hist(img, axes, bins=256):
"""Plot an image along with its histogram and cumulative histogram.
"""
img = img_as_float(img)
ax_img, ax_hist = axes
ax_cdf = ax_hist.twinx()
# Display image
ax_img.imshow(img, cmap=plt.cm.gray)
ax_img.set_axis_off()
# Display histogram
ax_hist.hist(img.ravel(), bins=bins, histtype='step', color='black')
ax_hist.ticklabel_format(axis='y', style='scientific', scilimits=(0, 0))
ax_hist.set_xlabel('Pixel intensity')
ax_hist.set_xlim(0, 1)
ax_hist.set_yticks([])
# Display cumulative distribution
img_cdf, bins = exposure.cumulative_distribution(img, bins)
ax_cdf.plot(bins, img_cdf, 'r')
ax_cdf.set_yticks([])
return ax_img, ax_hist, ax_cdf
mhgray = mh.imread(path,0)
binimg = mhgray[:,:,0]
print(type(binimg[0][0]))
thresh = mh.otsu(binimg)
gray =( binimg< thresh)
shape = list(gray.shape)
w = 0
if (shape[0] > shape[1]):
shape = shape[0]
else:
shape = shape[1]
if (shape < 100):
w = int((shape/100 )*1.5)
elif(shape > 100 and shape <420):
w = int((shape/100 )*2.5)
else:
w = int((shape/100)*4)
disk7 = pm.sedisk(w)
img = binimg
# Contrast stretching
p2 = np.percentile(img, 2)
p98 = np.percentile(img, 98)
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))
# Equalization
img_eq = exposure.equalize_hist(img)
# Adaptive Equalization
img_adapteq = exposure.equalize_adapthist(img, clip_limit=0.03)
# Display results
f, axes = plt.subplots(2, 4, figsize=(8, 4))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')
y_min, y_max = ax_hist.get_ylim()
ax_hist.set_ylabel('Number of pixels')
ax_hist.set_yticks(np.linspace(0, y_max, 5))
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_rescale, axes[:, 1])
ax_img.set_title('Contrast stretching')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_eq, axes[:, 2])
ax_img.set_title('Histogram equalization')
ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_adapteq, axes[:, 3])
ax_img.set_title('Adaptive equalization')
ax_cdf.set_ylabel('Fraction of total intensity')
ax_cdf.set_yticks(np.linspace(0, 1, 5))
# prevent overlap of y-axis labels
plt.subplots_adjust(wspace=0.4)
plt.show()
plt.gray()
plt.subplot(121)
plt.title("after histo")
plt.imshow(img_adapteq)
plt.show()
binimg = np.array(img_adapteq, dtype=np.bool)#uint16
plt.gray()
plt.subplot(121)
plt.title("after otsu")
plt.imshow(binimg)
plt.show()
imgbnbin = mh.morph.dilate(binimg, disk7)
#2
plt.gray()
plt.subplot(121)
plt.title("after dilate before close")
plt.imshow(imgbnbin)
plt.show()
imgbnbin = mh.morph.close(imgbnbin, disk7)
#2
plt.gray()
plt.subplot(121)
plt.title("before skeletonize")
plt.imshow(imgbnbin)
plt.show()
imgbnbin = mh.morph.close(imgbnbin, disk7)
out = morphology.skeletonize(imgbnbin>0)
The scikit-image algorithm probably returns a floating point image with values between 0 and 1. If you cast that to bool, you'll get all ones. You probably want
binimg = img_adapteq > 0.5
In general, also take note of the rescale_intensity function, which will take an image with values between 0 and 1 and return an image with values between 0 and 255.
from skimage import exposure
image = rescale_intensity(image, out_range=(0, 255))

Categories

Resources