Classification on the basis of shape and size - python

How to classify objects on the basis of shape and size using machine learning?
Say I've an circle and some small dotted squares in an image. The difference between the two is their shape and size. So given an image, how to distinguish between these objects and return the result.
In the actual problem those objects are hot spots in a solar PV folder, which are defected parts of it. I need to classify them.The I/P image is as:
[This is less or more a square type of hot spot:]
https://i.stack.imgur.com/4JL7E.png

This answer doesn't ellaborate machine learning or any approach using classifiers
The circles can be detected by the Hough Circle
Transform
from OpenCV cv2.HoughCircles():
Documentation for Hough Circles in OpenCV
Note: By using the radius you can tune the shape size of the circles you want to detect. And to be honest I didn't really get what dotted squares are, maybe you could show an exemplary image in your question.
If there are only two different kinds of objects in the image you probably don't even need a classifier, because the two classes are already being separated by the subsequent image processing (that though depends highly on your input images).
import cv2
import numpy as np
img = cv2.imread('opencv_logo.png',0)
img = cv2.medianBlur(img,5)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('detected circles',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result of the code is as follows

Related

How to draw a circle on image given float (subpixel) coordinates of it center

I want to visualize results of keypoint tracking algorithm in python. I have a sequence of (Image, Keypoint) pairs (video basically). Tracking algorithm is strong enough to give me subpixel accuracy. But i have no idea, how to visualize it properly.
I tried to round my coordinates and draw a circle by cv2.circle(image, (int(round(x)), int(round(y)))), but it leads to visual jittering of my keypoints due to small image resolution.
I checked OpenCV, Pillow, skimage, Pygame (pygame.draw.circle). All of them cannot properly draw circle with float coordinates.
DIPlib has the function DrawBandlimitedBall(), which draws a disk or a circle with smooth transitions and with floating-point origin coordinates (disclosure: I'm one of the authors). You might need to draw the circle in an empty image, then blend it in to get the effect you are looking for. Code would look something like this:
import diplib as dip
img = dip.ImageRead('/Users/cris/dip/images/flamingo.tif')
p = [366.4, 219.1]
# Create an empty image and draw a circle in it
circle = dip.Image(img.Sizes(), 1, 'SFLOAT')
circle.Fill(0)
dip.DrawBandlimitedBall(circle, diameter=22.3, origin=p, value=1, mode='empty')
circle /= dip.Maximum(circle)
# Blend: img * (1-circle) + circle * color
img *= 1 - circle
img += circle * dip.Create0D([0,255,0]) # we make the circle green here
img.Show()
dip.ImageWrite(img, 'so.jpg')
(Note that the circle actually looks better without the JPEG compression artifacts.)
You could draw the circle directly in the image, but this function adds the circle values to the image, it doesn't attempt to blend, and so you'd get a much worse look for this particular application.

Python: Return position and size of arbitrary/teeth shapes in image using OpenCV

I'm very new to the image processing and object detection. I'd like to extract/identify the position and dimensions of teeth in the following image:
Here's what I've tried so far using OpenCV:
import cv2
import numpy as np
planets = cv2.imread('model.png', 0)
canny = cv2.Canny(planets, 70, 150)
circles = cv2.HoughCircles(canny,cv2.HOUGH_GRADIENT,1,40, param1=10,param2=16,minRadius=10,maxRadius=80)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(planets,(i[0],i[1]),i[2],(255,0,0),2)
# draw the center of the circle
cv2.circle(planets,(i[0],i[1]),2,(255,0,0),3)
cv2.imshow("HoughCirlces", planets)
cv2.waitKey()
cv2.destroyAllWindows()
This is what I get after applying canny filter:
This is the final result:
I don't know where to go from here. I'd like to get all of the teeth identified. How can I do that?
I'd really appreciate any help..
Note that the teeth-structure is more-or-less a parabola (upside-down). If you could somehow guess the parabolic shape that defines the centroids of those blobs (teeth), then your problem could be simplified to a reasonable extent. I have shown a red line that passes through the centers of the teeth.
I would suggest you to approach it as follows:
Binarize your image (background=0, else 1). You could use sklearn.preprocessing.binarize.
Calculate the centroid of all the non-zero pixels. This is the central blue circle in the image. Call this structure_centroid. See this: How to center the nonzero values within 2D numpy array?.
Make polar slices of the entire image, centered at the location of the structure_centroid. I have shown a cartoon image of such polar slices (triangular semi-transparent). Cover complete 360 degrees. See this: polarTransform library.
Determine the position of the centroid of the non-zero pixels for each of these polar slices. See these:
find the distance between a point and a curve python.
Find the minimum distance from a point to a curve.
The array containing these centroids gives you the locus (path) of the average location of the teeth. Call this centroid_path.
Run an elimination/selection algorithm on the circles you were able to detect, that are closest to the centroid_path. Use a threshold distance to drop the outliers.
This should give you a good approximation of the teeth with the circles.
I hope this helps.

Difficulty in detected ellipses in image

I am trying to detect ellipses in some images.
After some functions I got this edges map:
I tried using Hough transform to detect ellipses, but this transform has very high complexity, so my computer didn't finish running the transform command even after 5 hours(!).
I also tried doing connected components and got this:
In last case I also tried continue and binarized the image.
In all cases I am stuck in these steps, and have no idea how continue from here.
My mission is detect tomatoes in the image. I am approaching this by trying to detect circles and ellipses and find the radius (or average radius in ellipses case) for each one.
edited:
I add my code for the first method (the result is edge map from above):
img = cv2.imread(r'../images/assorted_tomatoes.jpg')
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
imgAfterLight=lightreduce(img)
imgAfterGamma=gamma_correctiom(imgAfterLight,0.8)
th2 = 255 - cv2.adaptiveThreshold(imgAfterGamma,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,5,3)
median2 = cv2.medianBlur(th2,3)
where median2 is the result of shown above in edge map
and the code for connected components:
import scipy
from scipy import ndimage
import matplotlib.pyplot as plt
import cv2
import numpy as np
fname=r'../images/assorted_tomatoes.jpg'
blur_radius = 1.0
threshold = 50
img = scipy.misc.imread(fname) # gray-scale image
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(img.shape)
# smooth the image (to remove small objects)
imgf = ndimage.gaussian_filter(gray_img, blur_radius)
threshold = 80
# find connected components
labeled, nr_objects = ndimage.label(imgf > threshold)
where labeled is the result above
another edit:
this is the input image:
Input
The problem is that after edge detection, there are a lot of unnecessary edges in sub regions that disturbing for make smooth edge map
To me this looks like a classic problem for the watershed algorithm. It is designed for segmenting out touching objects like the tomatoes. My example is in Matlab (I'm on the wrong computer today) but it should translate to python easily. First convert to greyscale as you do and then invert the images
I=rgb2gray(img)
I2=imcomplement(I)
The image as is will over segment, so we remove minima that are too shallow. This can be done with the h-minima transform
I3=imhmin(I2,50);
You might need to play with the 50 value which is the height threshold for suppressing shallow minima. Now run the watershed algorithm and we get the following result.
L=watershed(I3);
The results are not perfect. It needs additional logic to remove some of the small regions, but it will give a reasonable estimate. The watershed and h-minima are contained in the skimage.morphology package in python.

Detecting silver and reflecting balls with OpenCV

I am trying to detect silver balls reflecting the environment with OpenCV:
With black balls, I successfully did it by detecting circles:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray,(5,5),0);
gray = cv2.medianBlur(gray,5)
gray = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,3.5)
kernel = np.ones((3,3),np.uint8)
gray = cv2.erode(gray,kernel,iterations = 1)
gray = cv2.dilate(gray,kernel,iterations = 1)
circles = cv2.HoughCircles(gray, cv.CV_HOUGH_GRADIENT, 1, 260, \
param1=30, param2=65, minRadius=0, maxRadius=0)
But when using the program with silver balls, we don't get any result.
When looking at the edges calculated by the program, the edges of the ball are quite sharp. But the Code is not recognizing any ball.
How do I improve the detection rate of the silver ball? I think of two ways doing that:
- Improving edge calculation
- Make the circle detection accept an image with unclear edges
Is that possible? What is the best way of doing so?
Help is very appreciated.
You have to tune your parameters. The HoughCircles function does a good job in detecting circles (even with gaps). Note that HoughCircles performs an internal binarization using canny edge detection. Thus, you don't have to do thresholding.
Given your image above, the code
import cv2
from matplotlib import pyplot as plt
import numpy as np
PATH = 'path/to/the/image.jpg'
img = cv2.imread(PATH, cv2.IMREAD_GRAYSCALE)
plt.imshow(img, cmap='gray')
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20, param1=130, param2=30, minRadius=0, maxRadius=0)
if circles is not None:
for x, y, r in circles[0]:
c = plt.Circle((x, y), r, fill=False, lw=3, ec='C1')
plt.gca().add_patch(c)
plt.gcf().set_size_inches((12, 8))
plt.show()
yields the result
What do the different parameters mean?
The function signature is defined as
cv.HoughCircles(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])
image and circles are self explanatory and will be skipped.
method
Specifies the variant of the hough algorithm that is used internally. As stated in the documentation, only HOUGH_GRADIENT is support atm. This method utilizes the 21HT (p.2, THE 2-1 HOUGH TRANSFORM) algorithm. The major advantage of this variant lies in the reduction of memory usage. The standard way of detecting circles using hough transform requires a search in a 3D hough space (x, y and radius). However, with 21HT your hough space is reduced to only 2 dimensions, which lowers the memory consumption by a fair amount.
dp
The dp parameter sets the inverse accumulator resolution. A good explanation can be found here. Note that this explanation uses the standard hough transform as an example. But the effect is the same for 21HT. The accumulator for 21HT is just a bit different as for the standard HT.
minDist
Simply specifies the minimum distance between the centers of circles. In the code example above it's set to 20 which means that the centers of two detected circles have to be at least 20 pixels away from each other. I'm not sure how opencv filters the circles out, but scanning the source code it looks like circles with lower matches are thrown out.
param1
Specifies the thresholds that are passed to the Canny Edge algorithm. Basically it's called like cv2.Canny(image, param1 / 2, param1).
param2
This paragraph should probably be validated by someone who is more familiar with the opencv source code.
param2 specifies the accumulator threshold. This value decides how complete a circle has to be in order to count as a valid circle. I'm not sure in which unit the parameter is given, tho. But (scanning the source code again) it looks like it is an absolute vote threshold (meaning that it is directly affected by different radii).
The image below shows different circles (or what can be identified as a circle). The further you go to the right, the lower the threshold has to be in order to detect that circle.
minRadius and maxRadius
Simply limits the circle search in the range [minRadius, maxRadius] for the radius. This is useful (and can increase performance) if you can approximate (or know) the size of the circles you are searching for.

