Moments of many objects (Python OpenCV) - python

I have an image that contains ~400 dots. I've been using the Simpleblob detector to find the keypoints. I then for loop over all keypoints to find the center of each of the keypoints (code below). This works well, but I'm also interested in the moment information, as I would imagine .pt is only averaging the position of all of the pixels associated with the keypoints.
import cv2
import numpy as np
import csv
im = cv2.imread("8f3secshim.bmp", cv2.IMREAD_GRAYSCALE)
detector = cv2.SimpleBlobDetector_create()
keypoints = detector.detect(im)
im_with_keypoints = cv2.drawKeypoints(im, keypoints, np.array([]),
(0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow("Keypoints", im_with_keypoints)
x = np.empty([len(keypoints), 2])
for i in range(len(keypoints)):
x[i] = keypoints[i].pt
I wanted to do something along the lines of this:
M = np.empty([lens(keypoints), 1])
for j in range(len(keypoints)):
M[j] = cv2.moments(keypoints[j])
but it fails.
I've tried abandoning the Simpledetector and using a treatment for moments listed here http://docs.opencv.org/trunk/dd/d49/tutorial_py_contour_features.html
, but that has failed as well.
If anyone has any suggestions they would be much appreciated.

I seem to have incorrectly worded my question. If anyone is looking to find the moments associated with individual objects found in a given image, the following code can be used.
import cv2
import numpy as np
img = cv2.imread('8f3secshim.bmp',0)
ret,thresh = cv2.threshold(img,127,255,0)
im2,contours,hierarchy = cv2.findContours(thresh, 1, 2)
print(len(contours))
a = np.empty([len(contours), 1])
cx = np.empty([len(contours), 1])
cy = np.empty([len(contours), 1])
for i in range(0, len(contours)):
Mi = cv2.moments(contours[i])
#if any m00 moment is equal to zero the code can not be completed...
if Mi['m00'] != 0:
cx[i]= Mi['m10']/Mi['m00']
cy[i]= Mi['m01']/Mi['m00']
a[i] = cv2.contourArea(contours[i])
x = np.hstack((cx, cy, a))
#x is a len(contours), 3 matrix.
I'm sure there is probably a more elegant way to do this, but this works.

Related

I am getting fractional artifacts (pieces of metal) instead of whole ones. It seems that the edges are not continuous but they are

My goal is to detect objects placed on a white surface. From there, count how many there are and calculate the area of each one.
It seems that this algorithm is detecting its edge but counting it as multiple objects.
original picture
picture after edge detection
part of the picture with problems
results
In short, I am using "canny" and "connected components" and I am getting fractional objects instead just a whole object.
Following code should do the job, you might need to tweak minItemArea and maxItemArea to filter objects.
import numpy as np
import cv2
import matplotlib.pyplot as plt
rgb = cv2.imread('/path/to/your/image/items_0001.png')
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
imh, imw = gray.shape
th = cv2.adaptiveThreshold(gray,255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,21,5)
contours, hier = cv2.findContours(th.copy(),cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
out_img = rgb.copy()
minItemArea = 50
maxItemArea = 4000
for i in range(len(contours)):
if hier[0][i][3] != -1:
continue
x,y,w,h = cv2.boundingRect(contours[i])
if minItemArea < w*h < maxItemArea:
cv2.drawContours(out_img, [contours[i]], -1, 255, 1)
plt.imshow(out_img)

Extract a fixed number of squares from an image with Python/OpenCV

I have several scanned images I would like to compute with Python/Opencv. Each of these images (see an example below) contains n rows of coloured squares. Each of these squares have the same size. The goal is to crop each of these squares and to extract the data from it.
I have found there a code which is able to extract squares from an image.
Here is my code where I have used it :
import numpy as np
import cv2
from matplotlib import pyplot as plt
def angle_cos(p0, p1, p2):
import numpy as np
d1, d2 = (p0-p1).astype('float'), (p2-p1).astype('float')
return abs( np.dot(d1, d2) / np.sqrt( np.dot(d1, d1)*np.dot(d2, d2) ) )
def find_squares(img):
import cv2 as cv
import numpy as np
img = cv.GaussianBlur(img, (5, 5), 0)
squares = []
for gray in cv.split(img):
for thrs in range(0, 255, 26):
if thrs == 0:
bin = cv.Canny(gray, 0, 50, apertureSize=5)
bin = cv.dilate(bin, None)
else:
_retval, bin = cv.threshold(gray, thrs, 255, cv.THRESH_BINARY)
contours, _hierarchy = cv.findContours(bin, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
for cnt in contours:
cnt_len = cv.arcLength(cnt, True)
cnt = cv.approxPolyDP(cnt, 0.02*cnt_len, True)
if len(cnt) == 4 and cv.contourArea(cnt) > 1000 and cv.isContourConvex(cnt):
cnt = cnt.reshape(-1, 2)
max_cos = np.max([angle_cos( cnt[i], cnt[(i+1) % 4], cnt[(i+2) % 4] ) for i in range(4)])
if max_cos < 0.1:
squares.append(cnt)
print(len(squares))
return squares
img = cv2.imread("test_squares.jpg",1)
plt.axis("off")
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()
squares = find_squares(img)
cv2.drawContours( img, squares, -1, (0, 255, 0), 1 )
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.show()
However, it finds two many squares (100 instead of 15 !!). Looking at the image, it seems that Opencv find a lot of contours for each square.
I'm pretty sure that it can be optimized since the squares have more or less the same size and far from each other. As a very beginner in Opencv, I haven't found yet a way to give more criteria in the function "find squares" in order to get only 15 squares at the end of the routine. Maybe the contour area can be maximized ?
I have also found there a more detailed code (very close to the previous one) but it seems to be developed in a old version of Opencv. I haven't managed to make it work (and so to modify it).
This is another more robust method.
I used this code to find the contours in the image (the full code can be found in this gist):
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Define square size
min_square_size = 987
# Read Image
img = cv2.imread('/home/stephen/Desktop/3eY0k.jpg')
# Threshold and find edges
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Threshold the image - segment white background from post it notes
_, thresh = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY_INV);
# Find the contours
_, contours, _ = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
I iterated through the contours. I only looked at the contours that were a reasonable size. I found the four corners of each contour.
# Create a list for post-it images
images = []
# Iterate through the contours in the image
for contour in contours:
area = cv2.contourArea(contour)
# If the contour is not really small, or really big
h,w = img.shape[0], img.shape[1]
if area > min_square_size and area < h*w-(2*(h+w)):
# Get the four corners of the contour
epsilon = .1 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
# Draw the point
for point in approx: cv2.circle(img, tuple(point[0]), 2, (255,0,0), 2)
# Warp it to a square
pts1 = np.float32(approx)
pts2 = np.float32([[0,0],[300,0],[300,300],[0,300]])
M = cv2.getPerspectiveTransform(pts1,pts2)
dst = cv2.warpPerspective(img,M,(300,300))
# Add the square to the list of images
images.append(dst.copy())
The post-it notes are squares, but because the camera warps the objects in the image they do not appear as squares. I used warpPerspective to make the post-it notes square shapes. Only a few of them are shown in this plot (there are more that didn't fit):
If your problem is that too many contours (edges) are found in the image, my suggestion is to modify the edge-finding part first. It'll be by far the easiest modification to make.
In particular, you'll need to change this call:
bin = cv.Canny(gray, 0, 50, apertureSize=5)
The cv.Canny() function takes as arguments two threshold values, the aperture size, and a boolean to indicate whether a precise form of gradient is used. Play with those parameters, and my guess is, you'll get much better results.

How to generate separate bounding boxes for non-contiguous photo color mask areas

I want to generate bounding boxes for approximately non-contiguous areas of a photo by color mask--in this case, green bands containing vegetation--so that I can clip that area to pass to an image classification function.
This is something that can be done with geospatial rasters fairly easily using GDAL, to polygonize areas of geotiff that have similar characteristics (https://www.gdal.org/gdal_polygonize.html). But in this case, I am trying to do this to a photo. I haven't found a solution for pure rasters.
For example, take a photo like this one:
which is masked into green bands using openCV and numpy:
try:
hsv=cv.cvtColor(image,cv.COLOR_BGR2HSV)
except:
print("File may be corrupt")
return(0,0)
# Define lower and uppper limits of what we call "brown"
brown_lo=np.array([18,0,0])
brown_hi=np.array([28,255,255])
green_lo=np.array([29,0,0])
green_hi=np.array([88,255,255])
# Mask image to only select browns
mask_brown=cv.inRange(hsv,brown_lo,brown_hi)
mask_green=cv.inRange(hsv,green_lo,green_hi)
hsv[mask_brown>0]=(18,255,255)
hsv[mask_green>0]=(53,255,255)
image2=cv.cvtColor(hsv,cv.COLOR_HSV2BGR)
cv.imwrite(QUERIES + 'queries/mask.jpg', image2)
I'd like to generate boxing boxes or polygons for the areas indicated here:
Any ideas how to do that?
I tried using openCV contours and convexhull algorithms, but they don't really get me anywhere:
threshold = val
# Detect edges using Canny
canny_output = cv.Canny(src_gray, threshold, threshold * 2)
# Find contours
_, contours, _ = cv.findContours(canny_output, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# Find the convex hull object for each contour
hull_list = []
for i in range(len(contours)):
hull = cv.convexHull(contours[i])
hull_list.append(hull)
# Draw contours + hull results
drawing = np.zeros((canny_output.shape[0], canny_output.shape[1], 3), dtype=np.uint8)
for i in range(len(contours)):
color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
cv.drawContours(drawing, contours, i, color)
cv.drawContours(drawing, hull_list, i, color)
# Show in a window
cv.imshow('Contours', drawing)
So, as you tried with contours and it didn't work, I tried to go the clustering way. I started off with K-means which is the best place to start IMO. Here is the code:
import cv2
import numpy as np
from sklearn.cluster import KMeans, MeanShift
import matplotlib.pyplot as plt
def centroid_histogram(clt):
numlabels = np.arange(0, len(np.unique(clt.labels_)) + 1)
(hist, _) = np.histogram(clt.labels_, bins=numlabels)
hist = hist.astype("float")
hist /= hist.sum()
return hist
def plot_colors(hist, centroids):
bar = np.zeros((50, 300, 3), dtype="uint8")
startX = 0
for (percent, color) in zip(hist, centroids):
endX = startX + (percent * 300)
cv2.rectangle(bar, (int(startX), 0), (int(endX), 50),
color.astype("uint8").tolist(), -1)
startX = endX
return bar
image1 = cv2.imread('mean_shift.jpg')
image1 = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
image = image1.reshape((image1.shape[0] * image1.shape[1], 3))
#clt = MeanShift(bandwidth=2, bin_seeding=True)
clt = KMeans(n_clusters=3)
clt.fit(image)
hist = centroid_histogram(clt)
bar = plot_colors(hist, clt.cluster_centers_)
plt.figure()
plt.axis("off")
plt.imshow(bar)
plt.show()
Using 3 cluster centers, I got the following result:
and using 6 cluster centers:
which basically shows you the proportion of these colors in the image.
The links that helped me do this:
https://www.pyimagesearch.com/2014/05/26/opencv-python-k-means-color-clustering/ and
https://github.com/log0/build-your-own-meanshift/blob/master/Meanshift%20Image%20Segmentation.ipynb
Now, there are couple of issues I see here:
You might not know the number of clusters in all the images. In that case, you should look into Mean-Shift. Unlike the K-Means algorithm, meanshift does not require specifying the number of clusters in advance. The number of clusters is determined by the algorithm with respect to the data.
I have used SLIC in these kind of problems. It is a subset of the K-means-based algorithms and is very efficient. You can also give this one a try since it is available in scikit, which is the goto library for machine learning in python IMO. In the same direction, you could also give this a try.
Hope I have helped you some way! Cheers!

Is there a better way to separate the writing from the background?

I am working on a project where I should apply and OCR on some documents.
The first step is to threshold the image and let only the writing (whiten the background).
Example of an input image: (For the GDPR and privacy reasons, this image is from the Internet)
Here is my code:
import cv2
import numpy as np
image = cv2.imread('b.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
h = image.shape[0]
w = image.shape[1]
for y in range(0, h):
for x in range(0, w):
if image[y, x] >= 120:
image[y, x] = 255
else:
image[y, x] = 0
cv2.imwrite('output.jpg', image)
Here is the result that I got:
When I applied pytesseract to the output image, the results were not satisfying (I know that an OCR is not perfect). Although I tried to adjust the threshold value (in this code it is equal to 120), the result was not as clear as I wanted.
Is there a way to make a better threshold in order to only keep the writing in black and whiten the rest?
After digging deep in StackOverflow questions, I found this answer which is about removing watermark using opencv.
I adapted the code to my needs and this is what I got:
import numpy as np
import cv2
image = cv2.imread('a.png')
img = image.copy()
alpha =2.75
beta = -160.0
denoised = alpha * img + beta
denoised = np.clip(denoised, 0, 255).astype(np.uint8)
#denoised = cv2.fastNlMeansDenoising(denoised, None, 31, 7, 21)
img = cv2.cvtColor(denoised, cv2.COLOR_BGR2GRAY)
h = img.shape[0]
w = img.shape[1]
for y in range(0, h):
for x in range(0, w):
if img[y, x] >= 220:
img[y, x] = 255
else:
img[y, x] = 0
cv2.imwrite('outpu.jpg', img)
Here is the output image:
The good thing about this code is that it gives good results not only with this image, but also with all the images that I tested.
I hope it helps anyone who had the same problem.
You can use adaptive thresholding. From documentation :
In this, the algorithm calculate the threshold for a small regions of the image. So we get different thresholds for different regions of the same image and it gives us better results for images with varying illumination.
import numpy as np
import cv2
image = cv2.imread('b.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = cv2.medianBlur(image ,5)
th1 = cv2.adaptiveThreshold(image,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,11,2)
th2 = cv2.adaptiveThreshold(image,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,11,2)
cv2.imwrite('output1.jpg', th1 )
cv2.imwrite('output2.jpg', th2 )

Filling holes in image with OpenCV or Skimage

I m trying to fill holes for a chessboard for stereo application. The chessboard is at micro scale thus it is complicated to avoid dust... as you can see :
Thus, the corners detection is impossible. I tried with SciPy's binary_fill_holes or similar approaches but i have a full black image, i dont understand.
Here is a function that replaces the color of each pixel with the color that majority of its neighbor pixels have.
import numpy as np
import cv2
def remove_noise(gray, num):
Y, X = gray.shape
nearest_neigbours = [[
np.argmax(
np.bincount(
gray[max(i - num, 0):min(i + num, Y), max(j - num, 0):min(j + num, X)].ravel()))
for j in range(X)] for i in range(Y)]
result = np.array(nearest_neigbours, dtype=np.uint8)
cv2.imwrite('result2.jpg', result)
return result
Demo:
img = cv2.imread('mCOFl.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
remove_noise(gray, 10)
Input image:
Out put:
Note: Since this function replace the color of corner pixels too, you can sue cv2.goodFeaturesToTrack function to find the corners and restrict the denoising for that pixels
corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 30)
corners = np.squeeze(np.int0(corners))
You can use morphology: dilate, and then erode with same kernel size.
A faster, more accurate way is to use skimage.morphology.remove_small_objects docs
im = imread('a.png',cv2.IMREAD_GRAYSCALE)
im = im ==255
from skimage import morphology
cleaned = morphology.remove_small_objects(im, 200)

Categories