I am trying to overlay a transparent red rectangle box on top of an image and return the whole thing from a function. For this, I'm using cv2.addWeight(). I have a previous PIL rectangle function call that draws a rectangle outline to mark where the output should go in case it doesn't appear. However, nothing is drawn unfortunately. This is my output when I use the overlay function:
This is my code:
def overlay(path, lg_x, lg_y):
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
overlay = cv2.rectangle(img, (0, 0), (lg_x, lg_y), (0, 0, 255), cv2.FILLED)
result = cv2.addWeighted(img, 1, overlay, 0.5, 1)
pil_image = Image.fromarray(np.uint8(result))
return pil_image
First, I have to say that I didn't manage to reproduce the same error as yours using your function. There must be another problem somewhere. I had another error: a fully filled rectangle.
There is a problem in your code that does that:
overlay = cv2.rectangle(img, (0, 0), (lg_x, lg_y), (0, 0, 255), cv2.FILLED) is also adding a rectangle over img therefore no matter what weights you put in cv2.addWeighted, your image will be full.
Here is a code that worked for me:
def overlay(path, lg_x, lg_y):
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
overlay = cv2.rectangle(img.copy(), (0, 0), (lg_x, lg_y), (0, 0, 255), cv2.FILLED)
alpha = 0.2
output = cv2.addWeighted(img, 1-alpha, overlay, alpha, 0)
output = cv2.cvtColor(np.uint8(output), cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(output)
return pil_image
I used cv2.rectangle on a copy of the image, and changed the way you applicate the weights so that the total is always 1.0 (Here we have 20% opacity for the rectangle).
The Image.fromarray somehow made my colors wrong, I guess there must be another problem but since it wasn't the main problem, I didn't check deeper.
Try:
result = cv2.addWeighted(img, 0.5, overlay, 0.5, 0)
Related
I'm trying to select a certain area of an image, and it's already successful. However, there's another problem, the selected area is not in the same place as the source image. Here's the visualization about it:
The left image is the area that I generate. But it's not in the right place as I wanted in the right image.
Here's a simple code that I tried already:
import cv2
import NumPy as np
pic= cv2.imread('set.jpeg')
pic = cv2.resize(pic, dsize=(500, 400), interpolation=cv2.INTER_CUBIC)
gray=cv2.cvtColor(pic,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),5)
_,thres = cv2.threshold(blur, 100,250, cv2.THRESH_TOZERO)
res = cv2.Canny(thres, 100, 200, L2gradient=True)
circles = cv2.HoughCircles(res,cv2.HOUGH_GRADIENT,1,20,param1=200,param2=15,minRadius=80,maxRadius=100)
crops = []
for i in circles[0,:]:
# draw the outer circle
cv2.circle(pic,(int(i[0]),int(i[1])),int(i[2]),(255,255,255),2)
i = i.astype(int)
crop = res[i[1]-i[2]:i[1]+i[2], i[0]-i[2]:i[0]+i[2]]
crop = np.pad(crop,[(101, ), (151, )], mode='constant')
crops.append(crop)
result = np.concatenate((crops[0],res),axis=1)
cv2.imshow('Hole',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
I want the result like the right image (generate the blue box image only) and return the rest as black (like the left image).
Is there any way to get the result in the right place as I wanted? (Like the right image) Thank you!!
The issue has been solved by creating masks and combine the foreground and background by these lines of code:
import cv2
import numpy as np
pic= cv2.imread('Assets/set.jpeg')
pic = cv2.resize(pic, dsize=(500, 400), interpolation=cv2.INTER_CUBIC)
gray=cv2.cvtColor(pic,cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),5)
_,thres = cv2.threshold(blur, 100,250, cv2.THRESH_TOZERO)
res = cv2.Canny(thres, 100, 250, L2gradient=True)
circles = cv2.HoughCircles(res,cv2.HOUGH_GRADIENT,1,20,param1=200,param2=15,minRadius=80,maxRadius=100)
circles = np.uint16(np.around(circles))
mask = np.full((res.shape[0], res.shape[1]), 1, dtype=np.uint8) # mask is only
clone = pic.copy()
for i in circles[0, :]:
cv2.circle(mask, (i[0], i[1]), i[2], (255, 255, 255), -1)
cv2.circle(clone, (i[0], i[1]), i[2], (255, 255, 255), 1)
# get first masked value (foreground)
fg = cv2.bitwise_or(res, res, mask=mask)
# get second masked value (background) mask must be inverted
mask = cv2.bitwise_not(mask)
background = np.full(res.shape, 255, dtype=np.uint8)
bk = cv2.bitwise_or(background, background, mask=mask)
# combine foreground+background
final = cv2.bitwise_or(fg, bk)
result = np.concatenate((res,final),axis=1)
cv2.imshow('Hole',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Nothing to be asked anymore and I will close the question. Thank you!!
I'm not sure if I encountered a bug or if I'm missing something (opencv 4.4.0.46 and 4.5.3.56, maybe others).
I'm trying to find the rotated bounding box for this image:
This is the result:
here is the code:
import cv2
import numpy as np
base_image = cv2.imread("so_sample.png", 0)
thresh = cv2.threshold(base_image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
coords = np.column_stack(np.where(thresh > 0))
rect = cv2.minAreaRect(coords)
print("RECT", rect)
box = np.int0(cv2.boxPoints(rect))
drawImg = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
drawImg = cv2.copyMakeBorder(drawImg, 0, 100, 0, 100, cv2.BORDER_REPLICATE) # to see the whole box
cv2.drawContours(drawImg, [box], 0,(0,0,255), 2)
cv2.imshow("base_image", base_image)
cv2.imshow("thresh", thresh)
cv2.imshow("drawImg", drawImg)
cv2.waitKey(0)
This code works fine for the "thunder" sample image and it looks like all the sample code I could find around. Am I missing something? Thanks
You are using np.column_stack(np.where(thresh > 0)) to find the contours.
OpenCV uses (x, y) notation whereas NumPy uses (row, col).
You can check it by printing coords.
I suggest using OpenCV functions where possible.
The following code works.
coords, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rect = cv2.minAreaRect(np.vstack(coords))
See Contour Features.
I trained a YoloV4 model and used opencv-contrib because openCv does not support YoloV4 yet. You can use it with yolov3. Thee are 2 problems with the code:
When the final image is seen using cv2.imshow, it shoes the traingle as Yellow instead of blue. I need to extract that triangle and pass to some other Network so I can not use the yellow image.
It is giving results only and only when we use scale=1/255. else, it provides bad results. Why is that?
I want to ask why is it changing the colours and how can I prevent it? I know that it corresponds to BGR format of opencv but how can it be resolved.
import requests
import numpy as np
from PIL import Image
from io import BytesIO
import cv2
CONFIDENCE_THRESHOLD = 0.5
NMS_THRESHOLD = 0.5
COLORS = [(0, 255, 255), (255, 255, 0), (0, 255, 0), (255, 0, 0)]
net = cv2.dnn.readNet("./yolov4-obj_best_1_class.weights", "./yolov4-custom_1_class.cfg")
model = cv2.dnn_DetectionModel(net)
model.setInputParams(size=(416, 416),scale=1/255.)
url = 'https://instasolv1.s3.ap-south-1.amazonaws.com/QuestionBank/5e9ad4b1e1c473f2bce0e4ff/crop_image.png' # Check original image
response = requests.get(url)
img = Image.open(BytesIO(response.content))
classes, scores, boxes = model.detect(img_array, CONFIDENCE_THRESHOLD, NMS_THRESHOLD)
box = boxes[0]
(x, y) = (box[0], box[1])
(w, h) = (box[2], box[3])
cv2.rectangle(img_array, (x, y), (x + w, y + h), (0,255,0), 2)
text = "Text"
cv2.putText(img_array, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (255,0,255), 2)
cv2.imshow("Show",img_array) # given result has yellow triangle
cv2.waitKey()
cv2.destroyAllWindows()
Run the following code:
import requests
import numpy as np
from PIL import Image
from io import BytesIO
import cv2
CONFIDENCE_THRESHOLD = 0.5
NMS_THRESHOLD = 0.5
COLORS = [(0, 255, 255), (255, 255, 0), (0, 255, 0), (255, 0, 0)]
#net = cv2.dnn.readNet("./yolov4-obj_best_1_class.weights", "./yolov4-custom_1_class.cfg")
#model = cv2.dnn_DetectionModel(net)
#model.setInputParams(size=(416, 416),scale=1/255.)
#url = 'https://instasolv1.s3.ap-south-1.amazonaws.com/QuestionBank/5e9ad4b1e1c473f2bce0e4ff/crop_image.png' # Check original image
#response = requests.get(url)
#img = Image.open(BytesIO(response.content))
img=Image.open('RGBY_example.png')
#classes, scores, boxes = model.detect(img_array, CONFIDENCE_THRESHOLD, NMS_THRESHOLD)
img_array_original=np.array(img)
img_array=img_array_original
#img_array=cv2.cvtColor(img_array_original,cv2.COLOR_BGR2RGB)
#box = boxes[0]
box=[100,100,50,60]
(x, y) = (box[0], box[1])
(w, h) = (box[2], box[3])
cv2.rectangle(img_array, (x, y), (x + w, y + h), (0,255,0), 2)
text = "Text"
cv2.putText(img_array, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX,
0.5, (255,0,255), 2)
cv2.imshow("Show",img_array) # given result has yellow triangle
cv2.waitKey()
cv2.destroyAllWindows()
...and you get erratic colors:
...as a result.
To solve that, remove # from line 26:
...and you will see the correct colors. This way you see the idea, and can handle whatever color-spaces with your images in future.
Answer for question n:o 2 "It is giving results only and only when we use scale=1/255. else, it provides bad results. Why is that?"
The required input scale for the inputs of the DNN is from 0 to 1. In RGB-images each pixel color value is between 0 and 255. Thus, to preprocess the image for the DNN each pixel value is multiplied by 1/225.
I draw some rectangles in OpenCV and put text in them. My general approach looks like this:
# Draw rectangle p1(x,y) p2(x,y) Student name box
cv2.rectangle(frame, (500, 650), (800, 700), (42, 219, 151), cv2.FILLED )
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(frame, name, (510, 685), font, 1.0, (255, 255, 255), 1
Everything works so far. The only thing is, that the opacity in all boxes is at 100 %. My question is: How can I change the opacity?
The final result should look like this:
I would like to add a small optimization to the #HansHirse answer, Instead of creating the canvas for whole image, we can crop the rectangle first from the src image and then later swap it with the cv2.addWeighted result as:
import cv2
import numpy as np
img = cv2.imread("lena.png")
# First we crop the sub-rect from the image
x, y, w, h = 100, 100, 200, 100
sub_img = img[y:y+h, x:x+w]
white_rect = np.ones(sub_img.shape, dtype=np.uint8) * 255
res = cv2.addWeighted(sub_img, 0.5, white_rect, 0.5, 1.0)
# Putting the image back to its position
img[y:y+h, x:x+w] = res
EDIT: Since this answer seems to have some importance, I decided to edit it again, incorporating the proper blending from ZdaR's answer, which initially was an improvement to my original answer (check the timeline if interested). Also, I incorporated Jon's comments to include an example of a non-rectangular shape.
At least from my point of view, built-in functions like cv2.rectangle don't support opacity, even on BGRA images, see here. So, as I described in the linked answer, the only possibility to achieve, what you want, is to use the cv2.addWeighted function. You can simply set up a blank mask image, and draw all possible shapes on that. Doing so, you can also use that as an actual mask to limit the blending to that part only.
An example could be:
import cv2
import numpy as np
# Load image
img = cv2.imread('images/paddington.png')
# Initialize blank mask image of same dimensions for drawing the shapes
shapes = np.zeros_like(img, np.uint8)
# Draw shapes
cv2.rectangle(shapes, (5, 5), (100, 75), (255, 255, 255), cv2.FILLED)
cv2.circle(shapes, (300, 300), 75, (255, 255, 255), cv2.FILLED)
# Generate output by blending image with shapes image, using the shapes
# images also as mask to limit the blending to those parts
out = img.copy()
alpha = 0.5
mask = shapes.astype(bool)
out[mask] = cv2.addWeighted(img, alpha, shapes, 1 - alpha, 0)[mask]
# Visualization
cv2.imshow('Image', img)
cv2.imshow('Shapes', shapes)
cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
The original Paddington img:
The intermediate image to draw the shapes on shapes:
And, the final result out:
After drawing the shapes and blending the images, you can add your texts as before.
Hope that helps!
Simply install pyshine and use putBText, it has following inputs and output.
pip install pyshine
"""
Inputs:
img: cv2 image img
text_offset_x, text_offset_x: X,Y location of text start
vspace, hspace: Vertical and Horizontal space between text and box boundaries
font_scale: Font size
background_RGB: Background R,G,B color
text_RGB: Text R,G,B color
font: Font Style e.g. cv2.FONT_HERSHEY_DUPLEX,cv2.FONT_HERSHEY_SIMPLEX,cv2.FONT_HERSHEY_PLAIN,cv2.FONT_HERSHEY_COMPLEX
cv2.FONT_HERSHEY_TRIPLEX, etc
thickness: Thickness of the text font
alpha: Opacity 0~1 of the box around text
gamma: 0 by default
Output:
img: CV2 image with text and background
"""
Example tested on Python3 and complete demonstration is here:
lena.jpg
simple.py
import pyshine as ps
import cv2
image = cv2.imread('lena.jpg')
text = 'HELLO WORLD!'
image = ps.putBText(image,text,text_offset_x=20,text_offset_y=20,vspace=10,hspace=10, font_scale=2.0,background_RGB=(0,250,250),text_RGB=(255,250,250))
cv2.imshow('Output', image)
cv2.imwrite('out.jpg',image)
cv2.waitKey(0)
out.jpg
another.py
import pyshine as ps
import cv2
import time
image = cv2.imread('lena.jpg')
text = 'ID: '+str(123)
image = ps.putBText(image,text,text_offset_x=20,text_offset_y=20,vspace=10,hspace=10, font_scale=1.0,background_RGB=(228,225,222),text_RGB=(1,1,1))
text = str(time.strftime("%H:%M %p"))
image = ps.putBText(image,text,text_offset_x=image.shape[1]-170,text_offset_y=20,vspace=10,hspace=10, font_scale=1.0,background_RGB=(228,225,222),text_RGB=(1,1,1))
text = '6842'
image = ps.putBText(image,text,text_offset_x=80,text_offset_y=372,vspace=10,hspace=10, font_scale=1.0,background_RGB=(228,225,222),text_RGB=(255,255,255))
text = "Lena Fors'en"
image = ps.putBText(image,text,text_offset_x=80,text_offset_y=425,vspace=20,hspace=10, font_scale=1.0,background_RGB=(20,210,4),text_RGB=(255,255,255))
text = 'Status: '
image = ps.putBText(image,text,text_offset_x=image.shape[1]-130,text_offset_y=200,vspace=10,hspace=10, font_scale=1.0,background_RGB=(228,225,222),text_RGB=(255,255,255))
text = 'On time'
image = ps.putBText(image,text,text_offset_x=image.shape[1]-130,text_offset_y=242,vspace=10,hspace=10, font_scale=1.0,background_RGB=(228,225,222),text_RGB=(255,255,255))
text = 'Attendence: '
image = ps.putBText(image,text,text_offset_x=image.shape[1]-200,text_offset_y=394,vspace=10,hspace=10, font_scale=1.0,background_RGB=(228,225,222),text_RGB=(255,255,255))
text = '96.2% '
image = ps.putBText(image,text,text_offset_x=image.shape[1]-200,text_offset_y=436,vspace=10,hspace=10, font_scale=1.0,background_RGB=(228,225,222),text_RGB=(255,255,255))
cv2.imshow('Output', image)
cv2.imwrite('out.jpg',image)
cv2.waitKey(0)
out.jpg
A simpler solution (although a bit less efficient in terms of memory) is:
create a copy of the original image
draw the desired shapes/text on the the original image
get the overlay with: alpha*img + (1-alpha)*img_cpy
In this way each original pixel will not change it's value (since alpha*px + (1-alpha)px = px), whereas pixels which were drawn on will be affected by the overlay.
This eliminates the need to perform crops and pesky calculations seen in the other answers.
...and applying to to the OP's code:
frame_cpy = frame.copy()
cv2.rectangle(frame, (500, 650), (800, 700), (42, 219, 151), cv2.FILLED)
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(frame, name, (510, 685), font, 1.0, (255, 255, 255), 1)
alpha = 0.4
frame_overlay=cv2.addWeighted(frame, alpha, frame_cpy,1-alpha, gamma=0)
cv2.imshow("overlay result",frame_overlay)
cv2.waitKey(0)
Disclaimer: this answer was inspired by a post on www.pyimagesearch.com
I want to use paste of the python PIL library to paste a image to a black background.
I know I can use the image itself as a alpha mask, but I only want to have the parts of the image where the alpha value is 255.
How is this possible?
Here is my code so far:
import PIL
from PIL import Image
img = Image.open('in.png')
background = Image.new('RGBA', (825, 1125), (0, 0, 0, 255))
offset = (50, 50)
background.paste(img, offset, img) #image as alpha mask as third param
background.save('out.png')
I can't find anything in the official but bad documentation
If I understand your question correctly, then
this is a possible solution. It generates
a dedicated mask, which is used for the paste:
from PIL import Image
img = Image.open('in.png')
# Extract alpha band from img
mask = img.split()[-1]
width, height = mask.size
# Iterate through alpha pixels,
# perform desired conversion
pixels = mask.load()
for x in range(0, width):
for y in range(0, height):
if pixels[x,y] < 255:
pixels[x,y] = 0
# Paste image with converted alpha mask
background = Image.new('RGBA', (825, 1125), (0, 0, 0, 255))
background.paste(img, (50, 50), mask)
background.save('out.png')
As a note, the alpha channel of the background image is fairly useless.
If you don't need it later on, you could also load the background with:
background = Image.new('RGB', (825, 1125), (0, 0, 0))