Circular Hough Transform misses circles

I've read a lot about the Circular Hough transform on Stack Overflow, but I seem to be missing something. I wrote a program that is supposed to detect the circles of a "Bull's Eye" target. However, even after playing with the parameters, the algorithm is quite bad - it ignores most of the circles and one time it finds a circle but seems to "wander off". I've even tried applying an "Unsharp Mask" to no avail. I have added my code, the image I started with and the output. I hope someone can point me at the right direction.
import cv2
import cv2.cv as cv
import numpy as np
import math
# Load Image
img = cv2.imread('circles1.png',0)
# Apply Unsharp Mask
tmp = cv2.medianBlur(img,5)
img = cv2.addWeighted(img,1.5,tmp,-0.5,0)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
# Hough Transform
circles = cv2.HoughCircles(img,cv.CV_HOUGH_GRADIENT,1,5,
param1=100,param2=100,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
# Go over circles, eliminating the ones that are not cocentric enough
height, width = img.shape
center = (width/2,height/2)
for i in circles[0,:]:
# draw the outer circle
if math.sqrt((center[0]-i[0])**2 + (center[1]-i[1])**2) < 15:
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),1)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('detected circles',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
Quick explanation: I load the image, apply Unsharp Mask, use the Hough Transfrom to detect circles, then draw the circles that are close to the center (I found that the other circles are false circles).
I tried playing with the parameters, and this is the best I got. I feel like this is a simple enough problem which has me buffled. I appriciate any help.
My input image:
My output image:
As I mentioned in my comment, you'll need to run successive iterations of cv2.HoughCircles for different range of radii to ensure that you get all of the circles. With the way the Circular Hough Transform works, specifying a minimum and maximum radius that has quite a large range will be inaccurate and will also be slow. They don't tell you this in the documentation, but for the Circular Hough Transform to work successfully, the following two things need to be valid:
maxRadius < 3*minRadius
maxRadius - minRadius < 100
With the above, blindly making the minimum radius very small and the maximum radius very large won't give you great results. Therefore, what you could do is start at... say...radius=1, then iterate up to radius=300 in steps of 20. Between each chunk of 20, run cv2.HoughCircles and update your image with these contours.
Doing this requires very little modification to your code. BTW, I removed the unsharp masking it because I was getting poor results with it. I also changed a couple of parameters in cv2.HoughCircles slightly to get this to work as best as possible given your situation:
import cv2
import cv2.cv as cv
import numpy as np
import math
# Load Image
img = cv2.imread('circles1.png',0)
cimg = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
# Specify different radii
radii = np.arange(0,310,10)
# For each pair of radii...
for idx in range(len(radii)-1):
# Get the minimum and maximum radius
# Note you need to add 1 to each minimum
# as the maximum of the previous pair covers this new minimum
minRadius = radii[idx]+1
maxRadius = radii[idx+1]
# Hough Transform - Change here
circles = cv2.HoughCircles(img,cv.CV_HOUGH_GRADIENT,1,5,
param1=25,param2=75,minRadius=minRadius,maxRadius=maxRadius)
# Skip if no circles are detected - Change here
if circles is None:
continue
circles = np.uint16(np.around(circles))
# Go over circles, eliminating the ones that are not cocentric enough
height, width = img.shape
center = (width/2,height/2)
for i in circles[0,:]:
# draw the outer circle
if math.sqrt((center[0]-i[0])**2 + (center[1]-i[1])**2) < 15:
cv2.circle(cimg,(i[0],i[1]),i[2],(0,255,0),1)
# draw the center of the circle
cv2.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('detected circles',cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
I get this figure:
Unfortunately it isn't perfect as it doesn't detect all of the circles. You'll have to play around with the cv2.HoughCircles function until you get good results.
However, I wouldn't recommend using cv2.HoughCircles here. May I suggest using cv2.findContours instead? This finds all of the contours in the image. In this case, these will be the black circles. However, you need to reverse the image because cv2.findContours assumes non-zero pixels are object pixels, so we can subtract 255 from the image assuming a np.uint8 type:
# Make copy of original image
cimg2 = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
# Find contours
contours,_ = cv2.findContours(255 - img, cv2.RETR_LIST, cv.CV_CHAIN_APPROX_NONE)
# Draw all detected contours on image in green with a thickness of 1 pixel
cv2.drawContours(cimg2, contours, -1, color=(0,255,0), thickness=1)
# Show the image
cv2.imshow('detected circles', cimg2)
cv2.waitKey(0)
cv2.destroyAllWindows()
This is what I get:

Categories