Detect scratches on the white Images - python

I have a data set that contains washing machine images with light and big deep scratches. I have tried the following two images with the the below-listed code.
Side cropped and rotated image:
Side cropped image but not rotated:
I have tried the following code from How to improve the detection of the defect on the surface? but it is not working for white images which show black screens in the output. I have a huge data set of such images but am not able to detect the scratches. How could I do this using the following code? If it's not possible then what would be the right approach?
import cv2
import numpy as np
# load image
img = cv2.imread('./detect-01.jpg')
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# adaptive threshold
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, -35)
# apply morphology
kernel = np.ones((3,30),np.uint8)
morph = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
kernel = np.ones((3,35),np.uint8)
morph = cv2.morphologyEx(morph, cv2.MORPH_OPEN, kernel)
# get hough line segments
threshold = 25
minLineLength = 10
maxLineGap = 20
lines = cv2.HoughLinesP(morph, 1, 30*np.pi/360, threshold, minLineLength, maxLineGap)
# draw lines
linear1 = np.zeros_like(thresh)
linear2 = img.copy()
for [line] in lines:
x1 = line[0]
y1 = line[1]
x2 = line[2]
y2 = line[3]
cv2.line(linear1, (x1,y1), (x2,y2), 255, 1)
cv2.line(linear2, (x1,y1), (x2,y2), (0,0,255), 1)
print('number of lines:',len(lines))
# save resulting masked image
cv2.imwrite('scratches_thresh.jpg', thresh)
cv2.imwrite('scratches_morph.jpg', morph)
cv2.imwrite('scratches_lines1.jpg', linear1)
cv2.imwrite('scratches_lines2.jpg', linear2)
# display result
cv2.imshow("thresh", thresh)
cv2.imshow("morph", morph)
cv2.imshow("lines1", linear1)
cv2.imshow("lines2", linear2)
cv2.waitKey(0)
cv2.destroyAllWindows()

I will provide you few suggestions based on my experience.
Get the Canny of the gray scale image.
Canny thresholds for the lighter background images should be less. For example threshold1: 20, threshold2: 35 etc.
For dark background images canny thresholds can be 150, 220 etc.
Apply the houghline on the canny output image.
Try to use Houghlines first instead of HoughlinesP.
Draw the detected lines on image and adjust the threshold values.

Related

Identify and count objects different from background

I try to use python, NumPy, and OpenCV to analyze the image below and just draw a circle on each object found. The idea here is not to identify the bug only identify any object that is different from the background.
Original Image:
Here is the code that I'm using.
import cv2
import numpy as np
img = cv2.imread('per.jpeg', cv2.IMREAD_GRAYSCALE)
if cv2.__version__.startswith('2.'):
detector = cv2.SimpleBlobDetector()
else:
detector = cv2.SimpleBlobDetector_create()
keypoints = detector.detect(img)
print(len(keypoints))
imgKeyPoints = cv2.drawKeypoints(img, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
status = cv2.imwrite('teste.jpeg',imgKeyPoints)
print("Image written to file-system : ",status)
But the problem is that I'm getting only a greyscale image as result without any counting or red circle, as shown below:
Since I'm new to OpenCV and object recognition world I'm not able to identify what is wrong, and any help will be very appreciated.
Here is one way in Python/OpenCV.
Threshold on the bugs color in HSV colorspace. Then use morphology to clean up the threshold. Then get contours. Then find the minimum enclosing circle around each contour. Then bias the radius to make a bit larger and draw the circle around each bug.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('bugs.jpg')
# convert image to hsv colorspace
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# threshold on bugs color
lower=(0,90,10)
upper=(100,250,170)
thresh = cv2.inRange(hsv, lower, upper)
# apply morphology to clean up
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6,6))
morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)
# get external contours
contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
bias = 10
for cntr in contours:
center, radius = cv2.minEnclosingCircle(cntr)
cx = int(round(center[0]))
cy = int(round(center[1]))
rr = int(round(radius)) + bias
cv2.circle(result, (cx,cy), rr, (0, 0, 255), 2)
# save results
cv2.imwrite('bugs_threshold.jpg', thresh)
cv2.imwrite('bugs_cleaned.jpg', morph)
cv2.imwrite('bugs_circled.jpg', result)
# display results
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold Image:
Morphology Cleaned Image:
Resulting Circles:

