I have a webcam feed using OpenCV, and I am trying to fit an ellipse in real time.
The code I am using at the moment works, but it fails to fit an ellipse to the image a lot of the time. What other methods of ellipse fitting to an image can I pursue?
Current code:
def find_ellipses(img): #img is grayscale image of what I want to fit
ret,thresh = cv2.threshold(img,127,255,0)
_,contours,hierarchy = cv2.findContours(thresh, 1, 2)
if len(contours) != 0:
for cont in contours:
if len(cont) < 5:
break
elps = cv2.fitEllipse(cont)
return elps #only returns one ellipse for now
return None
Where elps is of the form (x_centre,y_centre),(minor_axis,major_axis),angle
Here is an example of what I want to successfully fit an ellipse to. My current code fails with this image when I don't want it to.
Turns out I was wrong is just getting the first ellipse from the function. While I thought the first calculated ellipse was the most correct one, what I actually had to do was go through all the ellipses - and choose the most suitable one that bounded the object in the image.
I would define my contours outside of the function, as you don't need to keep re-defining them in this image.
def create_ellipse(thresh,cnt):
ellipse = cv2.fitEllipse(cnt)
thresh = cv2.ellipse(thresh,ellipse,(0,255,0),2)
return thresh
What this code is doing is im taking my thresh image stream and adding an ellipse on top of it. Later on in my code when I want to call it I use the line
thresh = create_ellipse(thresh,cnt)
Related
I will bring an example I have a picture of a swimming pool with some tracks I want to take only the three middle tracks Now what is the best way to cut the image in a trapeze shape then how to take this trapeze and try to fit it to the size of the window that will have a relatively similar ratio between the two sides (upper and lower)
image for the example
I modified this example
Result:
import numpy as np
import cv2
# load image
img = cv2.imread('pool.jpg')
# resize to easily fit on screen
img = cv2.resize(img,None,fx=0.5, fy=0.5, interpolation = cv2.INTER_CUBIC)
# determine cornerpoints of the region of interest
pts1 = np.float32([[400,30],[620,30],[50,700],[1000,700]])
# provide new coordinates of cornerpoints
pts2 = np.float32([[0,0],[300,0],[0,600],[300,600]])
# determine transformationmatrix
M = cv2.getPerspectiveTransform(pts1,pts2)
# apply transformationmatrix
dst = cv2.warpPerspective(img,M,(300,600))
# display image
cv2.imshow("img", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
Note the rezise function, you may wish to delete that line, but you will have to change the coordinates of the cornerpoints accordingly.
I used about the height and width of the base of the trapezoid for the new image (300,600).
You can tweak the cornerpoints and final image size as you see fit.
You can use imutils.four_point_transform function. You can read more about it here.
Basic usage is finding the document contours on a canny edge detected image (again, you can use imutils package that I linked), find the contours on that image and then apply four_point_transform on that contour.
EDIT: How to use canny edge detection and four_point_transform
For finding contours you can use openCV and imutils like this:
cnts = cv2.findContours(edged_image.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
Now, when you have the contours just iterate through and see which one is the biggest and has four points (4 vertices). Then just pass the image and the contour to the four_point_transform function like this:
image_2 = four_point_transform(image, biggest_contour)
That's it.
complete noob at open cv and numpy here. here is the image: here is my code:
import numpy as np
import cv2
im = cv2.imread('test.jpg')
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
imgray = cv2.medianBlur(imgray, ksize=7)
ret, thresh = cv2.threshold(imgray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
_, contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
print ("number of countours detected before filtering %d -> "%len(contours))
new = np.zeros(imgray.shape)
new = cv2.drawContours(im,contours,len(contours)-1,(0,0,255),18)
cv2.namedWindow('Display',cv2.WINDOW_NORMAL)
cv2.imshow('Display',new)
cv2.waitKey()
mask = np.zeros(imgray.shape,np.uint8)
cv2.drawContours(mask,[contours[len(contours)-1]],0,255,-1)
pixelpoints = cv2.findNonZero(mask)
cv2.imwrite("masked_image.jpg",mask)
print(len(pixelpoints))
print("type of pixelpoints is %s" %type(pixelpoints))
the length of pixelpoints is nearly 2 million since it contains all the point covered by the contours. But i only require the bordering point of that contour. How do I do it? I have tried several methods from opencv documentation but always errors with tuples and sorting operations. please...help?
I only require the border points of the contour :(
Is this what you mean by border points of a contour?
The white lines you see are points that I have marked out in white against the blue drawn contours. There's a little spot at the bottom right because I think its most likely that your black background isn't really black and so when I did thresholding and a floodfill to get this,
there was a tiny white speck at the same spot. But if you play around with the parameters and do a more proper thresholding and floodfill it shouldn't be an issue.
In openCV's drawContours function, the cnts would contain lists of contours and each contour will contain an array of points. Each point is also of type numpy.ndarray. If you want to place all points of each contour in one place so it returns you a set of points of boundary points (like the white dots outline in the image above), you might want to append them all into a list. You can try this:
#rgb is brg instead
contoured=cv2.drawContours(black, cnts, -1, (255,0,0), 3)
#list of ALL points of ALL contours
all_pixels=[]
for i in range(0, len(cnts)):
for j in range(0,len(cnts[i])):
all_pixels.append(cnts[i][j])
When I tried to
print(len(all_pixels))
it returned me 2139 points.
Do this if you want to mark out the points for visualization purposes (e.g. like my white points):
#contouredC is a copy of the contoured image above
contouredC[x_val, y_val]=[255,255,255]
If you want less points, just use a step function when iterating through to draw the white points out. Something like this:
In python, for loops are slow so I think there's better ways of replacing the nested for loops with a np.where() function or something instead. Will update this if/when I figure it out. Also, this needs better thresholding and binarization techniques. Floodfill technique referenced from: Python 2.7: Area opening and closing binary image in Python not so accurate.
Hope it helps.
I am new to opencv, start to learn it by extract char from simple captcha.
After some effort, I got findContours and some method to clean the image, sometimes worked, but not more often.
For example:
I have a original image(already scale to a large size):
convert to grayscale and use cv2.threshold clean:
use cv2.findContours to get bounding boxes:
W only cover a half, and not get b.
My code:
from StringIO import StringIO
import string
from PIL import Image
import requests
import cv2
import numpy as np
import matplotlib.pyplot as plt
def get_ysdm_captcha():
url = 'http://www.ysdm.net/common/CleintCaptcha'
r = requests.get(url)
img = Image.open(StringIO(r.content))
return img
def scale_image(img, ratio):
return img.resize((int(img.width*ratio), int(img.height*ratio)))
def draw_rect(im):
im = np.array(im)
if len(im.shape) == 3 and im.shape[2] == 3:
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
else:
imgray = im
#plt.imshow(Image.fromarray(imgray), 'gray')
pilimg = Image.fromarray(imgray)
ret,thresh = cv2.threshold(imgray,127,255,0)
threimg = Image.fromarray(thresh)
plt.figure(figsize=(4,3))
plt.imshow(threimg, 'gray')
plt.xticks([]), plt.yticks([])
contours, hierarchy = cv2.findContours(np.array(thresh),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
areas = []
for c in contours:
rect = cv2.boundingRect(c)
area = cv2.contourArea(c)
areas.append(area)
x,y,w,h = rect
if area > 2000 or area < 200 : continue
cv2.rectangle(thresh,(x,y),(x+w,y+h),(0,255,0),1)
plt.figure(figsize=(1,1))
plt.imshow(threimg.crop((x,y,x+w,y+h)), 'gray')
plt.xticks([]), plt.yticks([])
plt.figure(figsize=(10,10))
plt.figure()
plt.imshow(Image.fromarray(thresh), 'gray')
plt.xticks([]), plt.yticks([])
image = get_ysdm_captcha()
im = scale_image(image, 3)
im = np.array(im)
imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
imgray = cv2.GaussianBlur(imgray,(5,5),0)
# im = cv2.medianBlur(imgray,9)
# im = cv2.bilateralFilter(imgray,9,75,75)
draw_rect(imgray)
I tried my best to write above code.
The solutions I imagine is:
find was there any way to tell cv2.findContours I need 4 bounding boxes in some size
tried some different parameter (I tried all from http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=findcontours#findcontours , but still not work)
Now I'm stuck , have no idea how to improve cv2.findContours...
You can use morphological operations to modify your image and fill the gaps, for example erode and dilate
See here:
http://docs.opencv.org/2.4/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html
Original:
Dilated:
By the way: I would implement a HSV separation step in the original image, removing all the 'white/grey/black' content (low saturation). This will reduce the number of specks. Do this before converting to grayscale.
Here is the result filtering on: saturation > 90
Final result: (Added a blur step before)
Also, if there always a gradient, you could detect this and filter out even more colors. But that is a bit much if you just started image processing ;)
findCountours works properly as it finds all connected component of your image. Your area condition is what is probably avoiding you to get a bounding box around letter b, for instance. Of course, if you put a bounding box around each connected component you will not end up with a bounding box around each character because you have many holes in your letters.
If you want to segment the letters, I would first try to play with opening operations (because your letters are black on a white background, it would be closing if it was the opposite) in order to fill the holes that you have in your letters. Then I would project vertically the pixels and analyze the shape that you get. If you find the valley points in this projected shape you will get the vertical limits between characters. You can do the same horizontally to get the upper and bottom limits of your chars. This approach will only work if the text is horizontal. If it is not, you should find the main axis angle of your string and you could rotate the image accordingly. To find the main axis angle you could fit an ellipse to your text and find its main axis angle or you could keep rotating your image by a certain angle until your horixontal projection is maximum.
I am new to OpenCV but trying to solve the following problem:
Each "sprite" in the target images looks the same but the way they are arranged will differ:
For now I am trying to keep it simple and high contrast.
My question relates to technique:
1)should I look to use template matching and the try to extract the relationship of the objects once they have been extracted?
2) should I build my own Haar cascade using the patterns below?
The distance between the sprites will change in my sample images but the sprite will be the same each time.
Thanks for the help;
Andy
There are Two simple steps to solve this without using haar cascades.
Binarize the image and apply findContours function on the image through which you can uniquely identify each sprite from this you could possibly find the no of sprite.
Now apply boundingRect function on the contour and with that find the center point of each sprite. Check if the x coordinate of each sprite is equal then they are on same line. If the y axis is equal then they are stack. If both are not equal then it is angle sprite
Sorry for taking such a long time.
As I mentioned earlier I didn't use boundingRect function. Rather I used another method to find the boundary of the sprite. I will explain the code below
Initially I've cropped the images as
Step 1: Read the input image.
Step 2: Convert source image to Grayscale Image
Step 3: Conversion of gray image to Binary Image by thresholding.
Step 4: Applying findContour function to the binary image.
Step 5: If it has no contour, exit. If it has only one contour, print 1 square sprite and exit. If more than one sprite finding center and so on.
Step 6: Finding the center of the contour using moments and also drawing the
contour to blue.
Step 7: Plotting the center point on the sprite and finding where the x and y coordinate are lying.
Step 8: Finally printing the template of the sprite.
import cv2
import os
import sys
import numpy as np
### inintialization #######
centre =[]
moments=[]
initX=0
initY=0
flagLine=0
flagStack=0
######### Reading input image#########
source = cv2.imread("C:/Prabhakar/Data/anglesprite.png")
cv2.imshow("SourceDisplay", source)
#### Creating Empty matrix ####
binaryImage = np.zeros(source.shape, np.uint8)
#### GrayScale Conversion ####
gray = cv2.cvtColor(source,cv2.COLOR_BGR2GRAY)
#### Binarization ###
ret,thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)
cv2.imshow("Binary Image", thresh)
##### Finding Contour #####
im2, contours, hierarchy =cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#print "No.of Contours:",len(contours)
###### Finding Center of the contour also drawing it ######
if(len(contours)==0):
print "No Square found!!"
cv2.waitKey(0)
cv2.destroyAllWindows()
sys.exit(0)
elif(len(contours)==1):
print "It is a Square!!"
cv2.waitKey(0)
cv2.destroyAllWindows()
sys.exit(0)
else:
for cnts in contours:
moments = cv2.moments(cnts)
centre.append((int(moments['m10']/moments['m00']), int(moments['m01']/moments['m00'])))
cv2.drawContours(binaryImage, contours, -1, (255,0,0), -1)
#print centre
##### Findind Sprite Template #####
for i in range(len(centre)):
initX=centre[0][0]
initY=centre[0][1]
cv2.circle(binaryImage, centre[i], 1, (0, 0, 255), -1)
if(centre[i][0]>=initX-2 and centre[i][0]<=initX+2):
flagStack = flagStack+1
if(centre[i][1]>=initY-2 and centre[i][1]<=initY+2):
flagLine = flagLine+1
if(flagLine == len(contours)):
print "It is a ",len(contours),"Square Line"
elif(flagStack == len(contours)):
print "It is a ",len(contours),"Square Stack"
else:
print "It is a ",len(contours),"Square Angle"
cv2.imshow("Contour Image", binaryImage)
cv2.waitKey(0)
cv2.destroyAllWindows()
If you have any doubts please leave it in the comment.
The image below shows an aerial photo of a house block (re-oriented with the longest side vertical), and the same image subjected to Adaptive Thresholding and Difference of Gaussians.
Images: Base; Adaptive Thresholding; Difference of Gaussians
The roof-print of the house is obvious (to the human eye) on the AdThresh image: it's a matter of connecting some obvious dots. In the sample image, finding the blue-bounded box below -
Image with desired rectangle marked in blue
I've had a crack at implementing HoughLinesP() and findContours(), but get nothing sensible (probably because there's some nuance that I'm missing). The python script-chunk that fails to find anything remotely like the blue box, is as follows:
import cv2
import numpy as np
from matplotlib import pyplot as plt
# read in full (RGBA) image - to get alpha layer to use as mask
img = cv2.imread('rotated_12.png', cv2.IMREAD_UNCHANGED)
grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# Otsu's thresholding after Gaussian filtering
blur_base = cv2.GaussianBlur(grey,(9,9),0)
blur_diff = cv2.GaussianBlur(grey,(15,15),0)
_,thresh1 = cv2.threshold(grey,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
thresh = cv2.adaptiveThreshold(grey,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)
DoG_01 = blur_base - blur_diff
edges_blur = cv2.Canny(blur_base,70,210)
# Find Contours
(ed, cnts,h) = cv2.findContours(grey, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:4]
for c in cnts:
approx = cv2.approxPolyDP(c, 0.1*cv2.arcLength(c, True), True)
cv2.drawContours(grey, [approx], -1, (0, 255, 0), 1)
# Hough Lines
minLineLength = 30
maxLineGap = 5
lines = cv2.HoughLinesP(edges_blur,1,np.pi/180,20,minLineLength,maxLineGap)
print "lines found:", len(lines)
for line in lines:
cv2.line(grey,(line[0][0], line[0][1]),(line[0][2],line[0][3]),(255,0,0),2)
# plot all the images
images = [img, thresh, DoG_01]
titles = ['Base','AdThresh','DoG01']
for i in xrange(len(images)):
plt.subplot(1,len(images),i+1),plt.imshow(images[i],'gray')
plt.title(titles[i]), plt.xticks([]), plt.yticks([])
plt.savefig('a_edgedetect_12.png')
cv2.destroyAllWindows()
I am trying to set things up without excessive parameterisation. I'm wary of 'tailoring' an algorithm for just this one image since this process will be run on hundreds of thousands of images (with roofs/rooves of different colours which may be less distinguishable from background). That said, I would love to see a solution that 'hit' the blue-box target - that way I could at the very least work out what I've done wrong.
If anyone has a quick-and-dirty way to do this sort of thing, it would be awesome to get a Python code snippet to work with.
The 'base' image ->
Base Image
You should apply the following:
1. Contrast Limited Adaptive Histogram Equalization-CLAHE and convert to gray-scale.
2. Gaussian Blur & Morphological transforms (dialation, erosion, etc) as mentioned by #bad_keypoints. This will help you get rid of the background noise. This is the most tricky step as the results will depend on the order in which you apply (first Gaussian Blur and then Morphological transforms or vice versa) and the window sizes you choose for this purpose.
3. Apply Adaptive thresholding
4. Apply Canny's Edge detection
5. Find contour having four corner points
As said earlier you need to tweak with input parameters of these functions and also need to validate these parameters with other images. As it might be possible that it will work for this case but not for other cases. Based on trial and error you need to fix the parameter values.