Draw image in rectangle python - python

I have plotted a rectangle using matplotlib and would like to place an image in it as shown in the image below. Does anyone have an idea how I can achieve this in python?

Here is one way using Python/OpenCV/Numpy. Do a perspective warp of the panda image using its 4 corners and the 4 corners of the rectangle. Then make a mask of the excess regions, which are black in the warped image. Finally, blend the warped image and background image using the mask.
Input:
Graph Image:
import numpy as np
import cv2
import math
# read image to be processed
img = cv2.imread("panda.png")
hh, ww = img.shape[:2]
# read background image
bck = cv2.imread("rectangle_graph.png")
hhh, www = bck.shape[:2]
# specify coordinates for corners of img in order TL, TR, BR, BL as x,y pairs
img_pts = np.float32([[0,0], [ww-1,0], [ww-1,hh-1], [0,hh-1]])
# manually pick coordinates of corners of rectangle in background image
bck_pts = np.float32([[221,245], [333,26], [503,111], [390,331]])
# compute perspective matrix
matrix = cv2.getPerspectiveTransform(img_pts,bck_pts)
#print(matrix)
# change black and near-black to graylevel 1 in each channel so that no values
# inside panda image will be black in the subsequent mask
img[np.where((img<=[5,5,5]).all(axis=2))] = [1,1,1]
# do perspective transformation setting area outside input to black
img_warped = cv2.warpPerspective(img, matrix, (www,hhh), cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0,0,0))
# make mask for area outside the warped region
# (black in image stays black and rest becomes white)
mask = cv2.cvtColor(img_warped, cv2.COLOR_BGR2GRAY)
mask = cv2.threshold(mask, 0, 255, cv2.THRESH_BINARY)[1]
mask = cv2.merge([mask,mask,mask])
mask_inv = 255 - mask
# use mask to blend between img_warped and bck
result = ( 255 * (bck * mask_inv + img_warped * mask) ).clip(0, 255).astype(np.uint8)
# save images
cv2.imwrite("panda_warped.png", img_warped)
cv2.imwrite("panda_warped_mask.png", mask)
cv2.imwrite("panda_in_graph.png", result)
# show the result
cv2.imshow("warped", img_warped)
cv2.imshow("mask", mask)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Warped Input:
Mask:
Result:

You can use imshow to place the image at a given position. And add a transform to give the image the same rotation as the rectangle.
To steer away from possible copyright issues, the following code uses an image from wikipedia (author: Fernando Revilla):
import matplotlib.pyplot as plt
from matplotlib import transforms
from matplotlib.patches import Rectangle
file = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Giant_Panda_Tai_Shan.JPG/1200px-Giant_Panda_Tai_Shan.JPG'
img = plt.imread(file, format='jpg')
fig, ax = plt.subplots()
# suppose a rectangle was drawn onto the plot
x, y = 20, 30
width, height = 12, 9
angle = 70
rect = Rectangle((x, y), width, height, angle=angle, ec='black', fc='none', lw=3)
ax.add_patch(rect)
# draw the image using the rectangles position and rotation
tr = transforms.Affine2D().translate(-x, -y).rotate_deg(angle).translate(x, y)
ax.imshow(img, extent=[x, x + width, y, y + height], transform=tr + ax.transData)
ax.set_aspect('equal') # keep right angles
ax.relim()
ax.autoscale()
plt.show()

Related

(Python , OpenCV) Extract picture from picture

