Is there any convenient way to draw text right into OpenCV circle? Haven't found any similar answer in Google.
If I simply use circle's Centroid_X and Centroid_Y for putText I get something like in the picture below but I want Text completely fit circle and cannot find any elegant way to draw text inside circle.
cv2.putText(frame, text, (cX, cY), FONT, 1.5, TEXT_COLOUR, int(TEXT_THICKNESS), cv2.LINE_AA)
The problem occurs due to the fact that the position given to cv2.putText defines the origin (bottom left corner of the rectangular area that fits the drawn text).
In order to have the text centered around some given point, you first need to measure the size of this rectangular area (bounding box). This can be done using the function cv2.getTextSize.
Now, to have the text centered, the origin needs to move down by half the height of the bouding box, and to the left by half the width of the bounding box.
text_origin = (CENTER[0] - text_size[0] / 2, CENTER[1] + text_size[1] / 2)
Code:
import cv2
import numpy as np
img = np.zeros((128, 128, 3), dtype=np.uint8)
CENTER = (64, 64)
cv2.circle(img, CENTER, 48, (127,0,127), -1)
TEXT_FACE = cv2.FONT_HERSHEY_DUPLEX
TEXT_SCALE = 1.5
TEXT_THICKNESS = 2
TEXT = "0"
text_size, _ = cv2.getTextSize(TEXT, TEXT_FACE, TEXT_SCALE, TEXT_THICKNESS)
text_origin = (CENTER[0] - text_size[0] / 2, CENTER[1] + text_size[1] / 2)
cv2.putText(img, TEXT, text_origin, TEXT_FACE, TEXT_SCALE, (127,255,127), TEXT_THICKNESS, cv2.LINE_AA)
cv2.imwrite('centertext_out.png', img)
Output image:
Related
The goal is to find the line that represents the distance between the "hole" and the outer edge (transition from black to white). I was able to successfully binarize this photo and get a very clean black and white image. The next step would be to find the (almost) vertical line on it and calculate the perpendicular distance to the midpoint of this vertical line and the hole.
original picture
hole - zoomed in
ps: what I call "hole" is a shadow. I am shooting a laser into a hole. So the lines we can see is a steel and the black part without a line is a hole. The 2 white lines serve as a reference to measure the distance.
Is Canny edge detection the best approach? If so, what are good values for the A, B and C parameters? I can't tune it. I'm getting too much noise.
This is not complete; You have to take the time to reach the final result. But this idea might help you.
Preprocessors:
import os
import cv2
import numpy as np
Main code:
# Read original image
dir = os.path.abspath(os.path.dirname(__file__))
im = cv2.imread(dir+'/'+'im.jpg')
h, w = im.shape[:2]
print(w, h)
# Convert image to Grayscale
imGray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
cv2.imwrite(dir+'/im_1_grayscale.jpg', imGray)
# Eliminate noise and display laser light better
imHLine = imGray.copy()
imHLine = cv2.GaussianBlur(imHLine, (0, 9), 21) # 5, 51
cv2.imwrite(dir+'/im_2_h_line.jpg', imHLine)
# Make a BW mask to find the ROI of laser array
imHLineBW = cv2.threshold(imHLine, 22, 255, cv2.THRESH_BINARY)[1]
cv2.imwrite(dir+'/im_3_h_line_bw.jpg', imHLineBW)
# Remove noise with mask and extract just needed area
imHLineROI = imGray.copy()
imHLineROI[np.where(imHLineBW == 0)] = 0
imHLineROI = cv2.GaussianBlur(imHLineROI, (0, 3), 6)
imHLineROI = cv2.threshold(imHLineROI, 25, 255, cv2.THRESH_BINARY)[1] # 22
cv2.imwrite(dir+'/im_4_ROI.jpg', imHLineROI)
# Found laser array and draw box around it
cnts, _ = cv2.findContours(
imHLineROI, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts.sort(key=lambda x: cv2.boundingRect(x)[0])
pts = []
for cnt in cnts:
x2, y2, w2, h2 = cv2.boundingRect(cnt)
if h2 < h/10:
cv2.rectangle(im, (x2, y2), (x2+w2, y2+h2), (0, 255, 0), 1)
pts.append({'x': x2, 'y': y2, 'w': w2, 'h': h2})
circle = {
'left': (pts[0]['x']+pts[0]['w'], pts[0]['y']+pts[0]['h']/2),
'right': (pts[1]['x'], pts[1]['y']+pts[1]['h']/2),
}
circle['center'] = calculateMiddlePint(circle['left'], circle['right'])
circle['radius'] = (circle['right'][0]-circle['left'][0])//2
# Draw line and Circle inside it
im = drawLine(im, circle['left'], circle['right'], color=(27, 50, 120))
im = cv2.circle(im, circle['center'], circle['radius'], (255, 25, 25), 3)
# Remove pepper/salt noise to find metal edge
imVLine = imGray.copy()
imVLine = cv2.medianBlur(imVLine, 17)
cv2.imwrite(dir+'/im_6_v_line.jpg', imVLine)
# Remove remove the shadows to find metal edge
imVLineBW = cv2.threshold(imVLine, 50, 255, cv2.THRESH_BINARY)[1]
cv2.imwrite(dir+'/im_7_v_bw.jpg', imVLineBW)
# Finding the right vertical edge of metal
y1, y2 = h/5, h-h/5
x1 = horizantalDistance(imVLineBW, y1)
x2 = horizantalDistance(imVLineBW, y2)
pt1, pt2 = (x1, y1), (x2, y2)
imVLineBW = drawLine(imVLineBW, pt1, pt2)
cv2.imwrite(dir+'/im_8_v_bw.jpg', imVLineBW)
# Draw lines
im = drawLine(im, pt1, pt2)
im = drawLine(im, calculateMiddlePint(pt1, pt2), circle['center'])
# Draw final image
cv2.imwrite(dir+'/im_8_output.jpg', im)
Extra functions:
Find the first white pixel in one line of picture:
# This function only processes on a horizontal line of the image
# Its job is to examine the pixels one by one from the right and
# report the distance of the first white pixel from the right of
# the image.
def horizantalDistance(im, y):
y = int(y)
h, w = im.shape[:2]
for i in range(0, w):
x = w-i-1
if im[y][x] == 255:
return x
return -1
To draw a line in opencv:
def drawLine(im, pt1, pt2, color=(128, 0, 200), thickness=2):
return cv2.line(
im,
pt1=(int(pt1[0]), int(pt1[1])),
pt2=(int(pt2[0]), int(pt2[1])),
color=color,
thickness=thickness,
lineType=cv2.LINE_AA # Anti-Aliased
)
To calculate middle point of two 2d points:
def calculateMiddlePint(p1, p2):
return (int((p1[0]+p2[0])/2), int((p1[1]+p2[1])/2))
Output:
Original image:
Eliminate noise and process to see the laser array better:
Find the laser area to extract the hole:
Work on another copy of image to find the right side of metal object:
Remove the shadows to better see the right edge:
The final output:
I first defined an ROI area. I changed the code later but did not change the names of the variables. If you were asked.
You can try one of binarized Canny, or straight binarized image (Otsu). In both cases, you will obtain a quasi-straight line from which you can extract the right-most white pixel on every row or so.
Then use line fitting to these points (depending on image quality the fitting needs to be robust or not).
It would be wise to calibrate the method against ground truth, because the edge your are looking after borders a gradient area, and the location of the edge might be offset by several pixels.
I am supposed to write a program to process images of this type:
(All images are of this format: green rectangles, and blue circles). The circles are representing the binary representation of the number. The task is to output the decimal number after detecting the circles.
My approach was to first find the green rectangle (I did not use colour masking, reason explained later) and obtain its width and height.
Next, I reasoned by symmetry that the centers of the circles must be at distances w/8,3w/8,5w/8 and 7w/8 from the left edge of the rectangle. (Horizontally).
So, I used HoughCircles() method, and then tried to express the x-coordinates of the centers in the form (2x-1)w/8.
Clearly, the decimal equivalent of each circle is given by exp=2^(4-x).So, I used n+=2**exp to obtain the decmal representation.
I used the coordinates of the rectangle to also approximate the appropriate minRadius and maxRadius values for the HoughCircles() method, in order to not detect unnecessary circles.
import numpy as np
import cv2 as cv
img = cv.imread("7.jpeg")
img_height,img_w,c=img.shape
n=0 #stores the decimal rep.
width=0 #dimensions of the rectangle
height=0
start_x=0 #starting coordinates of the rectangle
start_y=0
end_x=0 #ending '''''''
end_y=0
minr=0 #for the houghCircles() method
maxr=0
mind=0
output = img.copy()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
_, th = cv.threshold(gray, 240, 255, cv.THRESH_BINARY)
contours, _ = cv.findContours(th, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
for contour in contours:
approx = cv.approxPolyDP(contour, 0.01* cv.arcLength(contour, True), True)
cv.drawContours(img, [approx], 0, (0, 0, 0), 5)
x = approx.ravel()[0]
y = approx.ravel()[1]
if len(approx) == 4 and x>15 :
x1 ,y1, w, h = cv.boundingRect(approx)
aspectRatio = float(w)/h
if aspectRatio <= 0.95 or aspectRatio >= 1.05:
width=w
height=h
start_x=x1
start_y=y1
end_x=start_x+width
end_y=start_y+height
cv.rectangle(output, (start_x,start_y), (end_x,end_y), (0,0,255),3)
cv.putText(output, "rectangle "+str(x)+" , " +str(y-5), (x, y-5), cv.FONT_HERSHEY_COMPLEX, 0.5, (0, 0, 0))
minr=int(17*width/192)
maxr=int(7*width/64)
mind=int(width//5)
print("start",start_x,start_y)
print("width",width)
print("height",height)
print("minr", minr)
print("maxr",maxr)
print("mind",mind)
gray = cv.medianBlur(gray, 5)
circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, mind,param1=50, param2=30, minRadius=minr, maxRadius=maxr)
detected_circles = np.uint16(np.around(circles))
for (x, y ,r) in detected_circles[0, :]:
if(y>start_y and x>start_x):
print("center ", x,y)
idx= int (((x-start_x)*8)//width)
exp=int(4- (0.5* (idx+1)))
n+= 2**exp
cv.circle(output, (x, y), r, (0, 0, 0), 3)
cv.circle(output, (x, y), 2, (0, 255, 255), 3)
print(n)
cv.imshow('output',output)
cv.waitKey(0)
cv.destroyAllWindows()
This works perfectly, for all the images of this type. However, there is a slight drawback:
The test images are in a very "nice" format: all are of fixed width and height, all are perfectly upright, all the colours for the rectangle and circle in each image are of exactly the same shade in all the images, etc.
However, we were supposed to make a code a bit more general: in order to accommodate "not so nice" images also. For example, images of this style:
Essentially the same format, but the background lighting + the stand not being perfectly upright makes it slightly more challenging to generalize the code, I feel. This is why I refrained from using HSV colour masking, because there wont be a set of higher and lower values that would fit all the images.
However, what I tried to do is also failing: its not able to detect the rectangle properly. I expected it to detect multiple rectangles, but its detecting rectangles at those locations, where there aren't any at all (and not at the locations where there are rectangles).
How can I tweak my code to make it a bit more general, in order to also process the second type of image properly?
I'm kinda new with the PIL was wondering why my circle is not perfect. Is there a fix for this? Thanks.
here's my code:
avatar_image = avatar_image.resize((128, 128))
avatar_size = (avatar_image.size[0] * 3, avatar_image.size[1] * 3)
circle_image = Image.new('L', avatar_size, 0)
circle_draw = ImageDraw.Draw(circle_image)
circle_draw.ellipse((0, 0) + avatar_size, fill=255)
mask = circle_image.resize(avatar_image.size, Image.ANTIALIAS)
avatar_image.putalpha(mask)
final = ImageOps.fit(avatar_image, mask.size, centering=(0.5, 0.5))
final.putalpha(mask)
final.show()
Draw Circle: right side of the circle looks off
Circle with Picture:
You have an off-by-one error, commonly caused by a confusion between size and position which is the case here too.
image.new takes a width and height in number of pixels.
circle_draw.ellipse takes a start and end position, which is based on a 0-indexed grid.
To get a full circle you need to make the circle one pixel smaller than it is now to fit inside circle_image
I have 20 small images (that I want to put in a target area of a background image (13x12). I have already marked my target area with a circle, I have the coordinates of the circle in two arrays of pixels. Now I want to know how I can randomly add my 20 small images in random area in my arrays of pixels which are basically the target area (the drawn circle).
In my code, I was trying for just one image, if it works, I'll pass the folder of my 20 small images.
# Depencies importation
import cv2
import numpy as np
# Saving directory
saving_dir = "../Saved_Images/"
# Read the background image
bgimg = cv2.imread("../Images/background.jpg")
# Resizing the bacground image
bgimg_resized = cv2.resize(bgimg, (2050,2050))
# Read the image that will be put in the background image (exemple of 1)
# I'm just trying with one, if it works, I'll pass the folder of the 20
small_img = cv2.imread("../Images/small.jpg")
# Convert the resized background image to gray
bgimg_gray = cv2.cvtColor(bgimg, cv2.COLOR_BGR2GRAY)
# Convert the grayscale image to a binary image
ret, thresh = cv2.threshold(bgimg_gray,127,255,0)
# Determine the moments of the binary image
M = cv2.moments(thresh)
# calculate x,y coordinate of center
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
# Drawing the circle in the background image
circle = cv2.circle(bgimg, (cX, cY), 930, (0,0,255), 9)
print(circle) # This returns None
# Getting the coordinates of the circle
combined = bgimg[:,:,0] + bgimg[:,:,1] + bgimg[:,:,2]
rows, cols = np.where(combined >= 0)
# I have those pixels in rows and cols, but I don't know
# How to randomly put my small image in those pixel
# Saving the new image
cv2.imwrite(saving_dir+"bgimg"+".jpg", bgimg)
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.resizeWindow("Test", 1000, 1200)
# Showing the images
cv2.imshow("image", bgimg)
# Waiting for any key to stop the program execution
cv2.waitKey(0)
In the expected results, the small images must be placed in the background image randomly.
If you have the center and the radius of your circle, you can easily generate random coordinates by randomly choosing an angle theta from [0, 2*pi], calculating corresponding x and y values by cos(theta) and sin(theta) and scaling these by some random chosen scaling factors from [0, radius]. I prepared some code for you, see below.
I omitted a lot of code from yours (reading, preprocessing, saving) to focus on the relevant parts (see how to create a minimal, complete, and verifiable example). Hopefully, you can integrate the main idea of my solution into your code on your own. If not, I will provide further explanations.
import cv2
import numpy as np
# (Artificial) Background image (instead of reading an actual image...)
bgimg = 128 * np.ones((401, 401, 3), np.uint8)
# Circle parameters (obtained somehow...)
center = (200, 200)
radius = 100
# Draw circle in background image
cv2.circle(bgimg, center, radius, (0, 0, 255), 3)
# Shape of small image (known before-hand...?)
(w, h) = (13, 12)
for k in range(200):
# (Artificial) Small image (instead of reading an actual image...)
smallimg = np.uint8(np.add(128 * np.random.rand(w, h, 3), (127, 127, 127)))
# Select random angle theta from [0, 2*pi]
theta = 2 * np.pi * np.random.rand()
# Select random distance factors from center
factX = (radius - w/2) * np.random.rand()
factY = (radius - h/2) * np.random.rand()
# Calculate random coordinates for small image from angle and distance factors
(x, y) = np.uint16(np.add((np.cos(theta) * factX - w/2, np.sin(theta) * factY - h/2), center))
# Replace (rather than "add") determined area in background image with small image
bgimg[x:x+smallimg.shape[0], y:y+smallimg.shape[1]] = smallimg
cv2.imshow("bgimg", bgimg)
cv2.waitKey(0)
The exemplary output:
Caveat: I haven't paid attention, if the small images might violate the circle boundary. Therefore, some additional checks or limitations to the scaling factors must be added.
EDIT: I edited my above code. To take the below comment into account, I shift the small image by (width/2, height/2), and limit the radius scale factor accordingly, so that the circle boundary isn't violated, neither top/left nor bottom/right.
Before, it was possible, that the boundary is violated in the bottom/right part (n = 200):
After the edit, this should be prevented (n = 20000):
The touching of the red line in the image is due to the line's thickness. For "safety reasons", one could add another 1 pixel distance.
i'm new to image processing and opencv, but so far the easy to understand functions and good documentation have enabled me to try out and understand upto some level code like facedetection etc.
Now when i detect the faces in the webcam video stream, the program draws a square around the face. Now i want that much area of the image, in the square around the face, to be created as an another image. From what i've been doing, i'm getting a rectangular area of the image in which the face is not even present.
i've used the cv.GetSubRect() and understood its use. Like for eg:
img=cv.LoadImage("C:\opencv\me.jpg")
sub=cv.GetSubRect(img, (700,525,200,119))
cv.NamedWindow("result",1)
cv.ShowImage("result",sub)
gives me the cropped picture of my eye.
But i can't get the face in my face and eye detection program.
Here's what i've done:
min_size = (17,17)
#max_size = (30,30)
image_scale = 2
haar_scale = 2
min_neighbors = 2
haar_flags = 0
# Allocate the temporary images
gray = cv.CreateImage((image.width, image.height), 8, 1)
smallImage = cv.CreateImage((cv.Round(image.width / image_scale),cv.Round (image.height / image_scale)), 8 ,1)
#eyeregion = cv.CreateImage((cv.Round(image.width / image_scale),cv.Round (image.height / image_scale)), 8 ,1)
#cv.ShowImage("smallImage",smallImage)
# Convert color input image to grayscale
cv.CvtColor(image, gray, cv.CV_BGR2GRAY)
# Scale input image for faster processing
cv.Resize(gray, smallImage, cv.CV_INTER_LINEAR)
# Equalize the histogram
cv.EqualizeHist(smallImage, smallImage)
# Detect the faces
faces = cv.HaarDetectObjects(smallImage, faceCascade, cv.CreateMemStorage(0),
haar_scale, min_neighbors, haar_flags, min_size)
#, max_size)
# If faces are found
if faces:
for ((x, y, w, h), n) in faces:
# the input to cv.HaarDetectObjects was resized, so scale the
# bounding box of each face and convert it to two CvPoints
pt1 = (int(x * image_scale), int(y * image_scale))
pt2 = (int((x + w) * image_scale), int((y + h) * image_scale))
cv.Rectangle(image, pt1, pt2, cv.RGB(255, 0, 0), 3, 4, 0)
face_region = cv.GetSubRect(image,(x,int(y + (h/4)),w,int(h/2)))
cv.ShowImage("face",face_region)
cv.SetImageROI(image, (pt1[0],
pt1[1],
pt2[0] - pt1[0],
int((pt2[1] - pt1[1]) * 0.7)))
eyes = cv.HaarDetectObjects(image, eyeCascade,
cv.CreateMemStorage(0),
eyes_haar_scale, eyes_min_neighbors,
eyes_haar_flags, eyes_min_size)
if eyes:
# For each eye found
for eye in eyes:
eye[0][0],eye[0][1] are x,y co-ordinates of the top-left corner of detected eye
eye[0][2],eye[0][3] are the width and height of the cvRect of the detected eye region (i mean c'mon, that can be made out from the for loop of the face detection)
# Draw a rectangle around the eye
ept1 = (eye[0][0],eye[0][1])
ept2 = ((eye[0][0]+eye[0][2]),(eye[0][1]+eye[0][3]))
cv.Rectangle(image,ept1,ept2,cv.RGB(0,0,255),1,8,0) # This is working..
ea = ept1[0]
eb = ept1[1]
ec = (ept2[0]-ept1[0])
ed = (ept2[1]-ept1[1])
# i've tried multiplying with image_scale to get the eye region within
# the window of eye but still i'm getting just a top-left area of the
# image, top-left to my head. It does make sense to multiply with image_scale right?
eyeregion=cv.GetSubRect(image, (ea,eb,ec,ed))
cv.ShowImage("eye",eyeregion)
I hope this code is from OpenCV/samples/Python. There is a small mistake in the arguments you have given for the co-ordinates inside cv.GetSubRect. Please replace last two lines of above program with following:
a=pt1[0]
b=pt1[1]
c=pt2[0]-pt1[0]
d=pt2[1]-pt1[1]
face_region = cv.GetSubRect(image,(a,b,c,d))
cv.ShowImage("face",face_region)
Make sure, you have no false detection or multiple detection.