Removing White Text with Black Borders From Image

I am trying to remove text from images that has a black border with white fill. Take the image below as an example.
I have tried a few options utilizing opencv and skimage inpaint
import cv2
from skimage.restoration import inpaint
img = cv2.imread('Documents/test_image.png')
mask = cv2.threshold(img, 210, 255, cv2.THRESH_BINARY)[1][:,:,0]
dst = cv2.inpaint(img, mask, 7, cv2.INPAINT_TELEA)
image_result = inpaint.inpaint_biharmonic(img, mask,
multichannel=True)
cv2.imshow('image',img)
cv2.imshow('mask',mask)
cv2.imshow('dst',dst)
cv2.imshow('image_result',image_result)
cv2.waitKey(0)
It seems like the inpainting is just trying to fill with black as that is what it is identifying as being around the areas of interest. What I would like to do is remove the white text and black borders completely, or secondarily try to fill the white with more information from surrounding colors than just the black.
Here is the best solution I could come up with, still open to others with more experience showing me a better way if anyone has an idea.
mask = cv2.threshold(img, 245, 255, cv2.THRESH_BINARY)[1][:,:,0]
new_mask = cv2.dilate(mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10,10)))
dst = cv2.inpaint(img, new_mask, 7, cv2.INPAINT_TELEA)
Here are two inpainting methods in Python/OpenCV. Note that I use the saturation channel to create the threshold, since white and black have zero saturation, in principle.
Input:
import cv2
import numpy as np
# read input
img = cv2.imread('white_black_text.png')
# convert to hsv and extract saturation
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
sat = hsv[:,:,1]
# threshold and invert
thresh = cv2.threshold(sat, 10, 255, cv2.THRESH_BINARY)[1]
thresh = 255 - thresh
# apply morphology dilate
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15,15))
thresh = cv2.morphologyEx(thresh, cv2.MORPH_DILATE, kernel)
# do inpainting
result1 = cv2.inpaint(img,thresh,11,cv2.INPAINT_TELEA)
result2 = cv2.inpaint(img,thresh,11,cv2.INPAINT_NS)
# save results
cv2.imwrite('white_black_text_threshold.png', thresh)
cv2.imwrite('white_black_text_inpainted1.png', result1)
cv2.imwrite('white_black_text_inpainted2.png', result1)
# show results
cv2.imshow('thresh',thresh)
cv2.imshow('result1',result1)
cv2.imshow('result2',result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Threshold and morphology cleaned result:
Result 1 (Telea):
Result 2 (Navier Stokes):

How to detect the horizontal and vertical lines of a table and eliminate the noise?

I am trying to get the horizontal and vertical lines of the table in an image in order to extract the texts in cells. Here's a picture I use:
I use the code below to extract the vertical and horizontal lines:
img = cv2.imread(img_for_box_extraction_path, 0) # Read the image
(thresh, img_bin) = cv2.threshold(img, 200, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU) # Thresholding the image
img_bin = 255-img_bin # Invert the image
cv2.imwrite("Image_bin_2.jpg",img_bin)
# Defining a kernel length
kernel_length = np.array(img).shape[1]//140
# A verticle kernel of (1 X kernel_length), which will detect all the verticle lines from the image.
verticle_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_length))
# A horizontal kernel of (kernel_length X 1), which will help to detect all the horizontal line from the image.
hori_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_length, 1))
# A kernel of (3 X 3) ones.
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# Morphological operation to detect verticle lines from an image
img_temp1 = cv2.erode(img_bin, verticle_kernel, iterations=3)
verticle_lines_img = cv2.dilate(img_temp1, verticle_kernel, iterations=3)
cv2.imwrite("verticle_lines_2.jpg",verticle_lines_img)
# Morphological operation to detect horizontal lines from an image
img_temp2 = cv2.erode(img_bin, hori_kernel, iterations=3)
horizontal_lines_img = cv2.dilate(img_temp2, hori_kernel, iterations=3)
cv2.imwrite("horizontal_lines_2.jpg",horizontal_lines_img)
The pictures below are the horizontal lines and vertical lines:
I use the code below to add two image together
# Weighting parameters, this will decide the quantity of an image to be added to make a new image.
alpha = 0.5
beta = 1.0 - alpha
# This function helps to add two image with specific weight parameter to get a third image as summation of two image.
img_final_bin = cv2.addWeighted(verticle_lines_img, alpha, horizontal_lines_img, beta, 0.0)
img_final_bin = cv2.erode(~img_final_bin, kernel, iterations=2)
(thresh, img_final_bin) = cv2.threshold(img_final_bin, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# For Debugging
# Enable this line to see verticle and horizontal lines in the image which is used to find boxes
cv2.imwrite("img_final_bin_2.jpg",img_final_bin)
However, I get a picture like this:
How do I remove the noise and get a better result? Thanks in advance.
Here's a simple method:
Binary image
Detected horizontal
Detected vertical
Combined masks
Lines to be removed in green
Result
import cv2
import numpy as np
# Load image, grayscale, Gaussian blur, Otsu's threshold
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]
# Detect horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
horizontal_mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
# Detect vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))
vertical_mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=1)
# Combine masks and remove lines
table_mask = cv2.bitwise_or(horizontal_mask, vertical_mask)
image[np.where(table_mask==255)] = [255,255,255]
cv2.imshow('thresh', thresh)
cv2.imshow('horizontal_mask', horizontal_mask)
cv2.imshow('vertical_mask', vertical_mask)
cv2.imshow('table_mask', table_mask)
cv2.imshow('image', image)
cv2.waitKey()
Try reduce with CV_REDUCE_AVG flag along horizontal for your first binary image and then anlong vertical direction for your second one. Threshols histograms you'll get. And use them to filter your lines.
As an alternative, you can try hough lines detector and filter lines by length and algle.

