How to remove mammography tag artifacts - python

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:

Related

Detecting handwritten boxes using OpenCV

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 :

Detect thick black lines in image with OpenCV

I have the following image of a lego board with some bricks on it
Now I am trying to detect the thick black lines (connecting the white squares) with OpenCV. I have already experimented a lot with HoughLinesP, converted the image to gray or b/w before, applied blur, ...
Nonthing led to usable results.
# Read image
img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
# Resize Image
img = cv2.resize(img, (0,0), fx=0.25, fy=0.25)
# Initialize output
out = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# Median blurring to get rid of the noise; invert image
img = cv2.medianBlur(img, 5)
# Adaptive Treshold
bw = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,15,8)
# HoughLinesP
linesP = cv2.HoughLinesP(bw, 500, np.pi / 180, 50, None, 50, 10)
# Draw Lines
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
cv2.line(out, (l[0], l[1]), (l[2], l[3]), (0,0,255), 3, cv2.LINE_AA)
The adaptive treshold lets you see edges quite well, but with HoughLinesP you don't get anything usable out of it
What am I doing wrong?
Thanks, both #fmw42 and #jeru-luke for your great solutions to this problem! I liked isolating / masking the green board, so I combined both:
import cv2
import numpy as np
img = cv2.imread("image.jpg")
scale_percent = 50 # percent of original size
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
dim = (width, height)
# resize image
img = cv2.resize(img, dim, interpolation = cv2.INTER_AREA)
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
a_component = lab[:,:,1]
# binary threshold the a-channel
th = cv2.threshold(a_component,127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
# numpy black
black = np.zeros((img.shape[0],img.shape[1]),np.uint8)
# function to obtain the largest contour in given image after filling it
def get_region(image):
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
c = max(contours, key = cv2.contourArea)
mask = cv2.drawContours(black,[c],0,255, -1)
return mask
mask = get_region(th)
# turning the region outside the green block white
green_block = cv2.bitwise_and(img, img, mask = mask)
green_block[black==0]=(255,255,255)
# median blur
median = cv2.medianBlur(green_block, 5)
# threshold on black
lower = (0,0,0)
upper = (15,15,15)
thresh = cv2.inRange(median, lower, upper)
# apply morphology open and close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (29,29))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# filter contours on area
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = green_block.copy()
for c in contours:
area = cv2.contourArea(c)
if area > 1000:
cv2.drawContours(result, [c], -1, (0, 0, 255), 2)
# view result
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here I am presenting a repeated segmentation approach using color.
This answer is based on the usage of LAB color space
1. Isolating the green lego block
img = cv2.imread(image_path)
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
a_component = lab[:,:,1]
# binary threshold the a-channel
th = cv2.threshold(a_component,127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
th
# function to obtain the largest contour in given image after filling it
def get_region(image):
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
c = max(contours, key = cv2.contourArea)
black = np.zeros((image.shape[0], image.shape[1]), np.uint8)
mask = cv2.drawContours(black,[c],0,255, -1)
return mask
mask = get_region(th)
mask
# turning the region outside the green block white
green_block = cv2.bitwise_and(img, img, mask = mask)
green_block[black==0]=(255,255,255)
green_block
2. Segmenting the road
To get an approximate region of the road, I subtracted the mask and th.
cv2.subtract() performs arithmetic subtraction, where cv2 will take care of negative values.
road = cv2.subtract(mask,th)
# `road` contains some unwanted spots/contours which are removed using the function "get_region"
only_road = get_region(road)
only_road
Masking only the road segment with the original image gives
road_colored = cv2.bitwise_and(img, img, mask = only_road)
road_colored[only_road==0]=(255,255,255)
road_colored
From the above image only the black regions (road) are present, which is easy to segment:
# converting to grayscale and applying threshold
th2 = cv2.threshold(road_colored[:,:,1],127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
# using portion of the code from fmw42's answer, to get contours above certain area
contours = cv2.findContours(th2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
for c in contours:
area = cv2.contourArea(c)
if area > 1000:
cv2.drawContours(result, [c], -1, (0, 0, 255), 4)
result
Note:
To clean up the end result, you can apply morphological operations on th2 before drawing contours.
Here is one way to do that in Python/OpenCV.
Read the image
Apply median blur
Threshold on black color using cv2.inRange()
Apply morphology to clean it up
Get contours and filter on area
Draw contours on input
Save the result
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('black_lines.jpg')
# median blur
median = cv2.medianBlur(img, 5)
# threshold on black
lower = (0,0,0)
upper = (15,15,15)
thresh = cv2.inRange(median, lower, upper)
# apply morphology open and close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (29,29))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# filter contours on area
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
for c in contours:
area = cv2.contourArea(c)
if area > 1000:
cv2.drawContours(result, [c], -1, (0, 0, 255), 2)
# save result
cv2.imwrite("black_lines_threshold.jpg", thresh)
cv2.imwrite("black_lines_morphology.jpg", morph)
cv2.imwrite("black_lines_result.jpg", result)
# view result
cv2.imshow("threshold", thresh)
cv2.imshow("morphology", morph)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold image:
Morphology image:
Result:

Detect squares (paintings) in images and draw contour around them using python

I'm trying to detect and draw a rectangular contour on every painting on for example this image:
I followed some guides and did the following:
Grayscale conversion
Applied median blur
Sharpen image
Applied adaptive Threshold
Applied Morphological Gradient
Find contours
Draw contours
And got the following result:
I know it's messy but is there a way to somehow detect and draw a contour around the paintings better?
Here is the code I used:
path = '<PATH TO THE PICTURE>'
#reading in and showing original image
image = cv2.imread(path)
image = cv2.resize(image,(880,600)) # resize was nessecary because of the large images
cv2.imshow("original", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
# grayscale conversion
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("painting_gray", gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
# we need to find a way to detect the edges better so we implement a couple of things
# A little help was found on stackoverflow: https://stackoverflow.com/questions/55169645/square-detection-in-image
median = cv2.medianBlur(gray,5)
cv2.imshow("painting_median_blur", median) #we use median blur to smooth the image
cv2.waitKey(0)
cv2.destroyAllWindows()
# now we sharpen the image with help of following URL: https://www.analyticsvidhya.com/blog/2021/08/sharpening-an-image-using-opencv-library-in-python/
kernel = np.array([[0, -1, 0],
[-1, 5,-1],
[0, -1, 0]])
image_sharp = cv2.filter2D(src=median, ddepth=-1, kernel=kernel)
cv2.imshow('painting_sharpend', image_sharp)
cv2.waitKey(0)
cv2.destroyAllWindows()
# now we apply adapptive thresholding
# thresholding: https://opencv24-python-tutorials.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html#adaptive-thresholding
thresh = cv2.adaptiveThreshold(src=image_sharp,maxValue=255,adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
thresholdType=cv2.THRESH_BINARY,blockSize=61,C=20)
cv2.imshow('thresholded image', thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()
# lets apply a morphological transformation
kernel = np.ones((7,7),np.uint8)
gradient = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel)
cv2.imshow('dilated image', gradient)
cv2.waitKey(0)
cv2.destroyAllWindows()
# # lets now find the contours of the image
# # find contours: https://docs.opencv.org/4.x/dd/d49/tutorial_py_contour_features.html
contours, hierarchy = cv2.findContours(gradient, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print("contours: ", len(contours))
print("hierachy: ", len(hierarchy))
print(hierarchy)
cv2.drawContours(image, contours, -1, (0,255,0), 3)
cv2.imshow("contour image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
Tips, help or code is appreciated!
Here's a simple approach:
Obtain binary image. We load the image, grayscale, Gaussian blur, then Otsu's threshold to obtain a binary image.
Two pass dilation to merge contours. At this point, we have a binary image but individual separated contours. Since we can assume that a painting is a single large square contour, we can merge small individual adjacent contours together to form a single contour. To do this, we create a vertical and horizontal kernel using cv2.getStructuringElement then dilate to merge them together. Depending on the image, you may need to adjust the kernel sizes or number of dilation iterations.
Detect paintings. Now we find contours and filter using contour area using a minimum threshold area to filter out small contours. Finally we obtain the bounding rectangle coordinates and draw the rectangle with cv2.rectangle.
Code
import cv2
# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.jpeg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (13,13), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Two pass dilate with horizontal and vertical kernel
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,5))
dilate = cv2.dilate(thresh, horizontal_kernel, iterations=2)
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,9))
dilate = cv2.dilate(dilate, vertical_kernel, iterations=2)
# Find contours, filter using contour threshold area, and draw rectangle
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
area = cv2.contourArea(c)
if area > 20000:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(image, (x, y), (x + w, y + h), (36, 255, 12), 3)
cv2.imshow('thresh', thresh)
cv2.imshow('dilate', dilate)
cv2.imshow('image', image)
cv2.waitKey()
So here is the actual size of the portrait frame.
So here is small code.
#!/usr/bin/python 37
#OpenCV 4.3.0, Raspberry Pi 3/B/4B-w/4/8GB RAM, Buster,v10.
#Date: 3rd, June, 2020
import cv2
# Load the image
img = cv2.imread('portrait.jpeg')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(img, 120,890)
# Apply adaptive threshold
thresh = cv2.adaptiveThreshold(edged, 255, 1, 1, 11, 2)
thresh_color = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
# apply some dilation and erosion to join the gaps - change iteration to detect more or less area's
thresh = cv2.dilate(thresh,None,iterations = 50)
thresh = cv2.erode(thresh,None,iterations = 50)
# Find the contours
contours,hierarchy = cv2.findContours(thresh,
cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
# For each contour, find the bounding rectangle and draw it
for cnt in contours:
area = cv2.contourArea(cnt)
if area > 20000:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,
(x,y),(x+w,y+h),
(0,255,0),
2)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Here is output:

Image cropping on white boundaries from a Complex Image using python

Problem Summary: I have got many complex histopathology images with different dimensions. Complex means a single image having multiple images in it as shown in below input image examples. I need to separate out or crop each single image only and not the text/label/caption of it from that input complex image and further save each of them individually. For the bounding boxes I have gone through the white boundaries (separation) along the single images.
Complex Input Image Example 1:
Complex Input Image Example 2:
Complex Input Image Example 3:
Code I have tried:
import cv2
import numpy as np
# reading the input image
img = cv2.imread('cmp.jpg')
cv2.imshow("histology image", img)
# defining border color
lower = (0, 80, 110)
upper = (0, 120, 150)
# applying thresholding on border color
mask = cv2.inRange(img, lower, upper)
cv2.imshow("masked", mask)
# Using dilate threshold
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15))
mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel)
# coloring border to white for other images
img[mask==255] = (255,255,255)
cv2.imshow("white_border", img)
# converting image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# applying otsu threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU )[1]
cv2.imshow("thresholded", thresh)
# applying 'Open' morphological operation
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17,17))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
morph = 255 - morph
cv2.imshow("morphed", morph)
# finding contours and bounding boxes
bboxes = []
bboxes_img = img.copy()
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
x,y,w,h = cv2.boundingRect(cntr)
cv2.rectangle(bboxes_img, (x, y), (x+w, y+h), (0, 0, 255), 1)
bboxes.append((x,y,w,h))
cv2.imshow("boundingboxes", bboxes_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
I am not getting exact bounding boxes for each of single images present in the input complex image and further I need to save each cropped image individually. Any kind of help will be much appreciated.

Vessel segmentation in retina image

I am trying to trace the vessels in a retina image. Currently I am using cv2's threshold functions to make the vessels contrast more with the surrounding retina:
from matplotlib import pyplot as plt
import cv2
img = cv2.imread('misc images/eye.jpeg',0)
img = cv2.medianBlur(img,5)
ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]
for i in range(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
And this is the result:
All 3 methods still have a lot of background noise from the rest of the retina. What can I do to improve the accuracy of vessel tracing?
Here is another way to threshold your image using division normalization followed by filtering on contour area.
Read the input
Convert to gray
Apply morphology dilate (or apply gaussianBlur)
Divide input by dilated result
Threshold
Invert so vessels are white on black background
Find all contours
Filter contours to discard ones that are too small
Draw remaining contours in white on black image as mask
Apply mask to threshold image as first result
Apply mask to input as second result
Save results
Input
import cv2
import numpy as np
# read the image
img = cv2.imread('retina_eye.jpg')
# convert to gray
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# apply morphology
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (5,5))
morph = cv2.morphologyEx(gray, cv2.MORPH_DILATE, kernel)
# divide gray by morphology image
division = cv2.divide(gray, morph, scale=255)
# threshold
thresh = cv2.threshold(division, 0, 255, cv2.THRESH_OTSU )[1]
# invert
thresh = 255 - thresh
# find contours and discard contours with small areas
mask = np.zeros_like(thresh)
contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
area_thresh = 10000
for cntr in contours:
area = cv2.contourArea(cntr)
if area > area_thresh:
cv2.drawContours(mask, [cntr], -1, 255, 2)
# apply mask to thresh
result1 = cv2.bitwise_and(thresh, mask)
mask = cv2.merge([mask,mask,mask])
result2 = cv2.bitwise_and(img, mask)
# save results
cv2.imwrite('retina_eye_division.jpg',division)
cv2.imwrite('retina_eye_thresh.jpg',thresh)
cv2.imwrite('retina_eye_mask.jpg',mask)
cv2.imwrite('retina_eye_result1.jpg',result1)
cv2.imwrite('retina_eye_result2.jpg',result2)
# show results
cv2.imshow('morph', morph)
cv2.imshow('division', division)
cv2.imshow('thresh', thresh)
cv2.imshow('mask', mask)
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Division image:
Threshold image:
Mask image:
Result 1:
Result 2:

Categories