How to count objects in image using python? - python

I am trying to count the number of drops in this image and the coverage percentage of the area covered by those drops.
I tried to convert this image into black and white, but the center color of those drops seems too similar to the background. So I only got something like the second picture.
Is there any way to solve this problem or any better ideas?
Thanks a lot.

You can fill the holes of your binary image using scipy.ndimage.binary_fill_holes. I also recommend using an automatic thresholding method such as Otsu's (avaible in scikit-image).
from skimage import io, filters
from scipy import ndimage
import matplotlib.pyplot as plt
im = io.imread('ba3g0.jpg', as_grey=True)
val = filters.threshold_otsu(im)
drops = ndimage.binary_fill_holes(im < val)
plt.imshow(drops, cmap='gray')
plt.show()
For the number of drops you can use another function of scikit-image
from skimage import measure
labels = measure.label(drops)
print(labels.max())
And for the coverage
print('coverage is %f' %(drops.mean()))

I used the following code to detect the number of contours in the image using OpenCV and python.
import cv2
import numpy as np
img = cv2.imread('ba3g0.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,127,255,1)
contours,h = cv2.findContours(thresh,1,2)
for cnt in contours:
cv2.drawContours(img,[cnt],0,(0,0,255),1)
For further removing the contours inside another contour, you need to iterate over the entire list and compare and remove the internal contours. After that, the size of "contours" will give you the count

The idea is to isolate the background form the inside of the drops that look like the background.
Therefore i found the connected components for the background and the inside drops took the largest connected component and change its value to be like the foreground value which left me with an image which he inside drops as a different value than the background.
Than i used this image to fill in the original threshold image.
In the end using the filled image i calculated the relevant values
import cv2
import numpy as np
from matplotlib import pyplot as plt
# Read image
I = cv2.imread('drops.jpg',0);
# Threshold
IThresh = (I>=118).astype(np.uint8)*255
# Remove from the image the biggest conneced componnet
# Find the area of each connected component
connectedComponentProps = cv2.connectedComponentsWithStats(IThresh, 8, cv2.CV_32S)
IThreshOnlyInsideDrops = np.zeros_like(connectedComponentProps[1])
IThreshOnlyInsideDrops = connectedComponentProps[1]
stat = connectedComponentProps[2]
maxArea = 0
for label in range(connectedComponentProps[0]):
cc = stat[label,:]
if cc[cv2.CC_STAT_AREA] > maxArea:
maxArea = cc[cv2.CC_STAT_AREA]
maxIndex = label
# Convert the background value to the foreground value
for label in range(connectedComponentProps[0]):
cc = stat[label,:]
if cc[cv2.CC_STAT_AREA] == maxArea:
IThreshOnlyInsideDrops[IThreshOnlyInsideDrops==label] = 0
else:
IThreshOnlyInsideDrops[IThreshOnlyInsideDrops == label] = 255
# Fill in all the IThreshOnlyInsideDrops as 0 in original IThresh
IThreshFill = IThresh
IThreshFill[IThreshOnlyInsideDrops==255] = 0
IThreshFill = np.logical_not(IThreshFill/255).astype(np.uint8)*255
plt.imshow(IThreshFill)
# Get numberof drops and cover precntage
connectedComponentPropsFinal = cv2.connectedComponentsWithStats(IThreshFill, 8, cv2.CV_32S)
NumberOfDrops = connectedComponentPropsFinal[0]
CoverPresntage = float(np.count_nonzero(IThreshFill==0)/float(IThreshFill.size))
# Print
print "Number of drops = " + str(NumberOfDrops)
print "Cover precntage = " + str(CoverPresntage)

Solution
image = cv2.imread('image path.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# (thresh, blackAndWhiteImage) = cv2.threshold(gray, 127, 255,
cv2.THRESH_BINARY)
plt.imshow(gray, cmap='gray')
blur = cv2.GaussianBlur(gray, (11, 11), 0)
plt.imshow(blur, cmap='gray')
canny = cv2.Canny(blur, 30, 40, 3)
plt.imshow(canny, cmap='gray')
dilated = cv2.dilate(canny, (1, 1), iterations=0)
plt.imshow(dilated, cmap='gray')
(cnt, hierarchy) = cv2.findContours(
dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
cv2.drawContours(rgb, cnt, -1, (0, 255, 0), 2)
plt.imshow(rgb)
print("No of circles: ", len(cnt))

Related

How to merge bounding boxes to detect group of spots?

I have a code that detects sunspots but I want to count the number of sunspot groups instead of the individual spots like the current output I have here ("actual").
Here is my code. How do I get my output to group the sunspots and look like this ("desired") instead?
import os
import cv2 # opencv library
import numpy as np
import matplotlib.pyplot as plt
"""Make the pwd implementation"""
cwd = os.getcwd()
file = "/sunspot1.jpg"
path = cwd + file
image = cv2.imread(path,0)
image_1 = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
#plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
#plt.show()
#plot the image in graycolor
#gray = cv2.cvtColor(image,cv2.COLOR_BGR2HSV)
#plt.imshow(gray)
#plt.show()
# perform image thresholding
ret, thresh = cv2.threshold(image, 90, 255, cv2.THRESH_BINARY)
#plt.imshow(thresh, cmap = 'gray')
#plt.show()
#circle = cv2.circle(thresh, (249,249),(238),(0, 255, 0),1)
# plt.imshow(circle)
# plt.show()
# find taches contours
contours, hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
valid_cntrs = []
for i,cntr in enumerate(contours):
x,y,w,h = cv2.boundingRect(cntr)
#print("x = ",x,"y = ",y,"w = ",w,"h = ",h)
if ((x-249)**2 + (y-249)**2)<= 238**2:
valid_cntrs.append(cntr)
"""implement image size detection for the contour LINE 36"""
#count the taches number
taches= len(valid_cntrs);
#sunspot= 1*(10*groups+taches);
# count the number of dicovered sunspots
print("The number of taches is: ",taches)
if taches == 0:
plt.imshow(image_1)
plt.show()
else:
contour_sizes = [(cv2.contourArea(contour), contour) for contour in valid_cntrs]
for i in range(len(valid_cntrs)):
x,y,w,h = cv2.boundingRect(contour_sizes[i][1])
prevtaches = cv2.rectangle(image_1,(x,y),(x+w,y+h),(0,255,0),1)
plt.imshow(prevtaches)
plt.show()
Actual:
Desired:
What you can do is after thresholding (and before detecting the individual contours), you can perform morphological operations like dilation, to make the white area more broader, such that the nearby ones get connected and make one big contour. To do that, you can adjust you kernel size to fit your needs in the best way and can also play with the iterations argument.
You can refer this
After that, you can draw your contours.

How can I speed up this image mask creation process in python?

I need to create masks for 100.000 images, this code runs on cpu and creates ~500 masks a hour. Is there a way I can speed this up either by parallelising or running code on gpu? I'm okay with solutions that make me heavily rewrite code as long as it speeds up the process.
I tried compiling opencv library myself with cuda support, however I couldn't get most of cv2 methods I use here to run on gpu.
This is my code
Edit #1
Added import list and comments to code.
Added input and output images.
import cv2
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import glob
import sys
import os
import skimage.color
import skimage.filters
import skimage.io
import skimage.viewer
grayScale = cv2.imread(filename,cv2.IMREAD_REDUCED_GRAYSCALE_4)#read image as grayscale with size reduction
kernel = cv2.getStructuringElement(1,(17,17))
blackhat = cv2.morphologyEx(grayScale, cv2.MORPH_BLACKHAT, kernel)
ret,thresh2 = cv2.threshold(blackhat,10,255,cv2.THRESH_BINARY)
dst = cv2.inpaint(newimg,thresh2,1,cv2.INPAINT_TELEA) #4 lines above are used to remove hair from image
mask = np.zeros(dst.shape[:2],np.uint8)
h,w,c = dst.shape
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (int(0.1*w),int(0.1*h),int(0.8*w),int(0.8*h))
cv2.grabCut(dst,mask,rect,bgdModel,fgdModel,1,cv2.GC_INIT_WITH_RECT) #removes some background from image
#code for k means clustering starts here
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
dst = dst*mask2[:,:,np.newaxis]
vectorized = dst.reshape((-1,3))
vectorized = np.float32(vectorized)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) #11 lines above are used to remove some background from image
K = 4
attempts=1
ret,label,center=cv2.kmeans(vectorized,K,None,criteria,attempts,cv2.KMEANS_PP_CENTERS)
center = np.uint8(center)
labels = label.flatten()
res = center[label.flatten()]
result_image = res.reshape((dst.shape)) #k means clustering ends here
gray = cv2.cvtColor(result_image, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 10, 20, cv2.THRESH_BINARY)
result_image[thresh == 0] = 255
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
erosion = cv2.erode(result_image, kernel, iterations = 1)
blur = skimage.color.rgb2gray(erosion)
blur = skimage.filters.gaussian(blur, sigma=float(1))
histogram, bin_edges = np.histogram(blur, bins=256, range=(0, 1))
index = next((i for i, x in enumerate(histogram) if x), None)
mask = blur > bin_edges[index+1] #10 lines above are used to create mask
mask = abs(mask-255) #inverts mask
array = np.array(mask, dtype='uint8')
finimg = cv2.resize(array,None,fx=4.0,fy=4.0) #returns image to original size
plt.imsave("Masks/"+filename, finimg, cmap = plt.cm.gray) #saves result image
input image - skin mole image
output image - mask of skin mole
You might try using kmeans processing in Python/Opencv as a first step. Then get the inner contour and use that for your mask. Draw the inner contour as white filled on a black background. You may need to use morphology to clean the kmeans results first
Input:
Kmeans 2:
Kmeans 3:
Kmeans 4:

How to find only the bolded text lines from no. of images

from PIL import Image
import pytesseract
from pdf2image import convert_from_path
import os
import pandas as pd
import cv2
import numpy as np
files = os.chdir("C:/Users/abhishek_kumar1/Desktop/New folder")
#print(os.getcwd())
pages = convert_from_path("d.pdf",190,single_file=True,
poppler_path='C:/Users/abhishek_kumar1/Downloads/poppler-0.68.0_x86/poppler-0.68.0/bin')
image_counter=1
for page in pages:
filename = "page_"+str(image_counter)+".jpg"
page.save(filename,'JPEG')
img = cv2.imread(filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite('grey.png',gray)
binary,thresh1 = cv2.threshold(gray, 0, 255,cv2.THRESH_OTSU|cv2.THRESH_BINARY_INV)
cv2.imwrite('Thresh1.png',thresh1)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
dilation = cv2.dilate(thresh1, rect_kernel, iterations = 6)
contours, hierarchy = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
im2 = img.copy()
ROI_number = 0
for cnt in contours[::-1]:
[x,y,w,h] = cv2.boundingRect(cnt)
ROI=im2[y:y+h, x:x+w]
#print(str(w),str(h))
#cv2.putText(im2, str(h), (x,y - 10 ), cv2.FONT_HERSHEY_SIMPLEX, 0.1, (255, 0, 0), 1)
#cv2.putText(im2, str(w), (x,y + 10 ), cv2.FONT_HERSHEY_SIMPLEX, 0.1, (0, 0, 255), 1)
cv2.imwrite('ROI_{}.jpg'.format(ROI_number),ROI)
cv2.rectangle(im2,(x,y),(x+w,y+h),(36,255,12),1)
ROI_number += 1
cv2.imwrite('contours1.png',im2)
How to find only this image from above code section section, is there any options to understand font type from image like bold, italic,something else
get trouble to find only the bold line part from all of images.
Please any body have a suggestion regarding this please help me out.
Alex Alex's answer did not work for me. Here is my alternative described in words.
The general idea is that we compare how many black pixels there are in comparison to the minimum possible pixels to still form characters. This provides us with a difference from the skeleton to normal text and skeleton to bold text. In this way, we can quite clearly separate normal text from the bold text.
Use OCR software to extract bounding boxes of individual words. Optional: Combine individual words into lines of words, for example by word_num in Pytesseract.
Convert the image to grayscale and invert the image colors
Perform Zhang-Suen thinning on the selected area of text on the image (opencv contribution: cv2.ximgproc.thinning)
Sum where there are white pixels in the thinned image, i.e. where values are equal to 255 (white pixels are letters)
Sum where there are white pixels in the inverted image
Finally compute the thickness (sum_inverted_pixels - sum_skeleton_pixels) / sum_skeleton_pixels (sometimes there will be zero division error, check when the sum of the skeleton is 0 and return 0 instead)
Normalize the thickness by minimum and maximum values
Apply a threshold for deciding when a word/line of text is bold, e.g. 0.6 or 0.7
See python code and result:
import cv2
import numpy as np
img = cv2.imread('C.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 160, 255, cv2.THRESH_BINARY)[1]
kernel = np.ones((5,5),np.uint8)
kernel2 = np.ones((3,3),np.uint8)
marker = cv2.dilate(thresh,kernel,iterations = 1)
mask=cv2.erode(thresh,kernel,iterations = 1)
while True:
tmp=marker.copy()
marker=cv2.erode(marker, kernel2)
marker=cv2.max(mask, marker)
difference = cv2.subtract(tmp, marker)
if cv2.countNonZero(difference) == 0:
break
marker_color = cv2.cvtColor(marker, cv2.COLOR_GRAY2BGR)
out=cv2.bitwise_or(img, marker_color)
cv2.imwrite('out.png', out)
cv2.imshow('result', out )

How to get RGB values of two separate lines in an image in two variables using opencv python

I have detected two lines in an image using cv2. now I want to get the RGB values of both lines in separate variables like left_line_veriable = ['rgb values'], right_line_rgb_values = ['rgb values']
Here is my code:
import cv2
import numpy as np
image = cv2.imread('tape.png')
image = cv2.cvtCOLOR(image, cv2.COLOR_BGR2GRAY)
# Apply adaptive threshold
image_thr = cv2.adaptiveThreshold(image, 255, cv2.THRESH_BINARY_INV, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 81, 2)
# Apply morphological opening with vertical line kernel
kernel = np.ones((image.shape[0], 1), dtype=np.uint8) * 255
image_mop = cv2.morphologyEx(image_thr, cv2.MORPH_OPEN, kernel)
color_detected_img = cv2.bitwise_and(image, image, mask=image_mop)
cv2.imshow('image', color_detected_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
This is the image from which I want to get both line's RGB values in two variables as described above:
Maybe is not the most optimal way, but it is not hard to do. As I said in my comments, you can label the image to kind of segment the lines, then get the mean of the rgb values in it and the average position to get to know which one is left and right. Here is a small script to demonstrate what I am saying. The last part is just to show the results.
import cv2
import numpy as np
# load img and get the greyscale
img = cv2.imread("x.png")
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# label the image
ret, thres = cv2.threshold(grey, 1, 255, cv2.THRESH_BINARY)
labelAmount, labels = cv2.connectedComponents(thres)
# get the mean of the color and position
values = []
# first label (0) is background
for i in range(1, labelAmount):
mask = np.zeros(labels.shape, dtype=np.uint8)
mask[labels == i] = 255
mean = cv2.mean(img, mask)[:-1]
meanPos = np.mean(cv2.findNonZero(mask), axis=0)[0]
values.append((mean, meanPos))
# sort them by x value (left to right)
values = sorted(values, key = lambda v : v[1][0])
left_line_color = values[0][0]
right_line_color = values[1][0]
# just to show the results
left_only = np.zeros(img.shape, dtype=np.uint8)
right_only = np.zeros(img.shape, dtype=np.uint8)
left_only = cv2.line (left_only, (int(values[0][1][0]), 0), (int(values[0][1][0]), img.shape[0]), left_line_color,5 )
right_only = cv2.line (right_only, (int(values[1][1][0]), 0), (int(values[1][1][0]), img.shape[0]), right_line_color,5 )
cv2.imshow("left_line", left_only)
cv2.imshow("right_line", right_only)
cv2.imshow("original", img)
cv2.waitKey(0)

OpenCV GrabCut Remove Background

I have been able to remove about 75% of the background from my original image, but I am struggling to fine tune my python code to remove the last bit.
Original Image
Output Image
As you can see there is one section of the background on the lower half of the image that isn't being removed along with the rest.
import os, time
import numpy as np
import cv2
import matplotlib.pyplot as plt
org_file_name = 'IMG_3237_reduced.jpg'
#Read Image File
img = cv2.imread(org_file_name))
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (1,1,1008,756)
rect2 = (11,222,975, 517)
# Perform the GrabCut on the Image File
t1 = time.clock()
cv2.grabCut(img,mask,rect2,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
t2 = time.clock()
print(t2-t1)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
gc_img = img*mask2[:,:,np.newaxis]
# convert to grayscale
gc_img_gray = cv2.cvtColor(gc_img, cv2.COLOR_BGR2GRAY)
_,alpha = cv2.threshold(gc_img_gray,0,255,cv2.THRESH_BINARY)
b, g, r = cv2.split(gc_img)
rgba = [b,g,r, alpha]
gc_split_img = cv2.merge(rgba,4)
# display results
#ax1 = plt.subplot(131); plt.imshow(img)
#ax1.set_title('Original')
#ax2 = plt.subplot(132); plt.imshow(gc_img)
#ax2.set_title('GrabCut')
ax3 = plt.subplot(111); plt.imshow(gc_split_img)
ax3.set_title('GrabCut Split')
plt.show()
I've attached the my working code above. I appreciate any help someone can offer. My plan is once the background is removed, I can do some analysis/statical modeling on the region of interest for further comparison.
If this is a one time process, I don't think you need to use grabcut. I'd suggest something like this, where you use a combination of spatial and simple code value thresholding:
import cv2
import numpy
# Read Image File
img = cv2.imread('NY3Ne.jpg')
# convert RGB to grayscale image
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
mask, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
rect_x = [15, 990]
rect_y = [220, 530]
y, x = numpy.indices(img.shape[:2])
# threshold based on otsu's method
img[mask < thresh] = 0
# set everything outside the rectangle to 0
img[(x < rect_x[0])] = 0
img[(x > rect_x[1])] = 0
img[(y < rect_y[0])] = 0
img[(y > rect_y[1])] = 0
cv2.imshow('masked', img)
cv2.waitKey(0)
But if grabcut is necessary for some other reason, I could combine it with simple thresholding to get the desired result. 150 is kind of arbitrary based on your image, but you could substitute Otsu or any other adaptive binary threshold calculation method.
alpha = np.where(gc_img_gray < 150, 255, 0).astype(np.uint8)
gc_img[alpha==0] = 0
More info: https://docs.opencv.org/3.4.0/d7/d4d/tutorial_py_thresholding.html

Categories

Resources