OpenCV - Morphological Opening not idempotent - python

EDIT: In the initial question the color map was reversed which caused some confusion, I changed the images and code to match so standard behavior.
Morphological opening and closing are idempotent operations (see Univ. Auckland):
Opening is an idempotent operation: once an image has been opened,
subsequent openings with the same structuring element have no further effect on that image:
(f ∘ s) ∘ s = f ∘ s.
I have the following image:
When trying to remove the skinny rectangles on the top right using OpenCV, I noticed that when I apply Opening iteratively with the same kernel, I get the desired result. To my understanding of how Opening/Closing works this should not be the case. Did I misunderstand anything or is there something wrong with the implementation in OpenCV?
Here is my example code and the results:
import cv2 as cv
import numpy as np
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
matplotlib.rc('image', cmap='gray')
# load image
img = cv.imread("image.jpg", 0)
# apply binary thresholding to circumvent jpg compression
img = (img > 128).astype(np.uint8)
# adding a border around the image solves the issue
# img = cv.copyMakeBorder(img, 1, 1, 1, 1, cv.BORDER_CONSTANT)
kernel = cv.getStructuringElement(cv.MORPH_RECT,(20,5))
A = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)
B = cv.morphologyEx(A, cv.MORPH_OPEN, kernel)
C = cv.morphologyEx(B, cv.MORPH_OPEN, kernel)
f, ax = plt.subplots(1,3)
ax[0].imshow(A)
ax[0].set_title("A")
ax[1].imshow(B)
ax[1].set_title("B")
ax[2].imshow(C)
ax[2].set_title("C")
plt.show()
Here the result, notice the removed rectangles on the right border after each iteration:

Related

Generate noiseless binary image and skeleton from image

I have a overlapping filaments image. I am interested in generating the noiseless binary image and subsequently use it for skeleton generation. I have tried different ways to get skeleton but not able to succeed. Below find the code written in python for the same and attached skeleton image generated through it. It would be great if anyone help in solving issue.
Original vs Skeleton image:
import cv2
import numpy as np
from skimage import morphology, graph
from skan import Skeleton
from skimage.morphology import skeletonize
import matplotlib.pyplot as plt
img00 = cv2.imread(r'img_test.jpg')
img01 = cv2.cvtColor(img00, cv2.COLOR_BGR2GRAY)
cv2.imshow('1',img01)
img02 = cv2.adaptiveThreshold(img01,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,5,5)
cv2.imshow('2',img02)
i_size = min(np.size(img02,1),600) # image size for imshow
kernel = np.ones((2, 2), np.uint8) # Creating kernel
# Using cv2.erode() method
img_erosion = cv2.erode(img02, kernel, borderType = cv2.BORDER_REFLECT, iterations=1, borderValue = 1)
filterSize =(5,5)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, filterSize)
tophat_img = cv2.morphologyEx(img_erosion, cv2.MORPH_BLACKHAT, kernel)
img03 = cv2.bitwise_not(tophat_img)
cv2.imshow('3',img03)
kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(2,2))
img04 = cv2.morphologyEx(img03, cv2.MORPH_CLOSE, kernel1)
img04 = cv2.morphologyEx(img04, cv2.MORPH_OPEN, kernel1)
cv2.imshow('4',img04)
thresh = (img04/255).astype(np.uint8)
# skeleton based on default method
skeleton1 = skeletonize(thresh)
skeleton2 = (skeleton1*255).astype(np.uint8)
cv2.imshow('5',skeleton2)
# Avg diameter calculation
diameter = np.sum(thresh)/np.sum(skeleton1)
print('diameter',diameter)
cv2.waitKey(0) & 0xFF == ord('q')
cv2.destroyAllWindows()
Straight binarization followed by morphological closing can give interesting results, though they are sensitive to parameters.

Using OpenCV Hough Tranform for line detection in 2D point cloud

