How to remove scratches from metallographic images using Python - python

I have the following image to analyze and extract the porosity out of it (black dots).
My issue is that my code might be picking up on the scratches (defects from polishing the cross-sections).
I am looking for a way to remove the scratches.
I tried Inpainting (https://github.com/Chandrika372/Image-Restoration-by-Inpainting-Algorithm/blob/main/Inpainting.py) but the scratches are too small for detection. I also found few examples running on C and with Matlab, but if I use my image as an input, the output is still the same; the scratches seem too fine for the algos.
Any help would be appreciated.

A simple Otsu's threshold should do it. The idea is to Gaussian blur to remove noise then cv2.bitwise_and to extract the dots.
Binary image
Result
You can also optionally filter out large/small dots using contour area filtering with a threshold value
import cv2
# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread("1.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# OPTIONAL to filter out small/large dots using contour area filtering
# Adjust the area to only keep larger dots
'''
DOT_AREA = 10
cnts = cv2.findContours(thresh, 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 < DOT_AREA:
cv2.drawContours(thresh, [c], -1, 0, -1)
'''
# Bitwise_and to extract dots
result = cv2.bitwise_and(image, image, mask=thresh)
result[thresh==0] = 255
cv2.imshow("thresh", thresh)
cv2.imshow("result", result)
cv2.waitKey()

Related

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:

Detect and replace region from one image onto another image with OpenCV

I have two pictures of the same dimension and I want to detect and replace the white region in the first picture (black image) at the same location in the second picture. Is there any way to do this using OpenCV? I want to replace the blue region in the original image with the white region in the first picture.
First picture
Original image
If I'm understanding you correctly, you want to replace the white ROI on the black image onto the original image. Here's a simple approach:
Obtain binary image. Load image, grayscale, Gaussian blur, then Otsu's threshold
Extract ROI and replace. Find contours with cv2.findContours then filter using contour approximation with cv2.arcLength and cv2.approxPolyDP. With the assumption that the region is a rectangle, if the contour approximation result is 4 then we have found our desired region. In addition, we filter using cv2.contourArea to ensure that we don't include noise. Finally we obtain the bounding box coordinates with cv2.boundingRect and extract the ROI with Numpy slicing. Finally we replace the ROI into the original image.
Detected region to extract/replace highlighted in green
Extracted ROI
Result
Code
import cv2
# Load images, grayscale, Gaussian blur, Otsu's threshold
original = cv2.imread('1.jpg')
image = cv2.imread('2.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Find contours, filter using contour approximation + area, then extract
# ROI using Numpy slicing and replace into original image
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.015 * peri, True)
area = cv2.contourArea(c)
if len(approx) == 4 and area > 1000:
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h,x:x+w]
original[y:y+h, x:x+w] = ROI
cv2.imshow('thresh', thresh)
cv2.imshow('ROI', ROI)
cv2.imshow('original', original)
cv2.waitKey()

Remove false text regions from an image

