How to make black background in cv2.putText with Python OpenCV - python

I have a project of opencv where on the frame I am displaying some text using cv2.putText(). Currently it looks like below:
As you can see on the top left corner, the text is present but its not clearly visible. Is it possible to make background black so that the text will then appear good. Something like below image:
Even if the black background covers till right side of the frame, that is also fine. Below is the code I am using for putting text on frame:
cv2.putText(frame, "Data: N/A", (5, 30), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 1)
cv2.putText(frame, "Room: C1", (5, 60), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 1)
Is there any prebuilt method/library available in opencv which can do this. Can anyone please suggest a good way?

Use this function:
import cv2
def draw_text(img, text,
font=cv2.FONT_HERSHEY_PLAIN,
pos=(0, 0),
font_scale=3,
font_thickness=2,
text_color=(0, 255, 0),
text_color_bg=(0, 0, 0)
):
x, y = pos
text_size, _ = cv2.getTextSize(text, font, font_scale, font_thickness)
text_w, text_h = text_size
cv2.rectangle(img, pos, (x + text_w, y + text_h), text_color_bg, -1)
cv2.putText(img, text, (x, y + text_h + font_scale - 1), font, font_scale, text_color, font_thickness)
return text_size
Then you can invoke the function like this:
image = 127 * np.ones((100, 200, 3), dtype="uint8")
pos = (10, 10)
w, h = draw_text(image, "hello", pos=(10, 10))
draw_text(image, "world", font_scale=4, pos=(10, 20 + h), text_color_bg=(255, 0, 0))
cv2.imshow("image", image)
cv2.waitKey()
note that by default it paints a black background, but you can use a different color if you want.

