Opencv Binary Item Detection - python

I am working on a program to find debris that get stuck in parts that I make. So far I have been able to take a clean part and a part with a chip in it and subtract the two images, leaving any difference between the two as a binary image. What I don't understand is how to detect this item in the binary image. So far I used the SimpleBlobDetector function, but I have to blur the image so much to get it to work that I'm concerned it won't work with smaller debris. I want to be able to detect the original without extensive blurring. Any help would be appreciated. Code and images are below.
import cv2
import numpy as np
#Load Images
tempImg = cv2.imread('images/partchip.jpg')
lineImg = cv2.imread('images/partnochip.jpg')
#Crop Images
cropTemp = tempImg[460:589, 647:875]
cropLine = lineImg[460:589, 647:875]
#Gray Scale
grayTemp = cv2.cvtColor(cropTemp,cv2.COLOR_BGR2GRAY)
grayLine = cv2.cvtColor(cropLine,cv2.COLOR_BGR2GRAY)
#Subtract Images
holder = cv2.absdiff(grayTemp,grayLine)
#THreshold Subtracted Image
th, imgDiff = cv2.threshold(holder, 160, 255, cv2.THRESH_BINARY_INV)
#Blur Image
#blur = imgDiff
blur = cv2.blur(imgDiff,(20,20))
#Detect Blobs
detector = cv2.SimpleBlobDetector_create()
blob = detector.detect(blur)
imgkeypoints = cv2.drawKeypoints(blur, blob, np.array([]), (0,255,0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
originalWithPoints=cv2.drawKeypoints(cropTemp, blob, np.array([]), (0,255,0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.namedWindow("Template", cv2.WINDOW_NORMAL)
cv2.namedWindow("Line", cv2.WINDOW_NORMAL)
cv2.namedWindow("Difference", cv2.WINDOW_NORMAL)
cv2.resizeWindow("Template", 500, 300)
cv2.resizeWindow("Line", 500, 300)
cv2.resizeWindow("Difference", 500, 300)
cv2.imshow('Template',originalWithPoints)
cv2.imshow('Line',cropLine)
cv2.imshow('Difference',imgkeypoints)
cv2.waitKey(0)
cv2.destroyAllWindows()
Part with chip
Part with No Chip

I used your code to find the anomaly. I obtained contour having the greatest area on the imgDiff binary image. Using that I was able to bound it with a rectangle.
I hope this is what you are looking for....
EDIT:
I have explained the procedure along with code below:
Note: Invert your imgDiff using cv2.bitwise_not(imgDiff), because contours are found if objects are in white.
#---Finding the contours present in 'imgDiff'---
_, contours,hierarchy = cv2.findContours(imgDiff,2,1)
ff = 0 #----to determine which contour to select---
area = 0 #----to determine the maximum area---
for i in range(len(contours)):
if(cv2.contourArea(contours[i]) > area):
area = cv2.contourArea(contours[i])
ff = i
#---Bounding the contour having largest area---
x,y,w,h = cv2.boundingRect(contours[ff])
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('fin.jpg',img)

Related

Get number of pixels of each object in image

As shown in the images, I have a masked MRI in which certain muscles have been segmented out. I am trying to get the area of these muscles in pixels. Currently, I can get all the non-zero pixels in the image using np.count_nonzero(result), however, I am looking to get the individual areas of each left and right muscle, not just the total.
I know I can manually crop/select an ROI of each area, but I do not want to do this for 100s of images. Also not all muscles are cleanly on each side as seen by the bottom image (cannot just split the image in half). The images are represented as 2D arrays.
Thank you!
You can use OpenCV Library to get the number of pixels for each object in the image.
import cv2 # OpenCV library
# Read the image
image = cv2.imread(r'D:\image_sample.jpg')
# Convert the image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Get the mask (predefined threshold)
mask = cv2.inRange(gray, 200, 255)
#Find all Objects contour's
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for i in range(len(contours)):
m=max(contours[i][0])
area = cv2.contourArea(contours[i])
print(f"Area{i+1} = {area} pixels")
image = cv2.drawContours(image, contours, i, (0,255,255*i), -1)
img_text = cv2.putText(image, str(area), m, cv2.FONT_HERSHEY_PLAIN, 1, (255,0,255), 1, cv2.LINE_AA, False)
#Show the resut
cv2.imshow('image with contours', image)
cv2.waitKey()

Detecting and counting blobs/connected objects with opencv

I want to detect and count the objects inside an image that touch while ignoring what could be considered as a single object. I have the basic image, on which i tried applying a cv2.HoughCircles() method to try and identify some circles. I then parsed the returned array and tried using cv2.circle() to draw them on the image.
However, I seem to always get too many circles returned by cv2.HoughCircles() and couldn't figure out how to only count the objects that are touching.
This is the image i was working on
My code so far:
import numpy
import matplotlib.pyplot as pyp
import cv2
segmentet = cv2.imread('photo')
houghCircles = cv2.HoughCircles(segmented, cv2.HOUGH_GRADIENT, 1, 80, param1=450, param2=10, minRadius=30, maxRadius=200)
houghArray = numpy.uint16(houghCircles)[0,:]
for circle in houghArray:
cv2.circle(segmented, (circle[0], circle[1]), circle[2], (0, 250, 0), 3)
And this is the image i get, which is quite a far shot from want i really want.
How can i properly identify and count said objects?
Here is one way in Python OpenCV by getting contour areas and the convex hull area of the contours. The take the ratio (area/convex_hull_area). If small enough, then it is a cluster of blobs. Otherwise it is an isolated blob.
Input:
import cv2
import numpy as np
# read input image
img = cv2.imread('blobs_connected.jpg')
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold to binary
thresh = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY)[1]
# find contours
#label_img = img.copy()
contour_img = img.copy()
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
index = 1
isolated_count = 0
cluster_count = 0
for cntr in contours:
area = cv2.contourArea(cntr)
convex_hull = cv2.convexHull(cntr)
convex_hull_area = cv2.contourArea(convex_hull)
ratio = area / convex_hull_area
#print(index, area, convex_hull_area, ratio)
#x,y,w,h = cv2.boundingRect(cntr)
#cv2.putText(label_img, str(index), (x,y), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0,0,255), 2)
if ratio < 0.91:
# cluster contours in red
cv2.drawContours(contour_img, [cntr], 0, (0,0,255), 2)
cluster_count = cluster_count + 1
else:
# isolated contours in green
cv2.drawContours(contour_img, [cntr], 0, (0,255,0), 2)
isolated_count = isolated_count + 1
index = index + 1
print('number_clusters:',cluster_count)
print('number_isolated:',isolated_count)
# save result
cv2.imwrite("blobs_connected_result.jpg", contour_img)
# show images
cv2.imshow("thresh", thresh)
#cv2.imshow("label_img", label_img)
cv2.imshow("contour_img", contour_img)
cv2.waitKey(0)
Clusters in Red, Isolated blobs in Green:
Textual Information:
number_clusters: 4
number_isolated: 81
approach it in steps.
label connected components. two connected blobs get the same label because they're connected. so far so good.
now separate your blobs. use watershed (first comment) or whatever other method gives you results. I can't fully predict the watershed approach. it might deal with touching blobs of dissimilar size or it might do something silly. the sample/tutorial also assumes a minimum size (0.7 * max peak); plug in something absolute in pixels maybe.
then, for each separated blob, check which label it sits on (take coordinates of centroid to be safe), and note down a +1 for that label (a histogram).
any label that has more than one separated blob sitting on it, would be what you are looking for.

Find positions of objects on the noisy image

I have a bunch of images and I need to determine positions of crosses for further transformation of the image and the alignment procedure. The problem is that images are quite noisy and I'm new to all these things of computer vision. Generally, I'm trying to solve the task via opencv and python. I have tried several approaches described in the tutorial of opencv library but I did not get the appropriate result.
Consider: I need to determine the exact positions of centers of the crosses (which I can do with about pixel accuracy by hand). The best result I have obtained via findContours function. I have adopted code from the tutorial and I got:
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
import random
random.seed(42)
img = cv.imread("sample.png")
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img_gray = cv.blur(img_gray, (3,3))
threshold = 150
dst = cv.Canny(img_gray, threshold, threshold * 2)
_, contours, hierarchy = cv.findContours(dst, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
result = np.zeros((dst.shape[0], dst.shape[1], 3), dtype=np.uint8)
for i in range(len(contours)):
color = (random.randint(0, 256), random.randint(0, 256), random.randint(0, 256))
cv.drawContours(result, contours, i, color, 2, cv.LINE_8, hierarchy, 0)
cv.imwrite("result.png", result)
fig, ax = plt.subplots()
fig.set_size_inches(10, 10);
ax.imshow(result, interpolation='none', cmap='gray');
which results in: Now I'm confused with the following steps. How can I define which contour is cross and which is not? What to do with crosses consisting of multiple contours?
Any help is really appreated!
A simple way on which you can determine what is a cross and what isn't is by making a bouning box x,y,w,h = cv2.boundingRect(cnt) over each contour and selecting those that have h (height) and w (weight) bigger than treshold you provide. If you observe the noises on the image arent as big as the crosses.
I have also made an example on how I would try to tackle such a task. You can try denoising the image by performing histogram equalization followed by thresholding with OTSU threshold and performing an opening to the threshold (erosion followed by dilation). Then you can filter out crosses with height and weight of the contour and then calculate the middle points of every bounding box of the contours that is in the mentioned criteria. Hope it helps a bit. Cheers!
Example:
import cv2
import numpy as np
img = cv2.imread('croses.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
equ = cv2.equalizeHist(gray)
_, thresh = cv2.threshold(equ,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
kernel = np.ones((2,2),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
_, contours, hierarchy = cv2.findContours(opening,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
if w > 40 and h > 40:
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.circle(img,(int(x+(w/2)), int(y+(h/2))),3,(0,0,255),-1)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

Detecting text in an image using python

I have around 100+ images with 2 different texts on it. The images are below. one is occupied and the other is unoccupied.
So is there any way in python to differentiate these images using some code to detect the text in it?
If so I wanted to identify the occupied images and delete unoccupied images.
Since I am new to python can anyone help me in doing this?
This answer is based on the assumption that that there are only two different texts on the images as you posted in the question. So I assume that the number of characters and the color of the text is always the same ("Room status: Unoccupied" and "Room status" Occupied" in red color). That being said, I would try a more simple way to differentiate between these two different types. These images contain caracters that are very near to each other so in my opinion is that it would be very difficult to seperate each character and identify it with an OCR. I would try a more simple approach like finding the area containg the text and find the pure lenght of the text - "unoccupied" has two more characters in the text as "occupied" and hence has a bigger distance in lenght. So you can transform the image to HSV color space and use the cv2.inRange() function to extract the text (red color). Then you can merge the characters to one contour with cv2.morphologyEx() and get its lenght with cv2.minAreaRect(). Hope it helps or at least gives you a new perspective on how to find your solution. Cheers!
Example code:
import cv2
import numpy as np
# Read the image and transform to HSV colorspace.
img = cv2.imread('ocupied.jpg')
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Extract the red text.
lower_red = np.array([0,150,50])
upper_red = np.array([40,255,255])
mask_red = cv2.inRange(hsv, lower_red, upper_red)
# Search for contours on the mask.
_, contours, hierarchy = cv2.findContours(mask_red,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
# Create a new mask for further processing.
mask = np.ones(img.shape, np.uint8)*255
# Draw contours on the mask with size and ratio of borders for threshold (to remove other noises from the image).
for cnt in contours:
size = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
if 10000 > size > 50 and w*2.5 > h:
cv2.drawContours(mask, [cnt], -1, (0,0,0), -1)
# Connect neighbour contours and select the biggest one (text).
kernel = np.ones((50,50),np.uint8)
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
gray_op = cv2.cvtColor(opening, cv2.COLOR_BGR2GRAY)
_, threshold_op = cv2.threshold(gray_op, 150, 255, cv2.THRESH_BINARY_INV)
_, contours_op, hierarchy_op = cv2.findContours(threshold_op, cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours_op, key=cv2.contourArea)
# Create rotated rectangle to get the 4 points of the rectangle.
rect = cv2.minAreaRect(cnt)
# Create bounding and calculate the "lenght" of the text.
box = cv2.boxPoints(rect)
a, b, c, d = box = np.int0(box)
bound =[]
bound.append(a)
bound.append(b)
bound.append(c)
bound.append(d)
bound = np.array(bound)
(x1, y1) = (bound[:,0].min(), bound[:,1].min())
(x2, y2) = (bound[:,0].max(), bound[:,1].max())
# Draw the rectangle.
cv2.rectangle(img,(x1,y1),(x2,y2),(0,255,0),1)
# Identify the room status.
if x2 - x1 > 200:
print('unoccupied')
else:
print('occupied')
# Display the result
cv2.imshow('img', img)
Result:
occupied
unoccupied
Using the tesseract OCR Engine and the python wrapper pytesseract, it is only a few lines' task:
import pytesseract
from PIL import Image
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe"
img = Image.open('D:\\tmp2.jpg').crop((0,0,250,35))
print(pytesseract.image_to_string(img, config='--psm 7'))
I have tested this on Windows 7. Of course, I have assumed that the text appears at the same position in every image (from your example, it does seem to be the case). Else, you need to find a better cropping mechanism.

OpenCV - Smoothing borders

I want to stitch multiple image patches to a new and mainly gray background image. The image patches contain colored elements which shall not be changed, if possible. Their shape and color is diverse. Like the new background image the borders of the image patches are also gray, just slightly different, but you can see strong borders if I just go by
ImgPatch = cv2.imread("C://...//ImagePatch.png")
NewBackground = cv2.imread("C://...//NewBackground.png")
height, width, channels = ImgPatch.shape
NewBackground[y:y+height,x:x+width] = ImgPatch
I tried cv2.seamlessClone() (docs.opencv.org) as explained in this tutorial:
www.learnopencv.com/seamless-cloning-using-opencv-python-cpp
The edges are perfectly smoothed, but unfortunately the colors of the elements are changed way too much. I know the approximate width and height of the gray border of each image patch. If i could specifically smooth that area that may be a start and lets the result look already better than what I have. I tried different masks with cv2.seamlessClone(), of which none of the tried ways workes. So unfortunately I couldn't find a correct way to blend only the border of the patches so far.
The following images visualize my problem in a very abstract way.
What I have:
Left: Background, Right: Image patch
What I want:
What I currently get by using cv2.seamlessClone():
Any help would be very much appreciated!
EDIT As I probably was not clear enough: The real images are way more complex and so unfortunately I can not get reasonable results for all image patches by using cv2.findContour... What I am looking for is a method to merge the borders, so you can not see the exact transition of patch to background anymore.
patch = cv2.imread('patch.png', cv2.IMREAD_UNCHANGED);
image = cv2.imread('image.png', cv2.IMREAD_UNCHANGED);
mask = 255 * np.ones(patch.shape, patch.dtype)
width, height, channels = image.shape
center = (height//2, width//2)
mixed_clone = cv2.seamlessClone(patch, image, mask, center, cv2.cv2.NORMAL_CLONE)
You could try to find contour in your image patch with cv2.findContour() (red spot). Then remove the background of the contour and save the image. You can finally combine the one you saved (red spot without background) with the gray background image with cv2.add(). I have combined some code I once played with and the code in OpenCV docs (for cv2.add()). Hope it helps a bit (Note the example ads the image in upper left corner - if you want elswhere you should change the code). Cheers!
Example:
import cv2
import numpy as np
from PIL import Image
img = cv2.imread('background2.png', cv2.IMREAD_UNCHANGED)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY_INV)
height,width = gray.shape
mask = np.zeros((height,width), np.uint8)
_, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
cv2.drawContours(mask,[cnt], -1, (255,255,255),thickness=-1)
masked = cv2.bitwise_and(img, img, mask=mask)
_,thresh = cv2.threshold(mask,1,255,cv2.THRESH_BINARY)
contours = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
x,y,w,h = cv2.boundingRect(contours[0])
circle = masked[y:y+h,x:x+w]
cv2.imwrite('temp.png', circle)
cv2.waitKey(0)
cv2.destroyAllWindows()
img = Image.open('temp.png')
img = img.convert("RGBA")
datas = img.getdata()
newData = []
for item in datas:
if item[0] == 0 and item[1] == 0 and item[2] == 0:
newData.append((255, 255, 255, 0))
else:
newData.append(item)
img.putdata(newData)
img.save('background3.png', "PNG")
img1 = cv2.imread('background1.png')
img2 = cv2.imread('background3.png')
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]
img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 110, 255, cv2.THRESH_BINARY_INV)
mask_inv = cv2.bitwise_not(mask)
img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
img2_fg = cv2.bitwise_and(img2,img2,mask = mask)
dst = cv2.add(img1_bg,img2_fg)
img1[0:rows, 0:cols] = dst
cv2.imshow('img',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

Categories