How to detect defect bean using python3 - python

normal bean sample
defect bean sample-1
I want to judge whether the bean is normal or defect.
I tried to use Canny method(to find edge) and so on... but I failed.
I just solve it using shape.(cracked bean & unshaped bean ...)
Please, give me some idea to solve it.
Sorry for my English, it's not my first language.

One of the most widely used methods to "fill cracks" in an image is dilate-erode. Simply put, you make your binary image "grow" at the edges, so the cracks get filled, then you reverse the process and make it "shrink" at the edges - but, as the cracks have been filled, there is no information about them left in the image, so they stay filled. Perhaps, you can use that and then look at the difference between your original image and the image after dilate-erode: if there were little to no cracks, there would be little to no difference, and if there were plenty of cracks, there would be plenty of difference.
For example. Let's convert our image to a binary black and white mask:
def get_th_binary_mask(img):
gs = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gs, 0, 255, cv2.THRESH_BINARY)
mask = np.zeros(thresh.shape, np.uint8)
mask[thresh == 255] = 0
mask[thresh == 0] = 1
return mask
Now, just sum the elements of the matrix, which gives us the number of white pixels, dilate-erode, sum again, subtract the sums:
def get_de_difference(binary_image):
s_before = np.sum(binary_image)
kernel = np.ones((17, 17), np.uint8)
d = cv2.dilate(binary_image, kernel, 1)
d = cv2.erode(d, kernel, 1)
s_after = np.sum(d)
return abs(s_after - s_before)
For the "good" bean it gives 72 pixels different, for the "bad" one it gives 1158.
Can be further improved by using a more sophisticated thresholding function, for example, based on Otsu and Grab cut:
def get_gc_binary_mask(img):
gs = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gs, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
mask = np.zeros(thresh.shape, np.uint8)
mask[thresh == 255] = cv2.GC_PR_BGD
mask[thresh == 0] = cv2.GC_FGD
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
cv2.grabCut(img, mask, (0, 0, 1365, 767), bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_MASK)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
return mask2
Using it instead of the previous one gives only 1 pixel difference for the "good" bean (despite an unfortunate artifact - see below), while for the "bad" bean it gives 741. Or, if you can change the background of your pictures, just put some bright green/blue sheet there before taking your pictures and use chroma keying.
This is how it looks, left to right: original images (column 1), basic threshold, dilate, erode, otsu/grabcut threshold, dilate, erode. The difference between columns 2 and 4 and between columns 5 and 7 is what matters.

Related

How to expand a filtering mask to cover more pixels of the area of interest in OpenCV?

Take a look at this image. I want to turn this card blue.
I use Python and OpenCV to perform image processing.
Here's how I do it now:
import cv2
import numpy as np
# Load the image
image = cv2.imread("input.jpg")
# Convert the image to the HSV color space
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Threshold the HSV image to get only the red colors
# Bitwise OR unites Hue value 170-179 and 0-10.
mask = cv2.bitwise_or(
cv2.inRange(hsv_image, np.array([0, 120, 100]), np.array([10, 255, 255])),
cv2.inRange(hsv_image, np.array([170, 120, 100]), np.array([180, 255, 255]))
)
# Perform median blurring
mask = cv2.medianBlur(mask, 5)
# Define a kernel for the morphological operations
kernel = np.ones((5, 5), np.uint8)
# Perform an opening operation to remove small objects
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2)
# Perform a closing operation to fill small holes
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=2)
# Perform a gradient operation to extract object boundaries
gradient_mask = cv2.morphologyEx(mask, cv2.MORPH_GRADIENT, kernel)
# Modify hue value of every masked pixel
hsv_image[:, :, 0][mask != 0] = (hsv_image[:, :, 0][mask != 0].astype(int, copy=False) + 120) % 180
# Convert the HSV image back to BGR color space
result = cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)
# Display the result
cv2.namedWindow("output", cv2.WINDOW_NORMAL)
cv2.imshow("output", result)
# Save images of the mask, result and gradient
cv2.imwrite('mask.jpg', mask)
cv2.imwrite('result.jpg', result)
cv2.imwrite('gradient.jpg', gradient_mask)
# Wait for the window to close
while cv2.getWindowProperty('output', 0) >= 0:
cv2.waitKey(50)
cv2.destroyAllWindows()
It works well. The result, The filtering mask
But if you take a closer look you'll see the problem: link, link
Some red pixels is still here, and here's why. They do not fall in the filtered range of the red color: 170 <= Hue <= 10, Saturation >= 120, Value >= 100. Their HSV color is near to (178, 32, 60). So, the saturation and value fall out of the filter range.
Why I can't lower the range of saturation and value? That's because in this case there would be too much noise on another background that has more colors. The noise in this case is hard to avoid even using multiple iterations of opening morphological operation.
I don't have much experience in image processing and using OpenCV, so my ideas may be far from the best solution. It's okay if you propose another approach.
Possible solution. Would it be possible to perform the dilate morphological operation on the filtering mask (expand the filtering mask) but only to those pixels that fall in another, broader range of red (with saturation and value range equal to 10, hue range stays the same). So that all the red pixels that fall in the broader range of the red color AND that are adjacent to the pixels of the existing mask (so no pixels from the background is added creating noise).
If that is a good idea, how can I implement it, especially the part of dilating only to pixels that fall in the broader range of the red color? Maybe it's already implemented in OpenCV and I just don't know about that?
Also I would be glad to hear any suggestions or recommendations. I am a student, I want to learn more.
Thanks in advance!
I found myself how to do it, but it's still not the best mask.
I create a broader mask as follows:
broader_mask = cv2.bitwise_or(
cv2.inRange(hsv_image, np.array([0, 30, 30]), np.array([20, 255, 255])),
cv2.inRange(hsv_image, np.array([160, 30, 30]), np.array([180, 255, 255]))
)
And apply bitwise AND on the dilated primary mask and the broader mask to get the resulting mask:
mask = cv2.bitwise_and(cv2.dilate(mask, kernel, iterations=2), broader_mask)
It works much better, but now the mask might be bigger than I want in some cases. For example:
It all depends on the kernel and the number of iterations of the dilation operation. I don't think it's the best solution because the kernel and the number of iterations might be different for different image sizes, and I'm still looking for a better solution.