I'm working on a project to detect text in images. So far I have been able to isolate candidate text regions. I used some threshold values for aspect ratio, contour area and white pixel count inside the bounding box of a counter to remove non text regions. But I cannot give too smaller thresholds for these parameters as there are images with small font sizes. Still there are some non text regions present. I read that Stroke Width Transform is a solution for this problem but it is to complicated. Is there any other method to remove these non text regions?
I thought of using the curve shape of text to distinguish the regions but couldn't think of a way to implement it.
This is a sample image
Identified regions
You can use simple contour area filtering to remove the noise. The idea is to find contours, filter using cv2.contourArea(), and draw the valid contours onto a blank mask. To reconstruct the image without the noise, we bitwise-and the input image with the mask to get our result.
Noise to remove highlighted in green
Result
Code
import cv2
import numpy as np
# Load image, create blank mask, grayscale, Otsu's threshold
image = cv2.imread('1.png')
mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Find contours and filter using contour area
cnts = cv2.findContours(thresh, 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 > 250:
cv2.drawContours(mask, [c], -1, (255,255,255), -1)
# Bitwise and to reconstruct image
result = cv2.bitwise_and(image, mask)
cv2.imshow('mask', mask)
cv2.imshow('result', result)
cv2.waitKey()
Note: If you know that the text will be yellow, another approach would be to use color thresholding to isolate the text. You can use this HSV color thresholder script to determine the lower/upper bounds

Counting special elements on image with OpenCV and Python

I want to count number of trees on this picture from above.
I know how to count elements, but till now I used images with white background, so counting is much easier. But on image like this i do not know what to do:
I converted the image to gray, and then done the threshold *(threshold value is done by hand, is there a way to find it automaticly?), my next idea is to find the 'centers' of black dots, or to 'group' them.
I also tried to change brightness and contrast but it didnt work.
What should I do?
This is the code that I wrote:
import cv2
import numpy as np
# Read image
img = cv2.imread('slika.jpg')
# Convert image to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Show grayscale image
cv2.imshow('gray image', gray)
cv2.waitKey(0)
#BIG PROBLEM: IM FINDING VALUE OF `40` IN THE LINE BELOW MANUALLY
# Inverse binary threshold image with threshold at 40,
_, threshold_one = cv2.threshold(gray, 40 , 255, cv2.THRESH_BINARY_INV)
# Show thresholded image
cv2.imshow('threshold image', threshold_one)
cv2.waitKey(0)
# Find contours
contours, h = cv2.findContours(threshold_one, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print('Number of trees found:', len(contours)) #GIVES WRONG RESULT
# Iterate all found contours
for cnt in contours:
# Draw contour in original/final image
cv2.drawContours(img, [cnt], 0, (0, 0, 255), 1)
# Show final image
cv2.imshow('result image', img)
cv2.waitKey(0)
This is the image with treshold, I have tried to blur it (in order to connect black dots), but the final output is the same:
This is the result image:
Here's a rough method to estimate the number of trees. The idea to to model each tree as a blob then use contour filtering with a minimum threshold area to ignore the noise. To determine automatic threshold levels, you can use Otsu's threshold by appending cv2.THRESH_OTSU or Adaptive threshold with cv2.adaptiveThreshold(). This approach has problems when the trees are very close together since they form a single blob. Possible improvements could be to find the average area of each tree then find the floor with a large blob. You would probably need to train a classifier and use deep/machine learning for better accuracy
Trees: 102
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)
opening = cv2.morphologyEx(close, cv2.MORPH_OPEN, kernel, iterations=2)
cnts = cv2.findContours(opening, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
trees = 0
for c in cnts:
area = cv2.contourArea(c)
if area > 50:
x,y,w,h = cv2.boundingRect(c)
cv2.drawContours(image, [c], -1, (36,255,12), 2)
trees += 1
print('Trees:', trees)
cv2.imshow('image', image)
cv2.waitKey()

How to invert the background of square headers from black to white in an image using python?

I am trying to make the background of the square headers (The black bar that contains TERMS PONUMBER PROJECT) white and the text within black.
I have tried using the findContours method to find the contours and then crop and invert them so that I get them in the black text and white background form. But the problem is I am not having any idea on how to proceed ahead or is there any better approach to this
image =cv2.imread("default.jpg")
gray=cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
th, thresh = cv2.threshold(gray,1, 255, cv2.THRESH_BINARY_INV)
kernel = cv2.getStructuringElemnt(cv2.MORPH_ELLIPSE,(7,7))
morp_image = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
contours = cv2.findContours(morp_image,
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cnts = sorted(contours,key=cv2.contourArea)[-1]
The code above does find each such contour on an individual basis like if I change the [-1] in the last line of the code to [-2], it will find the next contour but I want to find all such areas in the image in a single go and make the background of such areas white while changing the text to black.
Thanks
Here's a simple approach
Convert image to grayscale and Gaussian blur
Otsu's threshold to obtain binary image
Find contours
Filter using the number of corners and contour area
Extract ROI, invert ROI, and replace into original image
The idea is that if the contour has 4 corners, it must be a square/rectangle. In addition, we filter using a minimum contour area to ignore noise. If the contour passes our filter then we have a desired ROI to invert. The detected ROIs
Now we extract each ROI using Numpy slicing. Here's each ROI before and after inverting
Now we simply replace each inverted ROI back into the original image to get our result
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.015 * peri, True)
area = cv2.contourArea(c)
if len(approx) == 4 and area > 1000:
x,y,w,h = cv2.boundingRect(c)
ROI = 255 - image[y:y+h,x:x+w]
image[y:y+h, x:x+w] = ROI
cv2.imshow('image', image)
cv2.imwrite('image.png', image)
cv2.waitKey()

Categories