I am trying to implement the conversion discussed in the blogspot https://medium.com/#alexppppp/how-to-annotate-keypoints-using-roboflow-9bc2aa8915cd.
In my dataset I need 4 keypoints and 1 box. I want the edited code for it.
My code and error are shown below.
keypoint_names = ['Head', 'Tail']
rectangles2keypoints = {1:0, 2:1}
def converter(file_labels, file_image, keypoint_names):
img = cv2.imread(file_image)
img_w, img_h = img.shape[1], img.shape[0]
with open(file_labels) as f:
lines_txt = f.readlines()
lines = []
for line in lines_txt:
lines.append([int(line.split()[0])] + [round(float(el), 5) for el in line.split()[1:]])
bboxes = []
keypoints = []
# In this loop we convert normalized coordinates to absolute coordinates
for line in lines:
# Number 0 is a class of rectangles related to bounding boxes.
if line[0] == 0:
x_c, y_c, w, h = round(line[1] * img_w), round(line[2] * img_h), round(line[3] * img_w), round(line[4] * img_h)
bboxes.append([round(x_c - w/2), round(y_c - h/2), round(x_c + w/2), round(y_c + h/2)])
# Other numbers are the classes of rectangles related to keypoints.
# After convertion, numbers of keypoint classes should start with 0, so we apply rectangles2keypoints dictionary to achieve that.
# In our case:
# 1 is rectangle for head keypoint, which is 0, so we convert 1 to 0;
# 2 is rectangle for tail keypoint, which is 1, so we convert 2 to 1.
if line[0] != 0:
kp_id, x_c, y_c = rectangles2keypoints[line[0]], round(line[1] * img_w), round(line[2] * img_h)
keypoints.append([kp_id, x_c, y_c])
# In this loop we are iterating over each keypoint and looking to which bounding box it matches.
# Thus, we are matching keypoints and corresponding bounding boxes.
keypoints_sorted = [[[] for _ in keypoint_names] for _ in bboxes]
for kp in keypoints:
kp_id, kp_x, kp_y = kp[0], kp[1], kp[2]
for bbox_idx, bbox in enumerate(bboxes):
x1, y1, x2, y2 = bbox[0], bbox[1], bbox[2], bbox[3]
if x1 < kp_x < x2 and y1 < kp_y < y2:
keypoints_sorted[bbox_idx][kp_id] = [kp_x, kp_y, 1] # All keypoints are visible
return bboxes, keypoints_sorted
enter image description here
In python, I have written some code that generates a circle using Bresenham's Midpoint Algorithm:
from PIL import Image, ImageDraw
radius = 100 #radius of circle
xpts = [] #array to hold x pts
ypts = [] #array to hold y pts
img = Image.new('RGB', (1000, 1000))
draw = ImageDraw.Draw(img) #to use draw.line()
pixels = img.load()
d = (5/4) - radius
x = 0
y = radius
xpts.append(x) #initial x value
ypts.append(y) #initial y value
while x < y:
if d < 0:
d += (2*x + 3)
x += 1
xpts.append(x + 500) #translate points to center by 500px
ypts.append(y - 500)
else:
d += (2 * (x - y) + 5)
x += 1
y -= 1
xpts.append(x + 500) #translate points to center by 500px
ypts.append(y - 500)
for i in range(len(xpts)): #draw initial and reflected octant points
pixels[xpts[i] ,ypts[i]] = (255,255,0) #initial octant
pixels[xpts[i],-ypts[i]] = (255,255,0)
pixels[-xpts[i],ypts[i]] = (255,255,0)
pixels[-xpts[i],-ypts[i]] = (255,255,0)
pixels[ypts[i],xpts[i]] = (255,255,0)
pixels[-ypts[i],xpts[i]] = (255,255,0)
pixels[ypts[i],-xpts[i]] = (255,255,0)
pixels[-ypts[i],-xpts[i]] = (255,255,0)
img.show()
To fill it, I had planned to use ImageDraw to draw a line horizontally within the circle from each point that is generated from the initial octant using draw.line(). I have the x and y coordinates stored in arrays. However, I am stuck interpreting each point and its reflection point to draw the horizontal line using draw.line(). Could someone clarify this?
Instead of drawing individual pixels, you would just add a line that connects the pixels corresponding to each other (either -x and +x or -y and +y). For each Bresenham step, you draw four lines (each connecting two octants).
Here is your adapted sample code. I dropped the points array and instead drew the lines directly. I also added the cx and cy variables that define the circle center. In your code, you sometimes used negative indices. This only works by coincidence because the circle is in the center:
from PIL import Image, ImageDraw
radius = 100 # radius of circle
xpts = [] # array to hold x pts
ypts = [] # array to hold y pts
img = Image.new('RGB', (1000, 1000))
draw = ImageDraw.Draw(img) # to use draw.line()
pixels = img.load()
d = (5 / 4) - radius
x = 0
y = radius
cx = 500
cy = 500
def draw_scanlines(x, y):
color = (255, 255, 0)
draw.line((cx - x, cy + y, cx + x, cy + y), fill=color)
draw.line((cx - x, cy - y, cx + x, cy - y), fill=color)
draw.line((cx - y, cy + x, cx + y, cy + x), fill=color)
draw.line((cx - y, cy - x, cx + y, cy - x), fill=color)
draw_scanlines(x, y)
while x < y:
if d < 0:
d += (2 * x + 3)
x += 1
else:
d += (2 * (x - y) + 5)
x += 1
y -= 1
draw_scanlines(x, y)
img.show()
Instead of drawing lines, you can fill all points inside a circle with radius radius in O(n^2) using:
# Your code here
for x in range(radius):
for y in range(radius):
if x**2 + y**2 < radius**2:
pixels[ x + 500 , y-500] = (255,255,0)
pixels[ x + 500 , -y-500] = (255,255,0)
pixels[ -x + 500 , y-500] = (255,255,0)
pixels[ -x + 500 , -y-500] = (255,255,0)
img.show()
So apologies for the length of code here. Basically I have used opencv to analyse an image of 7 shapes and read 4 features from it.
The problem is that the code is only giving me out arrays for 5 shapes and I'm unsure why. I have left out the imports etc at the start to shorten the code.
img = cv2.imread("C:\\Users\\telli\\Desktop\\Shapetest.jpg")
#print(img)
#Converting the image to Grayscale
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(grey,127,255,1)
im2,contours, h = cv2.findContours(thresh, 1, cv2.CHAIN_APPROX_SIMPLE)
contours.sort(key = len)
numberOfSides = []
corners = []
standardDeviationsPerimeter = []
sidesDividedByPerimeter = []
standardDeviationsAngles = []
largestAngles = []
angles = []
perimeters = []
featureVectors = []
#finding all the possible circles in the image (likely many false positives)
circles = cv2.HoughCircles(grey,cv2.HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
#finding sides
count = 0;
#print("New perimeter")
for contour in contours:
#remove double recognitions due to thick lines
if count %2 == 0:
epsilon = 10
x, y, w, h = cv2.boundingRect(contour)
x-= epsilon
y-= epsilon
w+= 2 *epsilon
h+= 2 * epsilon
insideCircle = False
#Removing false positive circles by testing each circle against
#the bounding box of this contour
#if we find a circle that is COMPLETELY inside the bounding box
#we have found a circle
for possibleCircle in range (0, len(circles[0])):
centreX = circles[0][possibleCircle][0]
centreY = circles[0][possibleCircle][1]
radius = circles[0][possibleCircle][2]
ToRight = centreX - radius >= x
ToLeft = centreX + radius <= x + w
ToBottom = centreY - radius >= y
ToTop = centreY + radius <= y + h
insideCircle = insideCircle or ToRight and ToLeft and ToBottom and ToTop
#Finding the perimeter of the shapes
perim = cv2.arcLength(contour, True)
perimeters.append(perim)
#if we found that this contour is a circle then the number of sides is 1
if insideCircle:
numberOfSides.append(1)
#use the number of corners found in the contours to determine how many
#sides it has
else:
corner = cv2.approxPolyDP(contour, 0.01 * perim, True)
corners.append(corner)
numberOfSides.append(len(corner))
#print(numberOfSides)
count = count + 1
#finding angles in shape
for shape in range(0, len(corners)):
angles.append([])
sidesDividedByPerimeter.append([])
for corner in range(0, len(corners[shape])):
# 3 vertices we need to find the angle at vertice b
ax = corners[shape][corner % len(corners[shape])][0][0]
ay = corners[shape][corner % len(corners[shape])][0][1]
bx = corners[shape][(corner + 1) % len(corners[shape])][0][0]
by = corners[shape][(corner + 1) % len(corners[shape])][0][1]
cx = corners[shape][(corner + 2) % len(corners[shape])][0][0]
cy = corners[shape][(corner + 2) % len(corners[shape])][0][1]
#print ("A: ", ax, ", ", ay, "\tB: ", bx, ", ", by, "\tC: ", cx, ", ", cy)
dirBAx = ax - bx
dirBAy = ay - by
dirBCx = cx - bx
dirBCy = cy - by
#do dot product and find angle in degrees
dot = dirBAx * dirBCx + dirBAy * dirBCy
lengthBC = math.sqrt(dirBCx * dirBCx + dirBCy * dirBCy)
lengthBA = math.sqrt(dirBAx * dirBAx + dirBAy * dirBAy)
angle = math.acos(dot / (lengthBC * lengthBA))
angle = angle * 180 / math.pi
angles[shape].append(angle)
sidesDividedByPerimeter[shape].append(lengthBC / perimeters[shape])
#print(lengthBC / perimeters[shape])
#print(angle)
#finding max of all angles in each shape
for shape in range(0, len(angles)):
largestAngles.append(np.amax(angles[shape]))
#print(largestAngles)
#if len(approx) == 16:
# cv2.drawContours(img, [contours[0]], 0, (0,0,255), -1)
#Calculating the standard deviation of the sides divided by the perimeter
#print("Standard Devs")
for shape in range(0, len(sidesDividedByPerimeter)):
standarddevPerim = statistics.stdev(sidesDividedByPerimeter[shape])
#Caluclating the standard deviation of the angles of each shape
standarddevAngle = statistics.stdev(angles[shape])
standardDeviationsPerimeter.append(standarddevPerim)
standardDeviationsAngles.append(standarddevAngle)
for shape in range(0, len(sidesDividedByPerimeter)):
featureVectors.append([])
featureVectors[shape].append(numberOfSides[shape])
featureVectors[shape].append(standardDeviationsPerimeter[shape])
featureVectors[shape].append(standardDeviationsAngles[shape])
featureVectors[shape].append(largestAngles[shape])
print(featureVectors)
And featureVector prints out this:
[[4, 0.001743713493735165, 0.6497055601752815, 90.795723552739275],
[4, 0.0460937435599832, 0.19764217920409227, 90.204147248752378],
[1, 0.001185534503063044, 0.3034913722821194, 60.348908179729023],
[1, 0.015455289770298222, 0.8380914254332884, 109.02120657826231],
[3, 0.0169961646358455, 41.36919146079211, 136.83829993466398]]
However there should be 7 shapes.
What i cant figure out is where to append blank values for the 2nd/3rd/4th feature for a circle and allow the program to continue running. It currently appears to be giving the 2nd/3rd/4th value from the next two shapes to the circles.
Hi I am creating a program that replaces a face in a image with someone else's face. However, I am stuck on trying to insert the new face into the original, larger image. I have researched ROI and addWeight(needs the images to be the same size) but I haven't found a way to do this in python. Any advise is great. I am new to opencv.
I am using the following test images:
smaller_image:
larger_image:
Here is my Code so far... a mixer of other samples:
import cv2
import cv2.cv as cv
import sys
import numpy
def detect(img, cascade):
rects = cascade.detectMultiScale(img, scaleFactor=1.1, minNeighbors=3, minSize=(10, 10), flags = cv.CV_HAAR_SCALE_IMAGE)
if len(rects) == 0:
return []
rects[:,2:] += rects[:,:2]
return rects
def draw_rects(img, rects, color):
for x1, y1, x2, y2 in rects:
cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)
if __name__ == '__main__':
if len(sys.argv) != 2: ## Check for error in usage syntax
print "Usage : python faces.py <image_file>"
else:
img = cv2.imread(sys.argv[1],cv2.CV_LOAD_IMAGE_COLOR) ## Read image file
if (img == None):
print "Could not open or find the image"
else:
cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml")
gray = cv2.cvtColor(img, cv.CV_BGR2GRAY)
gray = cv2.equalizeHist(gray)
rects = detect(gray, cascade)
## Extract face coordinates
x1 = rects[0][3]
y1 = rects[0][0]
x2 = rects[0][4]
y2 = rects[0][5]
y=y2-y1
x=x2-x1
## Extract face ROI
faceROI = gray[x1:x2, y1:y2]
## Show face ROI
cv2.imshow('Display face ROI', faceROI)
small = cv2.imread("average_face.png",cv2.CV_LOAD_IMAGE_COLOR)
print "here"
small=cv2.resize(small, (x, y))
cv2.namedWindow('Display image') ## create window for display
cv2.imshow('Display image', small) ## Show image in the window
print "size of image: ", img.shape ## print size of image
cv2.waitKey(1000)
A simple way to achieve what you want:
import cv2
s_img = cv2.imread("smaller_image.png")
l_img = cv2.imread("larger_image.jpg")
x_offset=y_offset=50
l_img[y_offset:y_offset+s_img.shape[0], x_offset:x_offset+s_img.shape[1]] = s_img
Update
I suppose you want to take care of the alpha channel too. Here is a quick and dirty way of doing so:
s_img = cv2.imread("smaller_image.png", -1)
y1, y2 = y_offset, y_offset + s_img.shape[0]
x1, x2 = x_offset, x_offset + s_img.shape[1]
alpha_s = s_img[:, :, 3] / 255.0
alpha_l = 1.0 - alpha_s
for c in range(0, 3):
l_img[y1:y2, x1:x2, c] = (alpha_s * s_img[:, :, c] +
alpha_l * l_img[y1:y2, x1:x2, c])
Using #fireant's idea, I wrote up a function to handle overlays. This works well for any position argument (including negative positions).
def overlay_image_alpha(img, img_overlay, x, y, alpha_mask):
"""Overlay `img_overlay` onto `img` at (x, y) and blend using `alpha_mask`.
`alpha_mask` must have same HxW as `img_overlay` and values in range [0, 1].
"""
# Image ranges
y1, y2 = max(0, y), min(img.shape[0], y + img_overlay.shape[0])
x1, x2 = max(0, x), min(img.shape[1], x + img_overlay.shape[1])
# Overlay ranges
y1o, y2o = max(0, -y), min(img_overlay.shape[0], img.shape[0] - y)
x1o, x2o = max(0, -x), min(img_overlay.shape[1], img.shape[1] - x)
# Exit if nothing to do
if y1 >= y2 or x1 >= x2 or y1o >= y2o or x1o >= x2o:
return
# Blend overlay within the determined ranges
img_crop = img[y1:y2, x1:x2]
img_overlay_crop = img_overlay[y1o:y2o, x1o:x2o]
alpha = alpha_mask[y1o:y2o, x1o:x2o, np.newaxis]
alpha_inv = 1.0 - alpha
img_crop[:] = alpha * img_overlay_crop + alpha_inv * img_crop
Example usage:
import numpy as np
from PIL import Image
# Prepare inputs
x, y = 50, 0
img = np.array(Image.open("img_large.jpg"))
img_overlay_rgba = np.array(Image.open("img_small.png"))
# Perform blending
alpha_mask = img_overlay_rgba[:, :, 3] / 255.0
img_result = img[:, :, :3].copy()
img_overlay = img_overlay_rgba[:, :, :3]
overlay_image_alpha(img_result, img_overlay, x, y, alpha_mask)
# Save result
Image.fromarray(img_result).save("img_result.jpg")
Result:
If you encounter errors or unusual outputs, please ensure:
img should not contain an alpha channel. (e.g. If it is RGBA, convert to RGB first.)
img_overlay has the same number of channels as img.
Based on fireant's excellent answer above, here is the alpha blending but a bit more human legible. You may need to swap 1.0-alpha and alpha depending on which direction you're merging (mine is swapped from fireant's answer).
o* == s_img.*
b* == b_img.*
for c in range(0,3):
alpha = s_img[oy:oy+height, ox:ox+width, 3] / 255.0
color = s_img[oy:oy+height, ox:ox+width, c] * (1.0-alpha)
beta = l_img[by:by+height, bx:bx+width, c] * (alpha)
l_img[by:by+height, bx:bx+width, c] = color + beta
Here it is:
def put4ChannelImageOn4ChannelImage(back, fore, x, y):
rows, cols, channels = fore.shape
trans_indices = fore[...,3] != 0 # Where not transparent
overlay_copy = back[y:y+rows, x:x+cols]
overlay_copy[trans_indices] = fore[trans_indices]
back[y:y+rows, x:x+cols] = overlay_copy
#test
background = np.zeros((1000, 1000, 4), np.uint8)
background[:] = (127, 127, 127, 1)
overlay = cv2.imread('imagee.png', cv2.IMREAD_UNCHANGED)
put4ChannelImageOn4ChannelImage(background, overlay, 5, 5)
A simple function that blits an image front onto an image back and returns the result. It works with both 3 and 4-channel images and deals with the alpha channel. Overlaps are handled as well.
The output image has the same size as back, but always 4 channels.
The output alpha channel is given by (u+v)/(1+uv) where u,v are the alpha channels of the front and back image and -1 <= u,v <= 1. Where there is no overlap with front, the alpha value from back is taken.
import cv2
def merge_image(back, front, x,y):
# convert to rgba
if back.shape[2] == 3:
back = cv2.cvtColor(back, cv2.COLOR_BGR2BGRA)
if front.shape[2] == 3:
front = cv2.cvtColor(front, cv2.COLOR_BGR2BGRA)
# crop the overlay from both images
bh,bw = back.shape[:2]
fh,fw = front.shape[:2]
x1, x2 = max(x, 0), min(x+fw, bw)
y1, y2 = max(y, 0), min(y+fh, bh)
front_cropped = front[y1-y:y2-y, x1-x:x2-x]
back_cropped = back[y1:y2, x1:x2]
alpha_front = front_cropped[:,:,3:4] / 255
alpha_back = back_cropped[:,:,3:4] / 255
# replace an area in result with overlay
result = back.copy()
print(f'af: {alpha_front.shape}\nab: {alpha_back.shape}\nfront_cropped: {front_cropped.shape}\nback_cropped: {back_cropped.shape}')
result[y1:y2, x1:x2, :3] = alpha_front * front_cropped[:,:,:3] + (1-alpha_front) * back_cropped[:,:,:3]
result[y1:y2, x1:x2, 3:4] = (alpha_front + alpha_back) / (1 + alpha_front*alpha_back) * 255
return result
For just add an alpha channel to s_img I just use cv2.addWeighted before the line
l_img[y_offset:y_offset+s_img.shape[0], x_offset:x_offset+s_img.shape[1]] = s_img
as following:
s_img=cv2.addWeighted(l_img[y_offset:y_offset+s_img.shape[0], x_offset:x_offset+s_img.shape[1]],0.5,s_img,0.5,0)
When attempting to write to the destination image using any of these answers above and you get the following error:
ValueError: assignment destination is read-only
A quick potential fix is to set the WRITEABLE flag to true.
img.setflags(write=1)
A simple 4on4 pasting function that works-
def paste(background,foreground,pos=(0,0)):
#get position and crop pasting area if needed
x = pos[0]
y = pos[1]
bgWidth = background.shape[0]
bgHeight = background.shape[1]
frWidth = foreground.shape[0]
frHeight = foreground.shape[1]
width = bgWidth-x
height = bgHeight-y
if frWidth<width:
width = frWidth
if frHeight<height:
height = frHeight
# normalize alpha channels from 0-255 to 0-1
alpha_background = background[x:x+width,y:y+height,3] / 255.0
alpha_foreground = foreground[:width,:height,3] / 255.0
# set adjusted colors
for color in range(0, 3):
fr = alpha_foreground * foreground[:width,:height,color]
bg = alpha_background * background[x:x+width,y:y+height,color] * (1 - alpha_foreground)
background[x:x+width,y:y+height,color] = fr+bg
# set adjusted alpha and denormalize back to 0-255
background[x:x+width,y:y+height,3] = (1 - (1 - alpha_foreground) * (1 - alpha_background)) * 255
return background
I reworked #fireant's concept to allow for optional alpha masks and allow any x or y, including values outside of the bounds of the image. It will crop to the bounds.
def overlay_image_alpha(img, img_overlay, x, y, alpha_mask=None):
"""Overlay `img_overlay` onto `img` at (x, y) and blend using optional `alpha_mask`.
`alpha_mask` must have same HxW as `img_overlay` and values in range [0, 1].
"""
if y < 0 or y + img_overlay.shape[0] > img.shape[0] or x < 0 or x + img_overlay.shape[1] > img.shape[1]:
y_origin = 0 if y > 0 else -y
y_end = img_overlay.shape[0] if y < 0 else min(img.shape[0] - y, img_overlay.shape[0])
x_origin = 0 if x > 0 else -x
x_end = img_overlay.shape[1] if x < 0 else min(img.shape[1] - x, img_overlay.shape[1])
img_overlay_crop = img_overlay[y_origin:y_end, x_origin:x_end]
alpha = alpha_mask[y_origin:y_end, x_origin:x_end] if alpha_mask is not None else None
else:
img_overlay_crop = img_overlay
alpha = alpha_mask
y1 = max(y, 0)
y2 = min(img.shape[0], y1 + img_overlay_crop.shape[0])
x1 = max(x, 0)
x2 = min(img.shape[1], x1 + img_overlay_crop.shape[1])
img_crop = img[y1:y2, x1:x2]
img_crop[:] = alpha * img_overlay_crop + (1.0 - alpha) * img_crop if alpha is not None else img_overlay_crop