There's no prebuilt method but a simple appraoch is to use cv2.rectangle + cv2.putText. All you need to do is to draw the black rectangle on the image followed by placing the text. You can adjust the x,y,w,h parameters depending on how large/small you want the rectangle. Here's an example:
Input image:
Result:
import cv2
import numpy as np
# Load image, define rectangle bounds
image = cv2.imread('1.jpg')
x,y,w,h = 0,0,175,75
# Draw black background rectangle
cv2.rectangle(image, (x, x), (x + w, y + h), (0,0,0), -1)
# Add text
cv2.putText(image, "THICC flower", (x + int(w/10),y + int(h/2)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2)
# Display
cv2.imshow('image', image)
cv2.waitKey()

Here is one way to do that in Python OpenCV.
Read the input
Create an image of your desired background color that is the same size as the input
Draw your text on the background image
Get the bounding rectangle for the text region
Copy the text region from the background color image to a copy of the input image
Save the results
Input:
import cv2
import numpy as np
# load image
img = cv2.imread("zelda1.jpg")
# create same size image of background color
bg_color = (0,0,0)
bg = np.full((img.shape), bg_color, dtype=np.uint8)
# draw text on bg
text_color = (0,0,255)
cv2.putText(bg, "Data: N/A", (5,30), cv2.FONT_HERSHEY_COMPLEX_SMALL, 0.75, text_color, 1)
# get bounding box
# use channel corresponding to color so that text is white on black background
x,y,w,h = cv2.boundingRect(bg[:,:,2])
print(x,y,w,h)
# copy bounding box region from bg to img
result = img.copy()
result[y:y+h, x:x+w] = bg[y:y+h, x:x+w]
# write result to disk
cv2.imwrite("zelda1_background_text.jpg", bg)
cv2.imwrite("zelda1_text.jpg", result)
# display results
cv2.imshow("TEXT", bg)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Text on background color image:
Text on input image:
P.S. You can adjust the bounding rectangle (x,y,w,h) values to add some padding if you want when you do the crop.

import cv2 \
import numpy as np
#### Load image, define rectangle bounds
image = cv2.imread(r'C:\Users\Bharath\Downloads\test.jpg')
#### overlay space
x,y,w,h = 40,30,300,60
#### alpha, the 4th channel of the image
alpha = 0.3
overlay = image.copy()
output = image.copy()
##### corner
cv2.rectangle(overlay, (x, x), (x + w, y + h), (0,0,0), -1)
##### putText
cv2.putText(overlay, "HELLO WORLD..!", (x + int(w/10),y + int(h/1.5)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255,255,255), 2)
#### apply the overlay
cv2.addWeighted(overlay, alpha, output, 1 - alpha,0, output)
##### Display
cv2.imshow("Output", output)\
cv2.waitKey(0)
`
input
output

Related

Add transparency to the whole image except one element

i had added transparency to the whole image, but later i also added rectangle and aligned it by center. How to delete transparency in rectangle area?
Code:
import cv2
image = cv2.imread('test.jpeg')
overlay = image.copy()
print(image.shape)
x, y, w, h = 0, 0, image.shape[1], image.shape[0] # Rectangle parameters
cv2.rectangle(overlay, (x, y), (x+w, y+h), (500, 500, 500), -1) # A filled rectangle
Y, X = overlay.shape[0], overlay.shape[1]
print(X, Y)
cv2.rectangle(overlay, pt1=((X // 2) - 150,(Y // 2) - 150), pt2=((X // 2) + 150,(Y // 2) + 150), color=(0,0,0), thickness=5)
alpha = 0.5 # Transparency factor.
# Following line overlays transparent rectangle over the image
image_new = cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0)
cv2.imshow('frame', image_new)
cv2.waitKey(0) # waits until a key is pressed
cv2.destroyAllWindows()
You can simply index with the rectangle to copy the original pixels values into the new image:
import cv2
image = cv2.imread('test.jpeg')
overlay = image.copy()
print(image.shape)
x, y, w, h = 0, 0, image.shape[1], image.shape[0] # Rectangle parameters
cv2.rectangle(overlay, (x, y), (x+w, y+h), (500, 500, 500), -1) # A filled rectangle
Y, X = overlay.shape[0], overlay.shape[1]
# HERE: Save your points
pt1=((X // 2) - 150,(Y // 2) - 150)
pt2=((X // 2) + 150,(Y // 2) + 150)
cv2.rectangle(overlay, pt1=pt1, pt2=pt2, color=(0,0,0), thickness=5)
alpha = 0.5 # Transparency factor.
# Following line overlays transparent rectangle over the image
image_new = cv2.addWeighted(overlay, alpha, image, 1 - alpha, 0)
# HERE: Put the pixels inside the rectangle back
image_new[pt1[1]:pt2[1], pt1[0]:pt2[0]] = image[pt1[1]:pt2[1], pt1[0]:pt2[0]]
cv2.imshow('frame', image_new)
cv2.waitKey(0) # waits until a key is pressed
cv2.destroyAllWindows()

Opencv (opencv-contrib) is changing the color of image after using object detection module (cv2.dnn.readNet)

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.

Modified ROI and then adding it to the original image using python

I am new to Python and OpenCV. My task is to perform some operations on the ROI of an image and then adding back that image to the original image. How could I achieve this? For example, I want to change the colour of the ROI image and then add it back. My code is given below:
for (i,c) in enumerate(contours_from_left_to_right):
cv2.drawContours(duplicate_img, [c], -1, (0,0,255), 3)
cent_moment = cv2.moments(c)
centroid_x = int(cent_moment['m10'] / cent_moment['m00'])
centroid_y = int(cent_moment['m01'] / cent_moment['m00'])
cv2.putText(duplicate_img, str(i+1), (centroid_x, centroid_y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Contours from Left to Right', duplicate_img)
cv2.waitKey(0)
(x, y, w, h) = cv2.boundingRect(c)
print("Top-Left Corner=",(x,y), "width= ",w,"height =",h)
ROI = roi_img[y:y+h, x:x+w]
cv2.imwrite("ROI_{}.png".format(image_number), ROI)
image_number += 1
When you selectyour ROI,you will have (x,y). After you complete your image processing on the ROI image, you can save the image and use pillow to paste the roi image back to original image follows the coordinate of the ROI.
from PIL import Image, ImageDraw, ImageFilter
#test
im1 = Image.open(args["image"]) #Original Image
im2 = Image.open("ROI.tif") #ROI Image after process
back_im = im1.copy()
back_im.paste(im2, (ROIRegion[region][0][0], ROIRegion[region][0][1])) #(x, Y) coordinate
back_im.save('replace.tif', quality=95) #save image

How to extract only circular ROI portion of the image and show Radius of the circle with a button click in Tkinter window of Python OpenCV GUI

Extract Circular ROI & Show Radius of the Circle in Tkinter Label
I am requesting help from python experts in this community. I have searched about my problem all over Stackexchange as well as the Github community. But I didn't find anything helpful.
I have created a Tkinter GUI. In this GUI, I can upload my image from the destination folder. In Select of the evaluation section, I have written a script through which I can automatically view my ROI region in the circular part. The GUI is displayed at the bottom part of this question.
Help required Section: I am having trouble in creating a script through which:
when I click on Upload ROI button, only the selected ROI portion
of the image gets saved at the destination folder i.e path =
'Data/images/' + name + '_' + method + ext
I can view the Radius of the circle somewhere on the the Tkinter GUI.
def ROI(self, image, method):
if method == 'ROI':
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blimage = cv2.medianBlur(image, 15)
circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
maxRadius=0)
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
cv2.circle(image, (i[0], i[1]), i[2], (0, 255, 0), 6)
cv2.circle(image, (i[0], i[1]), 2, (0, 0, 255), 3)
cv2.waitKey()
else:
print('method is wrong')
return image
GUI
UPDATE:
I added variable border to calculate x1,y1,x2,y2 so now it crops with borderline. Images show results for old code without border.
If you have only one circle (x,y,r) then you can use it to crop image
image = image[y-r:y+r, x-r:x+r]
I test it on some image with circle bigger then image and I had to use int16 instead of unit16 to get -1 instead of 65535 for 170-171 (y-r). Add I had to use min(), max()to get0instead-1`
def ROI(self, image, method):
if method == 'ROI':
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blimage = cv2.medianBlur(image, 15)
circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
maxRadius=0)
if circles is not None:
#print(circles)
# need `int` instead of `uint` to correctly calculate `y-r` (to get `-1` instead of `65535`)
circles = np.int16(np.around(circles))
for x,y,r in circles[0, :]:
print('x, y, r:', x, y, r)
border = 6
cv2.circle(image, (x, y), r, (0, 255, 0), border)
cv2.circle(image, (x, y), 2, (0, 0, 255), 3)
height, width = image.shape
print('height, width:', height, width)
# calculate region to crop
x1 = max(x-r - border//2, 0) # eventually -(border//2+1)
x2 = min(x+r + border//2, width) # eventually +(border//2+1)
y1 = max(y-r - border//2, 0) # eventually -(border//2+1)
y2 = min(y+r + border//2, height) # eventually +(border//2+1)
print('x1, x2:', x1, x2)
print('y1, y2:', y1, y2)
# crop image
image = image[y1:y2,x1:x2]
print('height, width:', image.shape)
else:
print('method is wrong')
return image
For more circles you would have to first calculate region used for all circles (get drom all circles minimal values x-r,y-r and maximal values x+r,y+r) and next crop image.
Later I will try to use alpha channel to remove backgroud outside circle.
Image used for test (if someone else would like to test code)
EDIT: I added code which create black image with white circle to remove background.
def ROI(self, image, method):
if method == 'ROI':
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
blimage = cv2.medianBlur(image, 15)
circles = cv2.HoughCircles(blimage, cv2.HOUGH_GRADIENT, 1, 255, param1=100, param2=60, minRadius=0,
maxRadius=0)
if circles is not None:
print(circles)
circles = np.int16(np.around(circles)) # need int instead of uint to correctly calculate y-r (to get -1 instead of 65535)
for x,y,r in circles[0, :]:
print('x, y, r:', x, y, r)
height, width = image.shape
print('height, width:', height, width)
border = 6
cv2.circle(image, (x, y), r, (0, 255, 0), border)
cv2.circle(image, (x, y), 2, (0, 0, 255), 3)
mask = np.zeros(image.shape, np.uint8) # black background
cv2.circle(mask, (x, y), r, (255), border) # white mask for black border
cv2.circle(mask, (x, y), r, (255), -1) # white mask for (filled) circle
#image = cv2.bitwise_and(image, mask) # image with black background
image = cv2.bitwise_or(image, ~mask) # image with white background
x1 = max(x-r - border//2, 0) # eventually -(border//2+1)
x2 = min(x+r + border//2, width) # eventually +(border//2+1)
y1 = max(y-r - border//2, 0) # eventually -(border//2+1)
y2 = min(y+r + border//2, height) # eventually +(border//2+1)
print('x1, x2:', x1, x2)
print('y1, y2:', y1, y2)
image = image[y1:y2,x1:x2]
print('height, width:', image.shape)
else:
print('method is wrong')
return image

How to change the opacity of boxes (cv2.rectangle)?

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

Categories

Resources