I have tried my best to find out how to use OpenCV for line detection. However, I cannot find the examples that I'm looking for. I want to use it to find lines in simple 2-d point clouds. As a test I want to use the following points:
import random
import numpy as np
import matplotlib.pyplot as plt
a = np.random.randint(1,101,400) # Random points.
b = np.random.randint(1,101,400) # Random points.
for i in range(0, 90, 2): # A line to detect
a = np.append(a, [i+5])
b = np.append(b, [0.5*i+30])
plt.plot(a, b, '.')
plt.show()
I have found a lot of initial examples of how the Hough Tranform works. However, when it comes to code examples, I can only find that images have been used.
Is there a way to use the OpenCV Hough Transform to detect the line in a set of points, or can you recommend any other methods or libraries?
---- Edit ----
After reading some great ansewers I feel like I scould discribe what i intent to use it for a little bit better. I have a high resolution 2D LiDAR and need to extract walls from the data. A typicle scan can look like this:
Where the "correct output" would look something like this:
After I have done some more research I suspect that the Hough transform is less than optimal to use in this case. Any tips on what i should look for?
(If anyone is interested, the LiDAR and wall extraction is used to generate a map and navigate a robot.)
Thanks, Jakob
One way would be to implement Hough Transformation yourself following these slides skipping the Edge Detection part.
Alternatively you could create an image from your list of points such as
#create an image from list of points
x_shape = int(np.max(a) - np.min(a))
y_shape = int(np.max(b) - np.min(b))
im = np.zeros((x_shape+1, y_shape+1))
indices = np.stack([a-1,b-1], axis =1).astype(int)
im[indices[:,0], indices[:,1]] = 1
plt.imshow(im)
#feed to opencv as usual
following the answer to this question
EDIT: Do not feed to OpenCV but use instead skimage such as described here in the documentation:
import numpy as np
from skimage.transform import (hough_line, hough_line_peaks,
probabilistic_hough_line)
from skimage.feature import canny
from skimage import data
import matplotlib.pyplot as plt
from matplotlib import cm
# Constructing test image
#image = np.zeros((100, 100))
#idx = np.arange(25, 75)
#image[idx[::-1], idx] = 255
#image[idx, idx] = 255
image = im
# Classic straight-line Hough transform
h, theta, d = hough_line(image)
# Generating figure 1
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
ax = axes.ravel()
ax[0].imshow(image, cmap=cm.gray)
ax[0].set_title('Input image')
ax[0].set_axis_off()
ax[1].imshow(np.log(1 + h),
extent=[np.rad2deg(theta[-1]), np.rad2deg(theta[0]), d[-1], d[0]],
cmap=cm.gray, aspect=1/1.5)
ax[1].set_title('Hough transform')
ax[1].set_xlabel('Angles (degrees)')
ax[1].set_ylabel('Distance (pixels)')
ax[1].axis('image')
ax[2].imshow(image, cmap=cm.gray)
for _, angle, dist in zip(*hough_line_peaks(h, theta, d)):
y0 = (dist - 0 * np.cos(angle)) / np.sin(angle)
y1 = (dist - image.shape[1] * np.cos(angle)) / np.sin(angle)
ax[2].plot((0, image.shape[1]), (y0, y1), '-r')
ax[2].set_xlim((0, image.shape[1]))
ax[2].set_ylim((image.shape[0], 0))
ax[2].set_axis_off()
ax[2].set_title('Detected lines')
plt.tight_layout()
plt.show()
# Line finding using the Probabilistic Hough Transform
image = data.camera()
edges = canny(image, 2, 1, 25)
lines = probabilistic_hough_line(edges, threshold=10, line_length=5,
line_gap=3)
# Generating figure 2
fig, axes = plt.subplots(1, 3, figsize=(15, 5), sharex=True, sharey=True)
ax = axes.ravel()
ax[0].imshow(image, cmap=cm.gray)
ax[0].set_title('Input image')
ax[1].imshow(edges, cmap=cm.gray)
ax[1].set_title('Canny edges')
ax[2].imshow(edges * 0)
for line in lines:
p0, p1 = line
ax[2].plot((p0[0], p1[0]), (p0[1], p1[1]))
ax[2].set_xlim((0, image.shape[1]))
ax[2].set_ylim((image.shape[0], 0))
ax[2].set_title('Probabilistic Hough')
for a in ax:
a.set_axis_off()
plt.tight_layout()
plt.show()
Here's an approach
Convert image to grayscale
Threshold to obtain binary image
Perform morphological operations to connect contours and smooth image
Find lines
After converting to grayscale, we threshold image to obtain a binary image
import cv2
import numpy as np
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
Next we perform morphological operations to connect contours
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
Finally we find the line using cv2.HoughLinesP()
minLineLength = 550
maxLineGap = 70
lines = cv2.HoughLinesP(close,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(image,(x1,y1),(x2,y2),(36,255,12),3)
Instead of using cv2.HoughLinesP(), an alternative method is to find contours and filter using cv2.contourArea(). The largest contour will be our line.
Full code
import cv2
import numpy as np
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
minLineLength = 550
maxLineGap = 70
lines = cv2.HoughLinesP(close,1,np.pi/180,100,minLineLength,maxLineGap)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(image,(x1,y1),(x2,y2),(36,255,12),3)
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('image', image)
cv2.waitKey()
You most likely won't be able to use Hough transform to detect lines in a set of points. Hough transform works with images. Better yet, binarized images with edges marked as 1 and background stays as 0. So, forget about the Hough transform.
For your particular case I'd suggest some kind of RANSAC algorithm, which looks for specific points following some rules, ignoring everything else. Though, in your case you have a lot (=too much) noise. If you can keep the noise points below 50%, RANSAC will do the trick. You may read the details here: OpenCV - Ransac fitting line
Or here's the Wiki with the most generic explanation: https://en.wikipedia.org/wiki/RANSAC
After spending some (alot) of time with the problem I eventually reached a solution that I was sattisfied with.
My solution is to go through the scan data as it is read (as a revolving scanner) and iteratively look at smalles sections of the data, then running a custom ransac algorithm to fit a line to the current segment.
Then if the segment satisfies the critera for a possible line the segment is extended and checked again. This is then repeated for all small segments of data in different ways. In short, I used a custom, self written from scratch, iterative ransac line fit.
If we take a similar example to what I initially gave:
The following result is now generated by the algorith:
And comparing to the actual (wall) map of the enviroment we can see the following comparison:
Which I would say is good enough. Another important note is that the algorith can be run for multiple scans of data with the enviroment naturally chaning slightly inbetween all of the scans (taking quite some time to execute):
As can bee seen there are some extra walls that are not a part of the map, which is to be expected and also some faulty findings that can be filtered since they are clear outliers.
Now for the code.... The final solution is a 300 lines long python script converted though a .pyx file and compiled using Cython in order to improve time complexity. If the code or maybe more importantly a psuedo code (since my implementation is tweeked to my specfic need) is wanted I can provide it given that someone will enjoy using/reading it :)