How to prevent INPAINTING blocks and improve coloring

I wanted to Remove all the texts USING INPAINTING from this IMAGE. I had been trying various methods, and eventually found that I can get the results through OCR and then using thresholding MASK THE IMAGE.
processedImage = preprocess(partOFimg)
mask = np.ones(img.shape[:2], dtype="uint8") * 255
for c in cnts:
cv2.drawContours(mask, [c], -1, 0, -1)
img = cv2.inpaint(img,mask,7,cv2.INPAINT_TELEA)
Preprocess operations:
ret,thresh1 = cv2.threshold(gray, 0, 255,cv2.THRESH_OTSU|cv2.THRESH_BINARY_INV)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
dilation = cv2.dilate(thresh1, rect_kernel, iterations = 1)
edged = cv2.Canny(dilation, 50, 100)
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
mask =
np.ones(img.shape[:2], dtype="uint8") * 255
When I run the above code, I here am the OUTPUT Image OUTPUT. As we can see, it is making some BLOCKS OF DIFFERENT COLOR over the IMAGE, I want to prevent that, How do I achieve this? I see that mask images are not formed well many times, and in cases when the text is white the PREPROCESSING doesn't occur properly.
How do I prevent these BLOCKS of other colours to FORM on the IMAGE?
Grayed Sub Image GRAYED
Threshold Sub IMG part: Thresholded Image
Masked Image Masked
EDIT 1:
I've managed to get this new better result by noticing that my threshold is the best mask I can get. After doing this I performed the masking process 3 different times with variable masks and inversions. I did the inpainting algorithm 3 times, it basically the other times inverse the mask, because in some cases required mask is the inversed mask. Still I think it needs improvement, If I chose a different image the results are not so good.
Python/OpenCV inpaint methods, generally, are not appropriate to your type of image. They work best on thin (scratch-like) regions, not large blocks. You really need an exemplar type method such as https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/criminisi_tip2004.pdf. But OpenCV does not have that.
However, the OpenCV methods do work here, I suspect, because you are filling with constant colors (green) and not texture. So you are best to try to get the mask of just the letters (characters), not rectangular blocks for the words. So, to show you what I mean, here is my Python/OpenCV approach.
Input:
Read the input
Threshold on the green sign
Apply morphology to close it up and keep as mask1
Apply the mask to the image to blacken out the outside of the sign
Threshold on the white in this new image and keep as mask2
Apply morphology dilate to enlarge it slightly and save as mask3
Do the inpaint
Save the results
import cv2
import numpy as np
# read input
img = cv2.imread('airport_sign.jpg')
# threshold on green sign
lower = (30,80,0)
upper = (70,120,20)
thresh = cv2.inRange(img, lower, upper)
# apply morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (135,135))
mask1 = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# apply mask to img
img2 = img.copy()
img2[mask1==0] = (0,0,0)
# threshold on white
#gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
#mask2 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
lower = (120,120,120)
upper = (255,255,255)
mask2 = cv2.inRange(img2, lower, upper)
# apply morphology dilate
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
mask3 = cv2.morphologyEx(mask2, cv2.MORPH_DILATE, kernel)
# do inpainting
result1 = cv2.inpaint(img,mask3,11,cv2.INPAINT_TELEA)
result2 = cv2.inpaint(img,mask3,11,cv2.INPAINT_NS)
# save results
cv2.imwrite('airport_sign_mask.png', mask3)
cv2.imwrite('airport_sign_inpainted1.png', result1)
cv2.imwrite('airport_sign_inpainted2.png', result1)
# show results
cv2.imshow('thresh',thresh)
cv2.imshow('mask1',mask1)
cv2.imshow('img2',img2)
cv2.imshow('mask2',mask2)
cv2.imshow('mask3',mask3)
cv2.imshow('result1',result1)
cv2.imshow('result2',result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask 3:
Inpaint 1 (Telea):
Inpaint 2 (NS):

Is there any way to fill the empty gap between two parts in an image?

Threshold image
Mouth part
Extracted teeth
I am trying to make extraction for the teeth part only from the cropped mouth image but the threshold and mask way miss some teeth as shown in the extracted teeth image.
I am using python in do this by getting the threshold image then find contours based on the threshold image and extract the contour detected,
by this code:
labels = measure.label(threshold, connectivity=2, background=0)
mask = np.zeros(threshold.shape, dtype="uint8")
for label in np.unique(labels):
if label == 0:
continue
labelMask = np.zeros(threshold.shape, dtype="uint8")
labelMask[labels == label] = 255
numPixels = cv2.countNonZero(labelMask)
if numPixels > 600:
mask = cv2.add(mask, labelMask)
cv2.imshow("mask", mask)
_, contours, _ = cv2.findContours(mask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(equa, contours, -1, (0, 0, 255), 2)
cv2.imshow("thre",equa)
img_contours= cv2.findContours(mask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)[-2]
img_contours = sorted(img_contours, key=cv2.contourArea)
for i in img_contours:
if cv2.contourArea(i) > 1000:
break
mask = np.zeros(mask.shape[:2], np.uint8)
cv2.drawContours(mask, [i],-1, 255, -1)
new_img = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow("Image with background removed", new_img)
So is there anyway to make extraction and get all teeth or fill the empty gap between the extracted teeth?
I'd suggest to use a different approach:
convert to HSV colorspace and use just the Hue channel, then do some thresholding and morphing to get the mask.
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(img_hsv)
Maybe you want to blur the h channel before thresholding.
h_blur = cv2.GaussianBlur(h, (5, 5), 1)
For the thresholding on the h channel I suggest you to use the GUI for some interactive adjustment
ret, thh_blur = cv2.threshold(h_blur, 9, 255, cv2.THRESH_BINARY)
You can use thh_blur as a mask and see if it is good for you or follow with some e morphing:
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
morphed_th = cv2.morphologyEx(thh_blur, cv2.MORPH_OPEN, kernel, iterations = 1)
morphed_th = cv2.morphologyEx(morphed_th, cv2.MORPH_CLOSE, kernel, iterations = 2)
Finally use morphed_th as a mask:
res = img.copy()
res[morphed_th == 0] = 0
res
I got the following image.
I don't know if it good for you since it misses the side teeth in the darker corners, but you can extract them from the s channel and add to the mask.
If you want to get rid of the gap between theet (darker region) you can follow more or less the same logic starting from res as input image and playing with channels, thresholding etc.
Adding the left side requires to play with parameters.
This is the code. Do the same for the opposite side.
# blur the s channel
s_blur = cv2.GaussianBlur(s, (5, 5), 1)
# custom thresholding
val1 = 240
val2 = 170
ths_blur = s_blur.copy()
ths_blur[ths_blur > val1] = 0
ths_blur[ths_blur > val2] = 255
ths_blur[ths_blur <= val2] = 0
# add to the previous and slice the left part
mask = cv2.add(morphed_th, ths_blur)
left = np.s_[:,0:70]
# apply morphology to the left slice
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
morphed_mask_left = cv2.morphologyEx(mask[left], cv2.MORPH_CLOSE, kernel, iterations = 6)
# override the mask with the morphed left part
final_mask = morphed_th.copy()
final_mask[left] = morphed_mask_left
# use the mask
res_final = img.copy()
res_final[final_mask == 0] = (0, 0, 0)
res_final
This is what I get.
To get a better understanding of why I did this way, plot each step. Maybe this allow you to find an even better solution.
IMO, the answer to your very question is essentially negative.
The binary image has low information content, and there is no difference between a missing tooth and normal spacing around and between teeth.
With no a priori information on what a tooth pattern looks like, no low-level vision operation (filters) can help. You might be able to get some improvement by mapping a geometric model with a teeth layout, but this would not reflect the particular anatomy at hand and would be quite difficult to implement.
You must certainly work this out before binarization.

Computer vision: Creating mask of hand using OpenCv and Python

I am trying to create the mask from hand (black - background, white - hand)
This is the first original image 1:
This is my code:
hand = cv2.imread('hand.jpg')
blur = cv2.GaussianBlur(hand, (3, 3), 0)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
mask2 = cv2.inRange(hsv, np.array([2, 0, 0]), np.array([20, 255, 255]))
kernel = np.ones((7, 7))
dilation = cv2.dilate(mask2, kernel, iterations=1)
erosion = cv2.erode(dilation, kernel, iterations=1)
filtered = cv2.GaussianBlur(erosion, (5, 5), 0)
ret, thresh = cv2.threshold(filtered, 90, 255, 0)
cv2.imshow('Threshold', thresh)
And the result is:
But I need have better result - like that:
What should I do?
[EDIT]
second image with different background:
Result using #Rotem code:
1 Ajay Kumar. IIT Delhi Palmprint Image Database version 1.0. 2007
You may solve it by applying threshold on the red color channel.
The background color is dark blue and green, and the hand color is bright and tend to red color, so using only the red color channel may give better results than converting to HSV.
The solution below uses the following stages:
Extract red color channel - use the red channel as Grayscale image.
Apply binary threshold using automatically selected threshold (using cv2.THRESH_OTSU parameter).
Use "opening" morphological operation for clearing some small dots (noise).
Opening is equivalent to applying erode and than dilate.
Use "closing" morphological operation for closing small gaps (I chose disk shaped mask).
Closing is equivalent to applying dilate and than erode.
Here is the code:
import cv2
img = cv2.imread('hand.jpg')
# Extract red color channel (because the hand color is more red than the background).
gray = img[:, :, 2]
# Apply binary threshold using automatically selected threshold (using cv2.THRESH_OTSU parameter).
ret, thresh_gray = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Use "opening" morphological operation for clearing some small dots (noise)
thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)))
# Use "closing" morphological operation for closing small gaps
thresh_gray = cv2.morphologyEx(thresh_gray, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9)))
# Display result:
cv2.imshow('thresh_gray', cv2.resize(thresh_gray, (thresh_gray.shape[1]//2, thresh_gray.shape[0]//2)))
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
I think you may improve the result by finding the contour of the hand, and proximate it to polygon with less vertices (but it might be an over-fitting).

Image Card detection using python

I want to detect how many number of cards are present in this image using python.I was trying with white pixel but not getting the correct result.
My code is given below:
import cv2
import numpy as np
img = cv2.imread('imaagi.jpg', cv2.IMREAD_GRAYSCALE)
n_white_pix = np.sum(img == 255)
print('Number of white pixels:', n_white_pix)
I am a beginner. So unable to find out the way.
This solution is with respect to the image you have provided and the implementation is in OpenCV.
Code:
im = cv2.imread('C:/Users/Jackson/Desktop/cards.jpg', 1)
#--- convert the image to HSV color space ---
hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
cv2.imshow('H', hsv[:,:,0])
cv2.imshow('S', hsv[:,:,1])
#--- find Otsu threshold on hue and saturation channel ---
ret, thresh_H = cv2.threshold(hsv[:,:,0], 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
ret, thresh_S = cv2.threshold(hsv[:,:,1], 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
#--- add the result of the above two ---
cv2.imshow('thresh', thresh_H + thresh_S)
#--- some morphology operation to clear unwanted spots ---
kernel = np.ones((5, 5), np.uint8)
dilation = cv2.dilate(thresh_H + thresh_S, kernel, iterations = 1)
cv2.imshow('dilation', dilation)
#--- find contours on the result above ---
(_, contours, hierarchy) = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
#--- since there were few small contours found, retain those above a certain area ---
im2 = im.copy()
count = 0
for c in contours:
if cv2.contourArea(c) > 500:
count+=1
cv2.drawContours(im2, [c], -1, (0, 255, 0), 2)
cv2.imshow('cards_output', im2)
print('There are {} cards'.format(count))
Result:
On the terminal I got: There are 6 cards
Depending on how exactly your "white pixel approach" was working (please share more details on that if possible), you could try a simple image binarization, which is a well-established way of separating different objects/entities in your image. Granted, it will work only on grayscale images, but that is something you can also easily fix with sklearn.
It might provide optimal results right away, especially if the lighting conditions vary across images, or you have (as seen above) cards that contain a wide variety of colors.
To circumvent this, you could also try to look into different color spaces, e..g HSV.
If that still does not work, I would recommend using image segmentation libraries from OpenCV or similra libraries. The problem is that they usually also bring some unwanted complexity to your project, which might not be necessary if it works with a simple approach such as the binarization.

Categories