I have an image that has a stain (the green stain on the surface of the water), and my goal is to extract that stain. It was guiding me with this solution but it doesn't extract it properly. Is there any way to extract using Python?
Input image:
The attempt:
img = cv2.imread('/content/001.jpg')
# blur
blur = cv2.GaussianBlur(img, (3,3), 0)
# convert to hsv and get saturation channel
sat = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)[:,:,1]
# threshold saturation channel
thresh = cv2.threshold(sat, 90, 255, cv2.THRESH_BINARY)[1]
# apply morphology close and open to make mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)
mask = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel, iterations=1)
# do OTSU threshold to get circuit image
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# write black to otsu image where mask is black
otsu_result = otsu.copy()
otsu_result[mask==0] = 0
# write black to input image where mask is black
img_result = img.copy()
img_result[mask==0] = 0
The result:
I followed your approach but used the LAB color space instead.
img = cv2.imread(image_path)
# blur
blur = cv2.GaussianBlur(img, (3,3), 0)
# convert to LAB space and get a-channel
lab = cv2.cvtColor(blur, cv2.COLOR_BGR2LAB)
a = lab[:,:,1]
thresh = cv2.threshold(a, 95, 255, cv2.THRESH_BINARY_INV)[1]
thresh = cv2.threshold(lab[:,:,1], 95, 255, cv2.THRESH_BINARY)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel, iterations=1)
inv_morph = cv2.bitwise_not(morph)
img_result = img.copy()
img_result[inv_morph==0] = 0
cv2.imshow('Final result', img_result)
The result is not accurate. You can alter the threshold value and/or morphological kernel to get the desired result.
Related
I have the following image:
I want to extract the boxed diagrams as so:
Here's what I've attempted:
import cv2
import matplotlib.pyplot as plt
# Load the image
image = cv2.imread('diagram.jpg')
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply thresholding to create a binary image
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
# Find contours
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Draw the contours
cv2.drawContours(image, contours, -1, (0, 0, 255), 2)
# Show the final image
plt.imshow(image), plt.show()
However, I've realized it'll be difficult to extract the diagrams because the contours aren't closed:
I've tried using morphological closing to close the gaps in the box edges:
# Define a rectangular kernel for morphological closing
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# Perform morphological closing to close the gaps in the box edges
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
But this changes almost nothing. How should I approach this problem?
We may replace morphological closing with dilate then erode, but filling the contours between the dilate and erode.
For filling the gaps, the kernel size should be much larger than 5x5 (I used 51x51).
Assuming the handwritten boxes are colored, we may convert from BGR to HSV, and apply the threshold on the saturation channel of HSV:
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # Convert from BGR to HSV color space
gray = hsv[:, :, 1] # Use saturation from HSV channel as "gray".
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) # Apply automatic thresholding (use THRESH_OTSU).
Apply dilate with large kernel, and use drawContours for filling the contours:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (51, 51)) # Use relatively large kernel for closing the gaps
dilated = cv2.dilate(thresh, kernel) # Dilate with large kernel
contours, hierarchy = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(dilated, contours, -1, 255, -1)
Apply erode after filling the contours
Erode after dilate is equivalent to closing, but here we are closing after filling.
closed = cv2.erode(dilated, kernel)
Code sample:
import cv2
import numpy as np
# Load the image
image = cv2.imread('diagram.png')
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # Convert from BGR to HSV color space
# Convert to grayscale
#gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = hsv[:, :, 1] # Use saturation from HSV channel as "gray".
# Apply thresholding to create a binary image
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) # Apply automatic thresholding (use THRESH_OTSU).
thresh = np.pad(thresh, ((100, 100), (100, 100))) # Add zero padding (required due to large dilate kernels).
# Define a rectangular kernel for morphological operations.
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (51, 51)) # Use relatively large kernel for closing the gaps
dilated = cv2.dilate(thresh, kernel) # Dilate with large kernel
# Fill the contours, before applying erode.
contours, hierarchy = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(dilated, contours, -1, 255, -1)
closed = cv2.erode(dilated, kernel) # Apply erode after filling the contours.
closed = closed[100:-100, 100:-100] # Remove the padding.
# Find contours
contours, hierarchy = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Draw the contours
cv2.drawContours(image, contours, -1, (255, 0, 0), 2)
# Show images for testing
# plt.imshow(image), plt.show()
cv2.imshow('gray', gray)
cv2.imshow('thresh', thresh)
cv2.imshow('dilated', dilated)
cv2.imshow('closed', closed)
cv2.imshow('image', image)
cv2.waitKey()
cv2.destroyAllWindows()
Result:
gray (saturation channel):
thresh:
dilated (after filling):
closed:
Just need to dilate the image to make the rectangle closed, then define a threshold for the area of the contours:
import cv2
# Load the image
image = cv2.imread('diagram.jpg')
# Convert to grayscale
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# Apply thresholding to create a binary image
ret,thresh = cv2.threshold(gray,200,255,1)
# Need to dilate the image to make the contours closed
dilate = cv2.dilate(thresh,None)
erode = cv2.erode(dilate,None)
# Find contours
contours,hierarchy = cv2.findContours(erode,cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
for i,cnt in enumerate(contours):
# Check if it is an external contour and its area is more than 8000
if hierarchy[0,i,3] == -1 and cv2.contourArea(cnt)>8000:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imwrite('template {0}.jpg'.format(i), image[y:y+h,x:x+w])
cv2.imshow('img',image)
You will get :
I'm new to image processing. I have the following image :
I'm trying to get the contours shown in the green area (bottom of picture) but I only detect the large blue form. I tried to skeleton the forms but without success and this is the "best" result I could get. How can I achieve that ?
import cv2
import numpy as np
im = cv2.imread('picture.jpg', cv2.IMREAD_UNCHANGED)
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(imgray, 127, 255, 0)
cv2.imshow("Thresh", thresh)
cv2.waitKey(0)
kernel = np.ones((15,15),np.uint8)
opening = cv2.morphologyEx(imgray, cv2.MORPH_OPEN, kernel)
gradient = cv2.morphologyEx(opening, cv2.MORPH_GRADIENT, kernel)
opening1 = cv2.morphologyEx(gradient, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(opening1, cv2.MORPH_CLOSE, kernel)
opening2 = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)
cv2.imshow("Closing", closing)
cv2.waitKey(0)
#Detecting contours
contours, hierarchy = cv2.findContours(image=opening2, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
cv2.drawContours(image=im, contours=contours, contourIdx=-1, color=(255, 255, 0), thickness=2, lineType=cv2.LINE_AA)
cv2.imshow("Image final", im)
cv2.waitKey(0)
I have a mammography image dataset (mini DDSM). These images show letter artifacts indicating left or right mamma and other useless information for my ML model, so I want to curate this dataset before training the model.
In this paper, Preprocessing of Digital Mammogram Image Based on
Otsu’s Threshold, they use Otsu's binarization and opening on the mammography to clean the image (page 5 of 10):
Their results
So far, I have coded this:
im = io.imread('/content/drive/MyDrive/TFM/DDSMPNG/ALL2/0.jpg')
# thresholding
thresh = im > filters.threshold_otsu(im)
# opening with a disk structure
disk = morphology.disk(5)
opened = morphology.binary_opening(thresh,disk)
# plotting
plt.figure(figsize=(10, 10))
plt.subplot(131)
plt.imshow(im,cmap='gray')
plt.subplot(132)
plt.imshow(opened,cmap='gray')
plt.imsave('/content/drive/MyDrive/TFM/DDSMPNG/Blackened/0.jpg',opened)
And these are the plots:
Results
I have also tried with a higher disk shape to do the opening, it seems to remove more white of the small letter artifact, but also crops a bit the mammography:
disk = morphology.disk(45)
opened = morphology.binary_opening(thresh,disk)
The result:
Result with disk shape (45,45)
I guess I will have to create some kind of mask with the binarization and apply it to the original image, but I am new to image processing libraries and I'm not sure how to achieve the results
EDIT 1: I tried #fmw42 suggestion and I have some issues with it (I work on Google Colab, dont know If it has something to do...):
First, with the image used as example on your code, it doesn't seem to work propperly, don't know why, I copied your code and just modified the path to the image as well as some subplots to see the results:
# read image
img = cv2.imread('/content/drive/MyDrive/TFM/DDSMPNG/ALL2/0.jpg')
hh, ww = img.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# apply otsu thresholding
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU )[1]
# apply morphology close to remove small regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# apply morphology open to separate breast from other regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
# get largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw largest contour as white filled on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, cv2.FILLED)
# dilate mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (55,55))
mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel)
# apply mask to image
result = cv2.bitwise_and(img, img, mask=mask)
# save results
cv2.imwrite('/content/drive/MyDrive/TFM/DDSMPNG/Blackened/0.jpg', result)
# show resultls
plt.figure(figsize=(10, 10))
plt.subplot(141)
plt.imshow(thresh,cmap='gray')
plt.subplot(142)
plt.imshow(morph,cmap='gray')
plt.subplot(143)
plt.imshow(mask,cmap='gray')
plt.subplot(144)
plt.imshow(result,cmap='gray')
Results:
Second, for the rest of the images, it seems to work well for most of them, but it crops a bit the breast surface:
In your result image, it seems to be much more smooth, how can I achieve that?
Thanks in advance!
EDIT 2: #fmw42 solution works fine, if someone has the same issue, you only need to play with the kernel sizes of the morphological filters until the image behaves like his results on the answer.
Thank you so much!
Here is one way to process your image in Python/OpenCV.
- Read the input
- Convert to grayscale
- Otsu threshold
- Morphology processing
- Get largest contour from external contours
- Draw all contours as white filled on a black background except the largest as a mask and invert mask
- Apply the mask to the input image
- Save the results
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("mammogram.png")
hh, ww = img.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# apply otsu thresholding
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU )[1]
# apply morphology close to remove small regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# apply morphology open to separate breast from other regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
# apply morphology dilate to compensate for otsu threshold not getting some areas
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (29,29))
morph = cv2.morphologyEx(morph, cv2.MORPH_DILATE, kernel)
# get largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
big_contour_area = cv2.contourArea(big_contour)
# draw all contours but the largest as white filled on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
for cntr in contours:
area = cv2.contourArea(cntr)
if area != big_contour_area:
cv2.drawContours(mask, [cntr], 0, 255, cv2.FILLED)
# invert mask
mask = 255 - mask
# apply mask to image
result = cv2.bitwise_and(img, img, mask=mask)
# save results
cv2.imwrite('mammogram_thresh.jpg', thresh)
cv2.imwrite('mammogram_morph.jpg', morph)
cv2.imwrite('mammogram_mask.jpg', mask)
cv2.imwrite('mammogram_result.jpg', result)
# show resultls
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('mask', mask)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Thresholded Image:
Morphology Processed Image:
Mask Image From Contours:
Result Image:
Alternate
- Read the input
- Convert to grayscale
- Otsu threshold
- Morphology processing
- Get largest contour from external contours
- Draw largest as white filled on black background as a mask
- Dilate mask
- Apply the mask to the input image
- Save the results
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("mammogram.png")
hh, ww = img.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# apply otsu thresholding
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU )[1]
# apply morphology close to remove small regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# apply morphology open to separate breast from other regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
# get largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw largest contour as white filled on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, cv2.FILLED)
# dilate mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (55,55))
mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel)
# apply mask to image
result = cv2.bitwise_and(img, img, mask=mask)
# save results
cv2.imwrite('mammogram_thresh.jpg', thresh)
cv2.imwrite('mammogram_morph2.jpg', morph)
cv2.imwrite('mammogram_mask2.jpg', mask)
cv2.imwrite('mammogram_result2.jpg', result)
# show resultls
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('mask', mask)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology Processed Image:
Mask Image:
Result:
ADDITION
Here is the second method of processing applied to your larger JPG image. I noted that it was about 6x in width and height. So I increased the morphology kernels by about 6x from 5 to 31. I also trimmed the image borders 40 pixels all around and then added back a black border of the same amounts.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("mammogram.jpg")
hh, ww = img.shape[:2]
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# shave 40 pixels all around
gray = gray[40:hh-40, 40:ww-40]
# add 40 pixel black border all around
gray = cv2.copyMakeBorder(gray, 40,40,40,40, cv2.BORDER_CONSTANT, value=0)
# apply otsu thresholding
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU )[1]
# apply morphology close to remove small regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (31,31))
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# apply morphology open to separate breast from other regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (31,31))
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
# get largest contour
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw largest contour as white filled on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, 255, cv2.FILLED)
# dilate mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (305,305))
mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel)
# apply mask to image
result = cv2.bitwise_and(img, img, mask=mask)
# save results
cv2.imwrite('mammogram_thresh.jpg', thresh)
cv2.imwrite('mammogram_morph2.jpg', morph)
cv2.imwrite('mammogram_mask2.jpg', mask)
cv2.imwrite('mammogram_result2.jpg', result)
# show resultls
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('mask', mask)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology Image:
Mask Image:
Result:
I want to automatically remove the background from images using OpenCV. The code I use generally works well, but for some images with similar background color to the foreground (e.g. white images with gray shadows on gray background), the code also removes that section of the image.
For example, for this image:
target image to be processed
I get the following result:
image result
This is the code I use (source):
img = cv2.imread(imgpath)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
thresh = 255 - thresh
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, borderType=cv2.BORDER_CONSTANT, borderValue=0)
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
sorted_contour = sorted(contours, key=cv2.contourArea, reverse=True)
contour = np.zeros_like(gray)
cv2.drawContours(contour, [sorted_contour[0]], 0, 255, -1)
if len(sorted_contour) > 1:
cv2.drawContours(contour, [sorted_contour[1]], 0, 255, -1)
blur = cv2.GaussianBlur(contour, (5,5), sigmaX=0, sigmaY=0, borderType = cv2.BORDER_DEFAULT)
mask = skimage.exposure.rescale_intensity(blur, in_range=(127.5,255), out_range=(0,255))
result = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
result[:,:,3] = mask
Any idea on how to modify the code so that it includes the "holes" in the dress, or to "patch up" the mask automatically?
Thanks in advance.
I'm trying to detect the black spots on the following image.
I use adaptive thresholding and use find contours in opencv. This method is successful for detecting most of the black spots inside the gray background. However, it's not able to detect the spots on the edges, simply because contour detection thinks the spots are part of the black background, see here:
Here is the code I used to get these contours:
import cv2
image_path = "cropped.png"
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# do adaptive threshold on gray image
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 101, 3)
# apply morphology open then close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (1, 1))
blob = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
blob = cv2.morphologyEx(blob, cv2.MORPH_CLOSE, kernel)
# invert blob
blob = (255 - blob)
# Get contours
cnts, hierarchy = cv2.findContours(blob, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
result1 = img.copy()
cv2.drawContours(result1, cnts, -1, (0, 0, 255), 3)
cv2.imwrite("_Fail_Blob.png", result1)
Any suggestions on how to detect the black spots on the edges? Eventually looking for an algorithm to be able to output sth like the following:
You can use morphological operations for select spot:
By example:
import cv2
fn = 'IdTPp.jpg'
img = cv2.imread(fn)
img=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
se=cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (45,45))
img2=cv2.morphologyEx(img, cv2.MORPH_CLOSE, se)
img3=cv2.absdiff(img, img2)
cv2.imshow("detected circles", img3)