How do I make a mask from one image and then transfer it to another?

I'm trying to solve a homework problem where I need to get a mask from one image (DAPI) and then apply it to the second image (NPM1) of cells (they are the same cells in the exact same location)
I've been running in circles for about 4 hours trying to get the mask applied using a True/False approach but it doesn't seem to work. I've tried and failed with a bunch of other approaches but just pasting the one that I thought would most likely work (I'm super new to coding)
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from skimage.color import rgb2gray
import cv2
#Load the images
image = np.array(Image.open("NOTREATDAPI.jpg"))
image1 = np.array(Image.open("NOTREATNPM1.jpg"))
No_Treat_DAPI = rgb2gray(image)
No_Treat_NPM1 = rgb2gray(image1)
plt.imshow(image)
#Create a mask using the DAPI image
arr = np.array(No_Treat_DAPI)
DAPI_stain = arr[:,0] > 25
plt.imshow(arr)
The DAPI image:
The NPM1 image:
I'm trying to only get the regions on the original image that have an intensity of 25 or greater so that all of the black space in the isn't counted towards the mask as I'm trying to get a histogram of intensity of the cells in the NPM1 image.
I limited my solution to the use of OpenCV, numpy, and matplotlib.
The general approach is the following:
Load both images as grayscale images, see cv2.imread.
Create a binary mask from the DAPI image using binary thresholding at intensity value 25, see cv2.threshold.
Do some morphological opening to get rid of possible small artifacts, see cv2.morphologyEx and cv2.getStructuringElement.
Calculate the histogram of the NPM1 image, only incorporating the masked pixels, see cv2.calcHist.
Here's the complete code:
import cv2
import matplotlib.pyplot as plt
import numpy as np
# Load images as grayscale
dapi = cv2.imread('images/NOTREATDAPI.jpg', cv2.IMREAD_GRAYSCALE)
npm1 = cv2.imread('images/NOTREATNPM1.jpg', cv2.IMREAD_GRAYSCALE)
# Create a mask using the DAPI image and binary thresholding at 25
_, mask = cv2.threshold(dapi, 25, 255, cv2.THRESH_BINARY)
# Do some morphological opening to get rid of small artifacts
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15)))
# Calculate the histogram using the NPM1 image and the obtained binary mask
hist = cv2.calcHist([npm1], [0], mask, [256], [0, 256])
# Show bar plot of calculated histogram
plt.bar(np.arange(256), np.squeeze(hist))
plt.show()
# Show mask image
cv2.imshow('Mask', mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
The mask then looks like this:
And, the histogram might look like this:
Hope that helps!
P.S. Next time, better use the opencv and python tags instead of only using the cv2 tag. You'll reach way more people.

Thinning/Skeletenization is distorting my image

I am trying to thin this image but it keeps getting distorted.
This is my relevant code for applying the thinning. I have also tried the 'thin' function instead of 'skeletonize' but the results are similar.
from skimage.morphology import skeletonize, thin
new_im = cv2.imread(im_pth)
gray = cv2.cvtColor(new_im, cv2.COLOR_BGR2GRAY)
ske = (skeletonize(gray//255) * 255).astype(np.uint8)
cv2.imshow("image", gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
My goal is to get a shape similar to this after thinning:
What am I doing wrong? I have read online that sometimes jpg files cause issues however I don't have the experience in this field to confirm that.
I'm not sure if your conversion from input image to binary is correct. Here's a version using scikit-image functions that seems to do what you want:
from skimage import img_as_float
from skimage import io, color, morphology
import matplotlib.pyplot as plt
image = img_as_float(color.rgb2gray(io.imread('char.png')))
image_binary = image < 0.5
out_skeletonize = morphology.skeletonize(image_binary)
out_thin = morphology.thin(image_binary)
f, (ax0, ax1, ax2) = plt.subplots(1, 3, figsize=(10, 3))
ax0.imshow(image, cmap='gray')
ax0.set_title('Input')
ax1.imshow(out_skeletonize, cmap='gray')
ax1.set_title('Skeletonize')
ax2.imshow(out_thin, cmap='gray')
ax2.set_title('Thin')
plt.savefig('/tmp/char_out.png')
plt.show()
From your example, and since your image is binary, I think that what you want to do is better achieved via (binary) erosion. Wikipedia explains the concept well. Intuitively (in case you don't have time to read the wikipedia link), imagine you have a binary image A, like the one you have given, and let's call A_1 the set of pixels of A that have a value of 1. Then, you define a "structuring element" K, which for example can be a square patch of size n*n. Then in pseudocode
for pixel in A_1:
center K at pixel, and call this centered version K_pixel
if(K_pixel is contained in A_1):
keep pixel
else:
discard pixel
So, this has the effect of thinning the connected component in your image.
This function is standard and is implemented in opencv, here are some python examples, and here is a link to the documentation (c++).

medical image segmentation with cv2

I am using the MIAS data set of breast cancer mammography pictures. The data is available here:
http://peipa.essex.ac.uk/pix/mias/
for example, an image looks like this:
import cv2
import numpy as np
img = cv2.imread("mdb168.pgm",0)
import matplotlib.pyplot as plt
plt.imshow(img, cmap="gray")
I want to remove all artifacts and unnecessary parts of the image.
To do this,I first binarize the image
ret,thresh1 = cv2.threshold(img,0,255,cv2.THRESH_BINARY)
plt.imshow(thresh1, cmap="gray")
use opening
kernel = np.ones((20,20),np.uint8)
opening = cv2.morphologyEx(thresh1, cv2.MORPH_OPEN, kernel)
plt.imshow(opening, cmap="gray")
then erosion
kernel = np.ones((120,120),np.uint8)
erosion = cv2.erode(opening,kernel,iterations = 1)
plt.imshow(erosion, cmap="gray")
then merge this mask with the original image
merged = cv2.bitwise_and(img, img , mask=erosion)
plt.imshow(merged, cmap="gray")
I am now trying to remove the pectoral muscle in the upper left area.
In this publication: https://www.ncbi.nlm.nih.gov/pubmed/26742491
they use the exact same data set and do this with `seeded region growing'.
However, there is no code provided and I could not find this in opencv.
I could achieve a similar result by doing dilate/erosion etc again, but I'm looking for a more generalizable solution.
Also, some of these images do not show a muscle and this should be detected as well.
I would use the following approach:
(optional) I would replace the opening and the erosion with an opening by reconstruction <=> erosion followed by a geodesic dilation. It will preserve the original shape, and then you will keep a bigger ROI.
Convolution filter (gaussian or simple average) to smooth the image
Big white top-hat in order to detect the bright zone.
Then you subtract the top-hat result to the original image.

Categories