I want to extract each sticker 5x6 and to total 30 sticker
like below , how do I do so
(expect pic ) https://imgur.com/a/C5CiSxM
(original picture) https://imgur.com/a/V0lvqU3
from below link I come up my code
How extract pictures from an big image in python
following the suggestion:
The black pixels along the top are a distraction, so are the black
pixels of the QR codes. You are only interested in the white stickers.
So, take a copy of your image and threshold at a high value to give
you pure white stickers surrounded by black and with black QR codes
within each sticker. Now find white contours and reject black ones.
Apply the contours found on the thresholded image to your original
image.
I'm doing the Thresholding expecting pure white stickers surrounded by black and with black QR codes within each sticker
import numpy as np
import glob
import matplotlib.pyplot as plt
import skimage.io
import skimage.color
import skimage.filters
from PIL import Image
import pytesseract
import cv2 as cv
import numpy as np
def custom_blur_demo(image):
kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32) #锐化
dst = cv.filter2D(image, -1, kernel=kernel)
cv.imwrite("/home/joy/桌面/test_11_4/sharpen_images.png", dst)
cv.imshow("custom_blur_demo", dst)
src = cv.imread("/home/joy/桌面/test_11_4/original.png")
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)
cv.imshow("input image", src)
custom_blur_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()
# load the image
image = skimage.io.imread("/home/joy/桌面/test_11_4/sharpen_images.png")[:,:,:3]
# image = imageio.imread(image_name)[:,:,:3]
# img = rgb2gray(image)
fig, ax = plt.subplots()
plt.imshow(image)
# convert the image to grayscale
gray_image = skimage.color.rgb2gray(image)
# blur the image to denoise
blurred_image = skimage.filters.gaussian(gray_image, sigma=1.0)
fig, ax = plt.subplots()
plt.imshow(blurred_image, cmap="gray")
# create a histogram of the blurred grayscale image
histogram, bin_edges = np.histogram(blurred_image, bins=256, range=(0.0, 1.0))
fig, ax = plt.subplots()
plt.plot(bin_edges[0:-1], histogram)
plt.title("Grayscale Histogram")
plt.xlabel("grayscale value")
plt.ylabel("pixels")
plt.xlim(0, 1.0)
# create a mask based on the threshold
t1 = 0.72
t2 = 0.05
binary_mask = blurred_image < t1
fig, ax = plt.subplots()
plt.imshow(binary_mask, cmap="gray")
aaa = plt.imshow(binary_mask, cmap="gray")
plt.show()
plt.savefig("/home/joy/桌面/test_11_4/sharpen_images_del_gray_part.png", aaa)
img = Image.open('/home/joy/桌面/test_11_4/sharpen_images_del_gray_part.png')
text = pytesseract.image_to_string(img, lang='eng')
print("file name" ,"final output", ".png")
print("size")
print(img.size)
print(text)
here is the output for mine Thresholding : https://imgur.com/a/V0lvqU3
the product does after Thresholding but the word on every sticker seems blur (I'm going to OCR image to text every single sticker img later)
not correct yet, I want sticker part only that without gray color part
(pic 5b) in same link is how I reach for now
https://imgur.com/a/V0lvqU3
How to cut them in small piece, the sticker size
(expect pic ) https://imgur.com/a/C5CiSxM
The black pixels along the top are a distraction, so are the black pixels of the QR codes. You are only interested in the white stickers.
So, take a copy of your image and threshold at a high value to give you pure white stickers surrounded by black and with black QR codes within each sticker. Now find white contours and reject black ones.
Apply the contours found on the thresholded image to your original image.
This effort is probably not scientifically generalizable.
Just wanted to show it doesn't need Contour-finding/Binarization. just pixel value comparison might be enough.
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
orig_image = cv.imread("sample.png")
gray = cv.cvtColor(orig_image, cv.COLOR_BGR2GRAY)
kernel = np.ones((3,3),np.uint8)
eroded = cv.erode(gray,kernel,iterations = 1)
def show_img(img_bgr):
fig, ax = plt.subplots(figsize=(5, 5))
rgb = cv.cvtColor(img_bgr, cv.COLOR_BGR2RGB)
ax.imshow(rgb)
return fig
def detect_top_bottom(original_img, erodded_img):
"""Detects only top and bottom row number of each row of Qr_Codes.
"""
line_img = original_img.copy()
height, width = erodded_img.shape
top = 0
top_drawn = False
rows_range = []
for row in range(erodded_img.shape[0]):
for col in range(erodded_img.shape[1]):
if np.mean(erodded_img[row,col]) > 190:
if row - top > 3 and not top_drawn:
cv.line(line_img, (0, row), (width, row), (0,255,0), 2)
rows_range.append([row,None])
top_drawn = True
top = row
break
else:
if top_drawn:
cv.line(line_img, (0, row), (width, row), (0,255,0), 2)
rows_range[-1][1] = row
top_drawn = False
return line_img, rows_range
# make original image grayscale
gray = cv.cvtColor(orig_image, cv.COLOR_BGR2GRAY)
# erode image with 3x3 kernel in order to remove small noises
kernel = np.ones((3,3),np.uint8)
eroded = cv.erode(gray,kernel,iterations = 1)
line_img, rows_range = detect_top_bottom(orig_image, eroded)
# Rotate image 90 degs clockwise in order to use same function for detection
eroded_rotated_90 = cv.rotate(eroded, cv.ROTATE_90_CLOCKWISE)
line_img_rotated_90 = cv.rotate(line_img, cv.ROTATE_90_CLOCKWISE)
line_img, cols_range = detect_top_bottom(line_img_rotated_90, eroded_rotated_90)
# finally rotate 90 deg counter clockwise in to get orignal.
line_img= cv.rotate(line_img, cv.ROTATE_90_COUNTERCLOCKWISE)
fig = show_img(line_img)
fig.savefig("original_grid.png")
fig, axs = plt.subplots(len(rows_range), len(cols_range))
for i, row in enumerate(rows_range):
for j, col in enumerate(cols_range):
axs[i,j].axis('off')
# just for sake of visualization conver to RGB
# axs[i,j].imshow(orig_image[row[0]:row[1], :][:,col[0]:col[1]]) probabely is enough
orig_sub_channels = cv.cvtColor(orig_image[row[0]:row[1], :][:,col[0]:col[1]], cv.COLOR_BGR2RGB)
axs[i,j].imshow(orig_sub_channels)
fig.savefig("splitted_grid.png")

Fill concentric binary image in Numpy

I have an input binary (black and white) image containing two concentric ellipses. The background and the internal ellipse are black while the external ellipse is white.
My goal is to change all pixels of the internal ellipse from black to white.
This is a simple code snippet to generate two concentric ellipses:
import cv2
import numpy as np
# costants
min_height, max_height = 15, 60
min_width, max_width = 15, 60
min_rot, max_rot = 0, 360
center = (max_width*2, max_height*2)
# empty image
image = np.zeros((max_width*4, max_height*4), dtype=np.uint8)
# randomly selecting angle, horizontal and vertical compression
width = int(np.random.uniform(min_width, max_width))
height = int(np.random.uniform(min_height, max_height))
angle = np.random.uniform(min_rot, max_rot)
# draw external ellipse
image = cv2.ellipse(image, center, (width, height), angle, 0, 360, color=255, thickness=-1)
# draw internal ellipse
image = cv2.ellipse(image, center, (width//2, height//2), angle, 0, 360, color=0, thickness=-1)
cv2.imshow('concentric ellipses', image)
cv2.waitKey(0)
My goal is to change all pixels of the internal ellipse from black to white. Like this:
example
A Naive approach could be a loop for on each image but this seems very slow to me:
def fill_image(img):
import numpy as np
num_rows = img.shape[0]
for row in range(num_rows):
# get col index for white pixels (external ellipse)
white_pixels = np.where(img[row, :] == 255)[0]
# assign white pixels
start_px, end_px = min(white_pixels), max(white_pixels)
img[row, start_px:end_px] = 255
return img
Which is the most pythonic and efficient way to do that?

Extract circles from one image after have apply the circular hough transform

I'm trying to extract the detected circles in one image using the circular hough transform. My idea is get every circle or separate each one to then get his color histogram features and after send this features to one classifier as SVM, ANN, KNN etc..
This is my input image:
I'm getting the circles of this way:
import numpy as np
import cv2
import matplotlib.pyplot as plt
cv2.__version__
#read image
file = "lemon.png"
image = cv2.imread(file)
#BGR to RGB
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
#convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray,
cv2.HOUGH_GRADIENT,
15,
41,
param1=31,
param2=31,
minRadius=0,
maxRadius=33)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(image,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(image,(i[0],i[1]),2,(0,0,255),3)
print("Number of circles: "+ str(len(circles[0,:])))
plt.imshow(image, cmap='gray', vmin=0, vmax=255)
plt.show()
Output:
The next step is try to extract those circles but I don't have idea how to do it.
Well guys I would like to see your suggestions, any I idea I will apreciate it.
Thanks so much.
You can create a binary mask for every circle you detect. Use this mask to extract only the ROIs from the input image. Additionally, you can crop these ROIs and store them in a list to pass them to your classifier.
Here's the code:
import numpy as np
import cv2
# image path
path = "C://opencvImages//"
file = path + "LLfN7.png"
image = cv2.imread(file)
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray,
cv2.HOUGH_GRADIENT,
15,
41,
param1=31,
param2=31,
minRadius=0,
maxRadius=33)
# Here are your circles:
circles = np.uint16(np.around(circles))
# Get input size:
dimensions = image.shape
# height, width
height = image.shape[0]
width = image.shape[1]
# Prepare a list to store each ROI:
lemonROIs = []
The idea is that you process one circle at a step. Get the current circle, create a mask, mask the original input, crop the ROI and store it inside the list:
for i in circles[0, :]:
# Prepare a black canvas:
canvas = np.zeros((height, width))
# Draw the outer circle:
color = (255, 255, 255)
thickness = -1
centerX = i[0]
centerY = i[1]
radius = i[2]
cv2.circle(canvas, (centerX, centerY), radius, color, thickness)
# Create a copy of the input and mask input:
imageCopy = image.copy()
imageCopy[canvas == 0] = (0, 0, 0)
# Crop the roi:
x = centerX - radius
y = centerY - radius
h = 2 * radius
w = 2 * radius
croppedImg = imageCopy[y:y + h, x:x + w]
# Store the ROI:
lemonROIs.append(croppedImg)
For each circle you get a cropped ROI:
You can pass that info to your classifier.

to crop the rgb image based on the pixel value

I am having an rgb image and I want to crop it from all sides. i have tried the below code on gray scale image it works but for RGB image it doesn't.
My code is:
import cv2
import numpy as np
import glob,os
from PIL import Image
inputdir = "/Users/sripdeep/Desktop/Projects/3 Norm_Sub_Norm/rgb/"
outpath = "/Users/sripdeep/Desktop/Projects/3 Norm_Sub_Norm/rgb_norm/"
#filesname = sorted(glob.glob( inputdir + "*.jpg"))
for img1 in sorted(glob.glob( inputdir + "*.jpeg")):
img_path = img1.split('/')[-1]
imgstr = img_path.split('.')[0]
print(imgstr)
im1 = cv2.imread(img1,0)
thresh = cv2.threshold(im1, 100, 255, cv2.THRESH_BINARY)[1]
x, y, w, h = cv2.boundingRect(im1) # calculates nonzero pixels
# print(x,y,w,h)
# a tuple (x, y, w, h) with (x, y) the upper left point
# as well as width w and height h of the bounding rectangle.
left = (x, np.argmax(thresh[:, x])) #
right = (x+w-1, np.argmax(thresh[:, x+w-1])) #
top = (np.argmax(thresh[y, :]), y) #
bottom = (np.argmax(thresh[y+h-1, :]), y+h-1)
print('left: {}'.format(left))
print('right: {}'.format(right))
print('top: {}'.format(top))
print('bottom: {}'.format(bottom))
# cropped__img = img[y1:y2, x1:x2]
cropped__img = thresh[top[1]:bottom[1], left[0]:right[0]]
cropped__img = cv2.resize(cropped__img, (256,256), interpolation=cv2.INTER_LINEAR)
save_fname = os.path.join(outpath, os.path.basename(imgstr)+'.jpg')
cv2.imwrite(save_fname, cropped__img)
I want my image to be cropped like this:
But I get output is:
Your approach is close, and can be simplified a bit.
Note that cv2.boundingRect(..) can only be applied on single channel images. Because of this, it seems easiest to first read a color image, then convert it to grayscale to figure out bounding rectangle (x,h,w,h) coordinates (x1, y1, x2, y2), and then apply cropping to the original color image.
Perhaps something like this:
im1 = cv2.imread(img1,0) # color image
im1_gray = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY) # grayscale
thresh = cv2.threshold(im1_gray, 100, 255, cv2.THRESH_BINARY)[1] # threshold image
x, y, w, h = cv2.boundingRect(thresh) # find the bounding rectangle of nonzero points in the image
cropped__img = im1[y:y+h, x:x+w,:] # crop the original color image
This one is pretty straightforward. A color image in OpenCV in Python is a 3-dimensional NumPy array (x, y, color). A grayscale image is a 2-dimensional array (x, y).
In this line, you are cropping a grayscale (2D) image by giving limits on the x and y dimensions:
cropped__img = thresh[top[1]:bottom[1], left[0]:right[0]]
see this answer for more info on slice notation in Python. To crop a color image, you need to tell Python to grab everything in the third dimension by using simply :
# v
cropped__img = img[top[1]:bottom[1], left[0]:right[0], :]

Optimize the cropping function

I'm using the following code to crop an image and retrieve a non-rectangular patch.
def crop_image(img,roi):
height = img.shape[0]
width = img.shape[1]
mask = np.zeros((height, width), dtype=np.uint8)
points = np.array([roi])
cv2.fillPoly(mask, points, (255))
res = cv2.bitwise_and(img, img, mask=mask)
rect = cv2.boundingRect(points) # returns (x,y,w,h) of the rect
cropped = res[rect[1]: rect[1] + rect[3], rect[0]: rect[0] + rect[2]]
return cropped, res
The roi is [(1053, 969), (1149, 1071), (883, 1075), (813, 983)].
The above code works however How do I optimize the speed of the code? It is too slow. Is there any other better way of cropping non-rectangular patches?
I see two parts that could be optimized.
Cropping the image to the bounding rectangle bounds could be applied as the first step. Benefit? you dramatically reduce the size of the images you are working with. You only have to translate the points of the roi by the x,y of the rect and you are good to go.
At the bitwise_and operation, you are "anding" the image with itself and checking at each pixel whether it is allowed by the mask to output it. I guess this is where most time is spent. Instead, you can directly "and" with the mask and save your precious time (no extra mask checking step). Again, a minor tweak to be able to do so, the mask image should have exactly the same shape as the input image (including channels).
Edit:
Modify code to support any number of channels in the input image
The code below does these two things:
def crop_image(img, roi):
height = img.shape[0]
width = img.shape[1]
channels = img.shape[2] if len(img.shape) > 2 else 1
points = np.array([roi])
rect = cv2.boundingRect(points)
mask_shape = (rect[3], rect[2]) if channels == 1 else (rect[3], rect[2], img.shape[2])
#Notice how the mask image size is now the size of the bounding rect
mask = np.zeros(mask_shape, dtype=np.uint8)
#tranlsate the points so that their origin is the bounding rect top left point
for p in points[0]:
p[0] -= rect[0]
p[1] -= rect[1]
mask_filling = tuple(255 for _ in range(channels))
cv2.fillPoly(mask, points, mask_filling)
cropped = img[rect[1]: rect[1] + rect[3], rect[0]: rect[0] + rect[2]]
res = cv2.bitwise_and(cropped, mask)
return cropped, res
Here is one way using Python/OpenCV and Numpy.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread("efile.jpg")
points = np.array( [[ [693,67], [23,85], [62,924], [698,918] ]] )
# get bounding rectangle of points
x,y,w,h = cv2.boundingRect(points)
print(x,y,w,h)
# draw white filled polygon from points on black background as mask
mask = np.zeros_like(img)
cv2.fillPoly(mask, points, (255,255,255))
# fill background of image with black according to mask
masked = img.copy()
masked[mask==0] = 0
# crop to bounding rectangle
cropped = masked[y:y+h, x:x+w]
# write results
cv2.imwrite("efile_mask.jpg", mask)
cv2.imwrite("efile_masked.jpg", masked)
cv2.imwrite("efile_cropped.jpg", cropped)
# display it
cv2.imshow("efile_mask", mask)
cv2.imshow("efile_masked", masked)
cv2.imshow("efile_cropped", cropped)
cv2.waitKey(0)
Mask from provided points:
Image with background made black:
Cropped result:

Categories

Resources