Find area with content and get its bouding rect

I'm using OpenCV 4 - python 3 - to find an specific area in a black & white image.
This area is not a 100% filled shape. It may hame some gaps between the white lines.
This is the base image from where I start processing:
This is the rectangle I expect - made with photoshop -:
Results I got with hough transform lines - not accurate -
So basically, I start from the first image and I expect to find what you see in the second one.
Any idea of how to get the rectangle of the second image?
I'd like to present an approach which might be computationally less expensive than the solution in fmw42's answer only using NumPy's nonzero function. Basically, all non-zero indices for both axes are found, and then the minima and maxima are obtained. Since we have binary images here, this approach works pretty well.
Let's have a look at the following code:
import cv2
import numpy as np
# Read image as grayscale; threshold to get rid of artifacts
_, img = cv2.threshold(cv2.imread('images/LXSsV.png', cv2.IMREAD_GRAYSCALE), 0, 255, cv2.THRESH_BINARY)
# Get indices of all non-zero elements
nz = np.nonzero(img)
# Find minimum and maximum x and y indices
y_min = np.min(nz[0])
y_max = np.max(nz[0])
x_min = np.min(nz[1])
x_max = np.max(nz[1])
# Create some output
output = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.rectangle(output, (x_min, y_min), (x_max, y_max), (0, 0, 255), 2)
# Show results
cv2.imshow('img', img)
cv2.imshow('output', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
I borrowed the cropped image from fmw42's answer as input, and my output should be the same (or most similar):
Hope that (also) helps!
In Python/OpenCV, you can use morphology to connect all the white parts of your image and then get the outer contour. Note I have modified your image to remove the parts at the top and bottom from your screen snap.
import cv2
import numpy as np
# read image as grayscale
img = cv2.imread('blackbox.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
_,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)
# apply close to connect the white areas
kernel = np.ones((75,75), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get contours (presumably just one around the outside)
result = img.copy()
contours = cv2.findContours(thresh, 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(result, (x, y), (x+w, y+h), (0, 0, 255), 2)
# show thresh and result
cv2.imshow("thresh", thresh)
cv2.imshow("Bounding Box", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save resulting images
cv2.imwrite('blackbox_thresh.png',thresh)
cv2.imwrite('blackbox_result.png',result)
Input:
Image after morphology:
Result:
Here's a slight modification to #fmw42's answer. The idea is connect the desired regions into a single contour is very similar however you can find the bounding rectangle directly since there's only one object. Using the same cropped input image, here's the result.
We can optionally extract the ROI too
import cv2
# Grayscale, threshold, and dilate
image = cv2.imread('3.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Connect into a single contour and find rect
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations=1)
x,y,w,h = cv2.boundingRect(dilate)
ROI = original[y:y+h,x:x+w]
cv2.rectangle(image, (x, y), (x+w, y+h), (36, 255, 12), 2)
cv2.imshow('image', image)
cv2.imshow('ROI', ROI)
cv2.waitKey()

How to detect blurry blobs?

I would like to detect all the bright spots in this image (https://i.imgur.com/UnTWWHz.png)
The code I've tried is via thresholding, but it only detects the very bright ones. As you can see in the image below.
But some of the spots are out of focus which I need to also detect them.
Could you suggest a method? The picture below shows the blurred spots that I'd like to detect in yellow circles
I tried with the following code
import os
import cv2
import numpy as np
path="C:/Slides/Fluoroscent/E_03_G_O_subpics"
imgname="sub_2_4.png"
image = cv2.imread(os.path.join(path,imgname))
# constants
BINARY_THRESHOLD = 10
CONNECTIVITY = 4
DRAW_CIRCLE_RADIUS = 18
thr=50
# convert to gray
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# threshold the black/ non-black areas
_, thresh = cv2.threshold(gray_image, BINARY_THRESHOLD, thr, cv2.THRESH_BINARY)
# find connected components
components = cv2.connectedComponentsWithStats(thresh, CONNECTIVITY, cv2.CV_32S)
# draw circles around center of components
#see connectedComponentsWithStats function for attributes of components variable
centers = components[3]
for center in centers:
cv2.circle(image, (int(center[0]), int(center[1])), DRAW_CIRCLE_RADIUS, (0,0,255), thickness=1)
cv2.imwrite(os.path.join(path,"result_thresh_"+str(thr)+".png"), image)
cv2.imshow("result", image)
cv2.waitKey(0)
As mentioned in the comments you will get better results by changing the threshold values. I changed the values to 20 and 255 respectively and added erosion to get rid of some noise. You can play around with morphological transformations to get the exact desired result. Read more here .
Code:
import cv2
import numpy as np
kernel = np.ones((5,5),np.uint8)
CONNECTIVITY = 4
DRAW_CIRCLE_RADIUS = 18
img = cv2.imread('blobs.png')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray_img, 20, 255, cv2.THRESH_BINARY)
erosion = cv2.erode(thresh,kernel,iterations = 1)
components = cv2.connectedComponentsWithStats(erosion, CONNECTIVITY, cv2.CV_32S)
centers = components[3]
for center in centers:
cv2.circle(img, (int(center[0]), int(center[1])), DRAW_CIRCLE_RADIUS, (0,0,255), thickness=1)
cv2.imshow('Original', img)
cv2.imshow('Thresh', thresh)
cv2.imshow('Erosion', erosion)
cv2.waitKey(0)
Results:
Threshold
Erosion
Original with circles

Categories

Resources