I am trying to apply the kmeans from opencv in order to segment the image in HSV color space.
def leftOffset(src, p_countours):
height, width, size = src.shape
p_width = width/p_countours
o_left = src[0:height, 0:p_width]
HSV_img = cv2.cvtColor(o_left, cv2.COLOR_BGR2HSV)
hue = HSV_img[0]
hue = np.float32(HSV_img)
# Define criteria = ( type, max_iter = 10 , epsilon = 1.0 )
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
# Set flags (Just to avoid line break in the code)
flags = cv2.KMEANS_RANDOM_CENTERS
# Apply KMeans
compactness,labels,centers = cv2.kmeans(hue,2,criteria,10,flags)
centers = np.uint8(centers)
res = centers[labels.flatten()]
res2 = res.reshape((hue.shape))
cv2.imshow("o_left", hue)
cv2.waitKey(0)
I am now able to apply the kmeans algorithm to the HSVImage[0] with K=2, and how can I get a image like threshold according to the result?
Thanks
To clarify the question:
I have color-based captchas, and I want to segment each digits.
The image is like
I am going to use k-means method to find out the dominant color and segment the digits inside.
1) If all you need is to find the dominant color why not find the histograms of each color channel? Find the dominant channel then segment only that channel using otsu? For example if I threshold only the hue I can get nice results. K-means could be an overkill for this task:
import cv2
import numpy as np
import matplotlib.pylab as plt
## Simple Otsu over hue
six = cv2.imread('7zovC.jpg')
##convert to hsv
hsv = cv2.cvtColor(six, cv2.COLOR_BGR2HSV)
hue = hsv[:, :, 0]
binary_img = cv2.threshold(hue, 128, 255, cv2.THRESH_OTSU)
plt.figure()
plt.imshow(binary_img*255)
plt.show()
2) Why not use all channels for clustering instead of just hue? What you need is clustering -> color quantization this link should be useful. This is for opencv version > 3.0.0
Note for python 2.4.11, cv2.kmeans has a slightly difference interface and you could use this instead:
def color_quantize(img, K):
Z = img.reshape((-1, 3))
# convert to np.float32
Z = np.float32(Z)
# define criteria, number of clusters(K) and apply kmeans()
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
ret, label, center = cv2.kmeans(Z, 2, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# Now convert back into uint8, and make original image
center = np.uint8(center)
res = center[label.flatten()]
quantized_img = res.reshape((img.shape))
label_img = label.reshape((img.shape[:2]))
return label_img, quantized_img
six = cv2.imread('7zovC.jpg')
##convert to hsv
hsv = cv2.cvtColor(six, cv2.COLOR_BGR2HSV)
K = 2
label_img, six_q = color_quantize(hsv, K)
plt.figure()
plt.imshow(label_img)
plt.show()
My results for color quantization were not impressive.
May I suggest a conventional alternative? I you get rid of the very dark and bright regions first, you may be able to simply rely on the most frequent value of the hue component calculated from the histogram.
Mind that the borders of the numbers will never be absolutely exact, since colours are similar in the surrounding.
Furthrmore, you could select the maximum blob only (according to size) to suppress remaining small blobs outside.
Results:
Code:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('image1.jpg')
#get rid of very bright and very dark regions
delta=30
lower_gray = np.array([delta, delta,delta])
upper_gray = np.array([255-delta,255-delta,255-delta])
# Threshold the image to get only selected
mask = cv2.inRange(img, lower_gray, upper_gray)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(img,img, mask= mask)
#Convert to HSV space
HSV_img = cv2.cvtColor(res, cv2.COLOR_BGR2HSV)
hue = HSV_img[:, :, 0]
#select maximum value of H component from histogram
hist = cv2.calcHist([hue],[0],None,[256],[0,256])
hist= hist[1:, :] #suppress black value
elem = np.argmax(hist)
print np.max(hist), np.argmax(hist)
tolerance=10
lower_gray = np.array([elem-tolerance, 0,0])
upper_gray = np.array([elem+tolerance,255,255])
# Threshold the image to get only selected
mask = cv2.inRange(HSV_img, lower_gray, upper_gray)
# Bitwise-AND mask and original image
res2 = cv2.bitwise_and(img,img, mask= mask)
titles = ['Original Image', 'Selected Gray Values', 'Hue', 'Result']
images = [img, res, hue, res2]
for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
Related
I am trying to remove the checkered background (which represents transparent background in Adobe Illustrator and Photoshop) with transparent color (alpha channel) in some PNGs with Python script.
First, I use template matching:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img_rgb = cv2.imread('testimages/fake1.png', cv2.IMREAD_UNCHANGED)
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('pattern.png', 0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
if len(img_rgb[0][0]) == 3:
# add alpha channel
rgba = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2RGBA)
rgba[:, :, 3] = 255 # default not transparent
img_rgb = rgba
# replace the area with a transparent rectangle
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255, 255, 255, 0), -1)
cv2.imwrite('result.png', img_rgb)
Source Image: fake1.png
Pattern Template: pattern.png
Output: result.png (the gray area is actually transparent; enlarge a bit for viewing easier)
I know this approach has problems, as the in some cases, the template cannot be identified fully, as part of the pattern is hidden by the graphics in the PNG image.
My question is: How can I match such a pattern perfectly using OpenCV? via FFT Filtering?
References:
How particular pixel to transparent in opencv python?
Detecting a pattern in an image and retrieving its position
https://python.plainenglish.io/how-to-remove-image-background-using-python-6f7ffa8eab15
https://answers.opencv.org/question/232506/make-the-background-of-the-image-transparent-using-a-mask/
https://dsp.stackexchange.com/questions/36679/which-image-filter-can-be-applied-to-remove-gridded-pattern-from-corrupt-jpegs
Here is one way to do that in Python/OpenCV simply by thresholding on the checks color range.
Input:
import cv2
import numpy as np
# read input
img = cv2.imread("fake.png")
# threshold on checks
low = (230,230,230)
high = (255,255,255)
mask = cv2.inRange(img, low, high)
# invert alpha
alpha = 255 - mask
# convert img to BGRA
result = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
result[:,:,3] = alpha
# save output
cv2.imwrite('fake_transparent.png', result)
cv2.imshow('img', img)
cv2.imshow('mask', mask)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Download the resulting image to see that it is actually transparent.
Here is one way to use DFT to process the image in Python/OpenCV/Numpy. One does need to know the size of the checkerboard pattern (light or dark square size).
Read the input
Separate channels
Apply DFT to each channel
Shift origin from top left to center of each channel
Extract magnitude and phase images from each channel
Define the checkerboard pattern size
Create a black and white checkerboard image of the same size
Apply similar DFT processing to the checkerboard image
Get the spectrum from the log(magnitude)
Threshold the spectrum to form a mask
Zero out the DC center point in the mask
OPTION: If needed apply morphology dilate to thicken the white dots. But does not seem to be needed here
Invert the mask so the background is white and the dots are black
Convert the mask to range 0 to 1 and make 2 channels
Apply the two-channel mask to the center shifted DFT channels
Shift the center back to the top left in each masked image
Do the IDFT to get back from complex domain to real domain on each channel
Merge the resulting channels back to a BGR image as the final reconstituted image
Save results
Input:
import numpy as np
import cv2
import math
# read input
# note: opencv fft only works on grayscale
img = cv2.imread('fake.png')
hh, ww = img.shape[:2]
# separate channels
b,g,r = cv2.split(img)
# convert images to floats and do dft saving as complex output
dft_b = cv2.dft(np.float32(b), flags = cv2.DFT_COMPLEX_OUTPUT)
dft_g = cv2.dft(np.float32(g), flags = cv2.DFT_COMPLEX_OUTPUT)
dft_r = cv2.dft(np.float32(r), flags = cv2.DFT_COMPLEX_OUTPUT)
# apply shift of origin from upper left corner to center of image
dft_b_shift = np.fft.fftshift(dft_b)
dft_g_shift = np.fft.fftshift(dft_g)
dft_r_shift = np.fft.fftshift(dft_r)
# extract magnitude and phase images
mag_b, phase_b = cv2.cartToPolar(dft_b_shift[:,:,0], dft_b_shift[:,:,1])
mag_g, phase_g = cv2.cartToPolar(dft_g_shift[:,:,0], dft_g_shift[:,:,1])
mag_r, phase_r = cv2.cartToPolar(dft_r_shift[:,:,0], dft_r_shift[:,:,1])
# set check size (size of either dark or light square)
check_size = 15
# create checkerboard pattern
white = np.full((check_size,check_size), 255, dtype=np.uint8)
black = np.full((check_size,check_size), 0, dtype=np.uint8)
checks1 = np.hstack([white,black])
checks2 = np.hstack([black,white])
checks3 = np.vstack([checks1,checks2])
numht = math.ceil(hh / (2*check_size))
numwd = math.ceil(ww / (2*check_size))
checks = np.tile(checks3, (numht,numwd))
checks = checks[0:hh, 0:ww]
# apply dft to checkerboard pattern
dft_c = cv2.dft(np.float32(checks), flags = cv2.DFT_COMPLEX_OUTPUT)
dft_c_shift = np.fft.fftshift(dft_c)
mag_c, phase_c = cv2.cartToPolar(dft_c_shift[:,:,0], dft_c_shift[:,:,1])
# get spectrum from magnitude (add tiny amount to avoid divide by zero error)
spec = np.log(mag_c + 0.00000001)
# theshold spectrum
mask = cv2.threshold(spec, 1, 255, cv2.THRESH_BINARY)[1]
# mask DC point (center spot)
centx = int(ww/2)
centy = int(hh/2)
dot = np.zeros((3,3), dtype=np.uint8)
mask[centy-1:centy+2, centx-1:centx+2] = dot
# If needed do morphology dilate by small amount.
# But does not seem to be needed in this case
# invert mask
mask = 255 - mask
# apply mask to real and imaginary components
mask1 = (mask/255).astype(np.float32)
mask2 = cv2.merge([mask1,mask1])
complex_b = dft_b_shift*mask2
complex_g = dft_g_shift*mask2
complex_r = dft_r_shift*mask2
# shift origin from center to upper left corner
complex_ishift_b = np.fft.ifftshift(complex_b)
complex_ishift_g = np.fft.ifftshift(complex_g)
complex_ishift_r = np.fft.ifftshift(complex_r)
# do idft with normalization saving as real output and crop to original size
img_notch_b = cv2.idft(complex_ishift_b, flags=cv2.DFT_SCALE+cv2.DFT_REAL_OUTPUT)
img_notch_b = img_notch_b.clip(0,255).astype(np.uint8)
img_notch_b = img_notch_b[0:hh, 0:ww]
img_notch_g = cv2.idft(complex_ishift_g, flags=cv2.DFT_SCALE+cv2.DFT_REAL_OUTPUT)
img_notch_g = img_notch_g.clip(0,255).astype(np.uint8)
img_notch_g = img_notch_g[0:hh, 0:ww]
img_notch_r = cv2.idft(complex_ishift_r, flags=cv2.DFT_SCALE+cv2.DFT_REAL_OUTPUT)
img_notch_r = img_notch_r.clip(0,255).astype(np.uint8)
img_notch_r = img_notch_r[0:hh, 0:ww]
# combine b,g,r components
img_notch = cv2.merge([img_notch_b, img_notch_g, img_notch_r])
# write result to disk
cv2.imwrite("fake_checks.png", checks)
cv2.imwrite("fake_spectrum.png", (255*spec).clip(0,255).astype(np.uint8))
cv2.imwrite("fake_mask.png", mask)
cv2.imwrite("fake_notched.png", img_notch)
# show results
cv2.imshow("ORIGINAL", img)
cv2.imshow("CHECKS", checks)
cv2.imshow("SPECTRUM", spec)
cv2.imshow("MASK", mask)
cv2.imshow("NOTCH", img_notch)
cv2.waitKey(0)
cv2.destroyAllWindows()
Checkerboard image:
Spectrum of checkerboard:
Mask:
Result (notch filtered image):
The checkerboard pattern in the result is mitigated from the original, but still there upon close inspection.
From here one needs to threshold on the white background and invert to make an image for the alpha channel. Then convert the image to 4 BGRA and insert the alpha channel into the BGRA image as I described in my other answer below.
since you're working on PNG's with transparent backgrounds, it would probably be equally viable to instead of trying to detect the checkered background, you try to extract the stuff that isn't checkered. This could probably be achieved using a color check on all pixels. You could use opencv's inRange() function. I'll link a StackOverflow link below that tries to detect dark spots on a image.
Inrange example
I used K-Means Clustering to perform segmentation on this traffic sign as shown below.
These are my code
Read image and blur
img = cv.imread('000_0001.png')
img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
kernel_size = 5
img_rgb = cv.blur(img_rgb, (kernel_size, kernel_size))
# reshape
img_reshape = img_rgb.reshape((-1, 3))
img_reshape = np.float32(img_reshape)
Perform k-means clustering
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = 4
attempts = 10
ret, label, center = cv.kmeans(img_reshape, K, None, criteria, attempts, cv.KMEANS_PP_CENTERS)
# reshape into original dimensions
center = np.uint8(center)
res = center[label.flatten()]
result = res.reshape(img_rgb.shape)
plt.figure(figsize = (15, 15))
plt.subplot(1, 2, 1)
plt.imshow(img_rgb)
plt.subplot(1, 2, 2)
plt.imshow(result)
plt.show()
create masks
from numpy import linalg as LN
red = (255, 0, 0)
Idx_min_red = np.argmin(LN.norm(center-red, axis = 1))
white = (255, 255, 255)
Idx_min_white = np.argmin(LN.norm(center-white, axis = 1))
black = (0, 0, 0)
Idx_min_black = np.argmin(LN.norm(center-black, axis = 1))
mask_red = result == center[Idx_min_red]
mask_white = result == center[Idx_min_white]
mask_black = result == center[Idx_min_black]
pre_mask = cv.bitwise_or(np.float32(mask_red), np.float32(mask_white))
mask = cv.bitwise_or(np.float32(pre_mask), np.float32(mask_black))
Segment the image
seg_img = img*(mask.astype("uint8"))
Morphological Transformation
kernel = np.ones((5, 5), dtype = np.uint8)
img_dilate = cv.morphologyEx(seg_img, cv.MORPH_OPEN, kernel)
res = np.hstack((img, img_dilate))
cv.imshow("res", res)
cv.waitKey(0)
cv.destroyAllWindows()
Question here
These codes can only segment red traffic signs, is it possible to tweak a little bit on different colours so that it can segment red, blue and yellow traffic signs? (like the one below for example)
Update, this is what I have tried:
I used a pipeline to do OR operation on all the masks
mask = mask_red | mask_white | mask_black | mask_blue
but then the new mask will fail to segment the image
Rather than K means, I'd suggest a connected components based approach to find this. These signs and symbols have relatively uniform color areas that take a fairly significant part of the image, that would result in large connected components. You can later write downstream logic to select the relevant connected components to define your sign and create a segmented image from them.
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
colors = (np.array(plt.cm.tab10.colors) * 255).astype('int')
im1 = cv2.imread('37Gu6.png')
im2 = cv2.imread('dU47J.png')
for i in [im1, im2]:
gray = cv2.cvtColor(i, cv2.COLOR_BGR2GRAY)
# Calculate image value histogram to get an adaptive threshold value based
hist = np.histogram(gray.ravel(), bins=25)
modeIdx = np.where(hist[0] == hist[0].max())[0][0]
modeVal = hist[1][modeIdx]
tVal = modeVal * 1.15 # Our threshold is 115% of the most common hist value
_, thresh = cv2.threshold(gray, tVal, 255, cv2.THRESH_BINARY)
# Find connected componenents
output = cv2.connectedComponentsWithStats(thresh, 8, cv2.CV_32S)
(numLabels, labels, stats, centroids) = output
# The 5th column in the CC stats is the area
# Find the indices of CCs with above average area
CCArea = stats[:,4]
aboveAvgLbls = np.where(CCArea > CCArea.mean())[0]
# Generate labeled output image
outputImg = np.zeros(i.shape, dtype="uint8")
for l, c in zip(aboveAvgLbls, colors):
w = np.where(labels == l)
outputImg[ w[0], w[1] ] = c
f, ax = plt.subplots(1,4,figsize=(20,5))
for a, img, t in zip(ax, [i, gray, thresh, outputImg], ['RGB', 'Gray', f'Thresh [t={tVal}]', 'Labels']):
a.imshow(img[:,:,::-1] if img.shape[-1] == 3 else img, 'gray')
a.set_title(t)
plt.show()
I'm trying to detect the optic disc in an image of the back of the eye using OpenCV and findContour, then fitEllipse, but my program isn't detecting the optic disc at all. How do I fix this? Code and images are below
import cv2
import numpy as np
from sklearn.linear_model import LinearRegression
import math
from decimal import Decimal
def find_elongation(image):
img = cv2.imread(image,0)
ret,thresh = cv2.threshold(img,127,255,0)
contour,hierarchy = cv2.findContours(thresh, 1, 2)
contours = []
for i in range(len(contour)):
if len(contour[i])>=5:
contours.append(contour[i])
cnt = contours[0]
k = cv2.isContourConvex(cnt)
ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(img,ellipse,(0,255,0),2)
(x,y),(ma,Ma),angle = cv2.fitEllipse(cnt)
return Ma/ma
print(find_elongation('eye.png'))
print(find_elongation('eye2.png'))
print(find_elongation('eye3.png'))
Image (one of them):
I'm trying to get the brightly colored circle in the middle:
Thanks for the help!
I have developed a piece of code to implement what you have asked. It mainly uses de Value channel of the HSV color space followed by some morphological operations.
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Read the image
img = cv2.imread('so.png')
# Transform the image to HSV color-space and keep only the value channel
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
# Threshold the iamge with the 95% of the brightest possible pixels
maxVal = np.max(v)
per = 0.95
_, th = cv2.threshold(v, maxVal*per, 255, cv2.THRESH_BINARY)
# Erode the image and find the connected components
th = cv2.erode(th, np.ones((2,2), np.uint8))
n, conComp, stats, centroids = cv2.connectedComponentsWithStats(th)
# Obtain the sizes of the connectedComponents skipping the background
sizes = stats[1:,-1]
# Obtain the number of the connectedComponent with biggest size
nComp = np.argmax(sizes) + 1
# Isolate the connectedComponent and apply closing
out = np.zeros((img.shape[0], img.shape[1]), np.uint8)
out[conComp==nComp] = 1
out = cv2.morphologyEx(out, cv2.MORPH_CLOSE, np.ones((10,10)))
# Apply gradient to mask to obtain the border of the ellipse
out = cv2.morphologyEx(out, cv2.MORPH_GRADIENT, np.ones((2,2)))
# Join the border of the ellipse with the image to display it
img[out==1] = (0,0,0)
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
plt.show()
I attach the output I have obtained with the picture you posted:
I'm trying to get the patched regions of a citrus fruit using Otsu method with opencv. According to this paper: https://www.intechopen.com/books/agricultural-robots-fundamentals-and-applications/multimodal-classification-of-mangoes/ these authors were using the Green channel (G) to get the patches regions of mangoes:
I'm doing the same but usign lemons but I can't get those regions of my lemon.
This is my input image:
First I read the image and I'm calling to a function to show the image:
def show(img, titulo):
plt.figure(figsize=(7,7))
plt.title(titulo)
plt.imshow(img)
plt.show()
#read img
file = "lemons/bad/bad_5.jpg"
image = cv2.imread(file)
#convert from BGR to RGB
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
original = image
show(image, "original img "+str(image.shape))
Then I Added a blur filter:
#(blur) filter
image = cv2.blur(image,(31,31),0)
show(image, "img with BLUR")
Convert to HSV:
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
#get hsv channels
h, s, v = cv2.split(hsv)
show(s, "channel S of HSV")
Then I added the Otsu's method:
#OTSU
_, thr = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
show(thr, "Binarized image with the OTSU method")
Finally I put this Otsu mask to my original image:
result = cv2.bitwise_and(original, original, mask=thr)
show(result, "Lemon Segmented")
From here, with this only hole there is not problems because I'm getting the entire image.
According the research of the below URL https://www.intechopen.com/books/agricultural-robots-fundamentals-and-applications/multimodal-classification-of-mangoes/ it says that to isolate the patched regions, we should get the Green channel:
image = gray
B,green_ch,R = cv2.split(result)
show(green_ch, "Green channel 'G'")
This is the output:
Here there is a notary visualization of the two patched regions but when I use this channel to apply Otsu method, there is not results instead of I'm getting black holes:
_, thr = cv2.threshold(green_ch, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
show(thr, "OTSU with Green channel")
Result:
result = cv2.bitwise_and(original, original, mask=thr)
show(result, "Lemon Segmented")
As we can see the two patched regions are not segmented using the green channel. I tryed using the HSL color space but there is not good results. My idea is detect this patched and the get the color histogram to then train a classifier using those features.
I left this second image were I was testing the code:
Well guys I would like to see your suggestions to try to get the same result the paper of above, any I idea I will apreciate it.
Thanks so much.
Edit: I inverted the second mask to get just the defect areas.
Once you use otsu's the first time it'll give you back a mask that separates the foreground (the fruit) and the background. You can use otsu's a second time on the masked area to get another mask that separates out the dark spots on the fruit.
Unfortunately, OpenCV doesn't have a simple way of running otsu's on just a masked area. However, otsu's is just looking for thresholds on the pixel intensity histogram that create the greatest interparty variance. Since this histogram is all proportional, we can force otsu's to run on just the masked area by making all of the unmasked pixels match the histogram propotions.
I converted to HSV and used the saturation channel to separate the fruit from the background.
I then used the histogram to replicate the pixel proportions on the unmasked pixels of the hue channel.
Hue Before
Hue After
Then I run otsu's a second time on the hue channel.
Now to get the final mask, we just bitwise_and the first and second masks together (and do an opening and closing operation to clean up little holes)
import cv2
import numpy as np
import random
# apply histogram
def applyHist(gray, mask, hist):
# get cumulative distribution
cumulative = [];
total = 0;
for val in hist:
total += val;
cumulative.append(total);
# apply to each pixel not in max
positions = np.where(mask != 255);
for a in range(len(positions[0])):
# choose value
rand = random.randint(0, cumulative[-1]);
index = 0;
while rand > cumulative[index]:
index += 1;
# apply
y = positions[0][a];
x = positions[1][a];
gray[y,x] = index;
# load image
img = cv2.imread("lemon.png");
# hsv
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV);
h,s,v = cv2.split(hsv);
# use the saturation channel for the first mask
s = cv2.GaussianBlur(s, (5,5), 0);
_, mask = cv2.threshold(s, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
# grab positions from mask and make histogram
positions = np.where(mask == 255);
hist = [0 for a in range(256)];
for a in range(len(positions[0])):
y = positions[0][a];
x = positions[1][a];
pix = h[y,x];
hist[pix] += 1;
# opencv doesn't have a way to just let you otsu on a mask...
# eheheheheheh, AHAHAHAHAHA
# LET'S JUST MAKE THE REST OF THE IMAGE MATCH THE HISTOGRAM
applyHist(h, mask, hist);
# otsu the image
h = cv2.GaussianBlur(h, (5,5), 0);
_, second_mask = cv2.threshold(h, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU);
second_mask = cv2.bitwise_not(second_mask); # just get the defects
# combine with first mask
final_mask = cv2.bitwise_and(mask, second_mask);
# opening and closing to get rid of small holes
kernel = np.ones((3,3), np.uint8);
# closing
final_mask = cv2.dilate(final_mask, kernel, iterations = 2);
final_mask = cv2.erode(final_mask, kernel, iterations = 2);
# opening
final_mask = cv2.erode(final_mask, kernel, iterations = 2);
final_mask = cv2.dilate(final_mask, kernel, iterations = 2);
# mask the image
cropped = np.zeros_like(img);
cropped[final_mask == 255] = img[final_mask == 255];
# show image
cv2.imshow("image", img);
cv2.imshow("cropped", cropped);
cv2.imshow("final", final_mask);
cv2.waitKey(0);
I'm trying to follow movement of a part using red dots. I tried with white dots and thresholding before, but there is too much reflection from the smartphone I'm using. The plan is to recognize a dot as a contour, find the center and fill the array with the coordinates of all contour centers for further calculation.
The code is posted bellow, it recognizes the correct number of dots, but I get the division by zero error. Does anyone know what I'm doing wrong?
Image:https://imgur.com/a/GLXGCPP
import cv2
import numpy as np
from matplotlib import pyplot as plt
import imutils
#load image
img = cv2.imread('dot4_red.jpg')
#apply median blur, 15 means it's smoothing image 15x15 pixels
blur = cv2.medianBlur(img,15)
#convert to hsv
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
#color definition
red_lower = np.array([0,0,240])
red_upper = np.array([10,10,255])
#red color mask (sort of thresholding, actually segmentation)
mask = cv2.inRange(hsv, red_lower, red_upper)
#copy image for, .findContours distorts the source image
mask_copy = mask.copy()
#find contours
cnts = cv2.findContours(mask_copy,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
#extract contours from the list??
cnts = imutils.grab_contours(cnts)
#count number of conoturs of specific size
s1 = 500
s2 = 10000
xcnts = []
for cnt in cnts:
if s1<cv2.contourArea(cnt)<s2:
xcnts.append(cnt)
n = len(xcnts)
#pre-allocate array for extraction of centers of contours
s = (n,2)
array = np.zeros(s)
#fill array of center coordinates
for i in range(0,n):
cnt = cnts[i]
moment = cv2.moments(cnt)
c_x = int(moment["m10"]/moment["m00"])
c_y = int(moment["m01"]/moment["m00"])
array[i,:] = [c_x, c_y]
#display image
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', mask)
cv2.waitKey(0) & 0xFF
cv2.destroyAllWindows()
#print results
print ('number of dots, should be 4:',n)
print ('array of dot center coordinates:',array)
The problem was the wrong color range. Because of this, there were holes in the mask of the circles. Due to division by zero. M00. You can choose the correct color range or pre-fill the holes in the mask. Or use this code:
import cv2
import numpy as np
#load image
img = cv2.imread('ZayrMep.jpg')
#apply median blur, 15 means it's smoothing image 15x15 pixels
blur = cv2.medianBlur(img,15)
#convert to hsv
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
#color definition
red_lower = np.array([160,210,230])
red_upper = np.array([180,255,255])
#red color mask (sort of thresholding, actually segmentation)
mask = cv2.inRange(hsv, red_lower, red_upper)
connectivity = 4
# Perform the operation
output = cv2.connectedComponentsWithStats(mask, connectivity, cv2.CV_32S)
# Get the results
num_labels = output[0]-1
centroids = output[3][1:]
#print results
print ('number of dots, should be 4:',num_labels )
print ('array of dot center coordinates:',centroids)
moments00 (area) can be 0 for some shapes according to cv documentation. This is probably what is happening here:
Note Since the contour moments are computed using Green formula, you
may get seemingly odd results for contours with self-intersections,
e.g. a zero area (m00) for butterfly-shaped contours.
From: https://docs.opencv.org/3.4/d8/d23/classcv_1_1Moments.html#a8b1b4917d1123abc3a3c16b007a7319b
You need to ensure the area (m00) is not 0 before using it for division.