How can I speed up this image mask creation process in python? - 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:

Related

How to mask out of focus area in a photo?

I've tried to use the Laplace filter for the highest sharpness and focus detection and it works fine, but
I don't know how to use that to mask the blurred area on the original image.
I have this piece of code so far:
import numpy as np
import cv2
imgPath = r"IMG_0727.jpg"
img = cv2.imread(imgPath)
imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
def laplaceFilter(image):
kernel_size = 5
blur_size = 5
blurred = cv2.GaussianBlur(image, (blur_size,blur_size), 0)
return cv2.Laplacian(blurred, cv2.CV_64F, ksize=kernel_size)
cv2.imwrite("laplaceFilter.png", laplaceFilter(imgGray))
Here is what I could generate with gaussian blur + threshold but it's not very accurate, maybe there is a better way to do that.

OpenCV python overlapping particles size and number

I got greyscale images which show particles on a surface. I like to write a program which finds the particles draws a circle around and gives counts the circles and the pixels inside the circles.
One of the main problems is that the particles overlapp. The next problem is that the contrast of the images is changing, from one image to the next.
Here is my first trial:
import matplotlib.pyplot as plt
import cv2 as cv
import imutils
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import os.path
fileref="test.png"
original = cv.imread(fileref)
img = original
cv.imwrite( os.path.join("inverse_"+fileref[:-4]+".png"), ~img );
img = cv.medianBlur(img,5)
img_grey = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret,th1 = cv.threshold(img_grey,130,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img_grey,255,cv.ADAPTIVE_THRESH_MEAN_C,\
cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img_grey,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1]
cv.imwrite( os.path.join("threshhold_"+fileref[:-4]+".jpg"), th1 );
cv.imwrite( os.path.join("adaptivthreshhold-m_"+fileref[:-4]+".jpg"), th2 );
cv.imwrite( os.path.join("adaptivthreshhold-g_"+fileref[:-4]+".jpg"), th3 );
imghsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
imghsv[:,:,2] = [[max(pixel - 25, 0) if pixel < 190 else min(pixel + 25, 255) for pixel in row] for row in imghsv[:,:,2]]
cv.imshow('contrast', cv.cvtColor(imghsv, cv.COLOR_HSV2BGR))
# Setup SimpleBlobDetector parameters.
params = cv.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold = 0
params.maxThreshold = 150
# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0.87
# Filter by Inertia
params.filterByInertia = True
params.minInertiaRatio = 0.08 # 0.08
# Set edge gradient
params.thresholdStep = 0.5
# Filter by Area.
params.filterByArea = True
params.minArea = 300
# Set up the detector with default parameters.
detector = cv.SimpleBlobDetector_create(params)
# Detect blobs.
keypoints = detector.detect(original)
# Draw detected blobs as red circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures the size of the circle corresponds to the size of blob
im_with_keypoints = cv.drawKeypoints(original, keypoints, np.array([]), (0, 0, 255),
cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
print(len(keypoints))
# Show keypoints
display=cv.resize(im_with_keypoints,None,fx=0.5,fy=0.5)
cv.imshow("Keypoints", display)
cv.waitKey(0)
cv.imwrite( os.path.join("keypoints_"+fileref[:-4]+".jpg"), im_with_keypoints );
It circles most particles but the parameters need to be changed for each image to get better results the circles can't overlapp and I don't know how to count the circles or count the pixels inside the circles.
Any help or hints which point me in the right direction are much appreciated.
I added a couple sample pics
This is an alternative approach and may not necessarily give better results than what you already have. You can try out plugging in different values for parameters and see if it gives you acceptable results.
import numpy as np
import cv2
import matplotlib.pyplot as plt
rgb = cv2.imread('/your/image/path/blobs_0002.jpeg')
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
imh, imw = gray.shape
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,21,2)
th = cv2.adaptiveThreshold(gray,255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,15,15)
contours, hier = cv2.findContours(th.copy(),cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
out_img = rgb.copy()
for i in range(len(contours)):
if hier[0][i][3] != -1:
continue
x,y,w,h = cv2.boundingRect(contours[i])
ar = min(w,h)/max(w,h)
area = cv2.contourArea(contours[i])
extent = area / (w*h)
if 20 < w*h < 1000 and \
ar > 0.5 and extent > 0.4:
cv2.circle(out_img, (int(x+w/2), int(y+h/2)), int(max(w,h)/2), (255, 0, 0), 1)
plt.imshow(out_img)
For larger coalesced blobs you might try running Hough circles to see if partial contours fit the test. Just a thought. Just to acknowledge the fact that the images you are dealing with are challenging to come up with a clean solution.

Remove background

I am doing OCR to extract information from the ID card. However, accuracy is quite low.
My assumption is that removing the background will make OCR more accurate.
I use the ID scanner machine (link) to obtain the grey image below. It seems that the machine uses IR instead of image processing.
Does anyone knows how to get the same result by using Opencv or tools (photoshop, gimp, etc)?
Thanks in advance.
Here are two more methods: adaptive thresholding and division normalization.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("green_card.jpg")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# do adaptive threshold on gray image
thresh1 = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 51, 25)
# write results to disk
cv2.imwrite("green_card_thresh1.jpg", thresh1)
# apply morphology
kernel = cv2.getStructuringElement(cv2.MORPH_RECT , (11,11))
morph = cv2.morphologyEx(gray, cv2.MORPH_DILATE, kernel)
# divide gray by morphology image
division = cv2.divide(gray, morph, scale=255)
# threshold
thresh2 = cv2.threshold(division, 0, 255, cv2.THRESH_OTSU )[1]
# write results to disk
cv2.imwrite("green_card_thresh2.jpg", thresh2)
# display it
cv2.imshow("thresh1", thresh1)
cv2.imshow("thresh2", thresh2)
cv2.waitKey(0)
Adaptive Thresholding Result:
Division Normalization Result:
EDIT:
since there are different lighting conditions, contrast adjustment is added here.
The simple approache in my mind to solve your issue is that: since the undesired background colours are Green and Red, and the desired font colour is Black, simply suppress the Red and green colours as following:
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imread, imsave
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
from skimage import exposure
def adjustContrast(img):
p2, p98 = np.percentile(img, (2, 98))
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))
return img_rescale
# Read the image
img = imread('ID_OCR.jpg')
# Contrast Adjustment for each channel
img[:,:,0] = adjustContrast(img[:,:,0]) # R
img[:,:,1] = adjustContrast(img[:,:,1]) # G
img[:,:,2] = adjustContrast(img[:,:,2]) # B
# # Supress unwanted colors
img[img[...,0] > 100] = 255 # R
img[img[...,1] > 100] = 255 # B
# Convert the image to graylevel
img = rgb2gray(img)
# Rescale into 0-255
img = 255*img.astype(np.uint8)
# Save the results
imsave('Result.png', img)
The image will look like:
The Results are not optimal, because also your image resolution isn't high.
At the end, there are many solutions, and improvements, also you can use Morphology to make it look nicer, this is just a simple proposal to solve the problem.

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

How to count objects in image using 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))

Categories

Resources