Reputation: 5126
I want to create a bounding box out of the following dimensions using meshgrid but just not able to get the right box.
My parent dimensions are x = 0 to 19541
and y = 0 to 14394
. Out of that, I want to cut a box from x' = 4692 to 12720 and y' = 4273 to 10117
.
However, I am not getting the right bounds. Could someone please help me here?
from matplotlib.path import Path
xmin, xmax = 4692, 12720
ymin, ymax = 4273, 10117
sar_ver = [(4692, 10117), (12720, 10117), (12658, 4274), (4769, 4273), (4692, 10117)]
x, y = np.meshgrid(np.arange(xmin, xmax + 1), np.arange(ymin, ymax + 1))
shx = x
x, y = x.flatten(), y.flatten()
points = np.vstack((x, y)).T
path = Path(sar_ver)
grid = path.contains_points(points)
grid.shape = shx.shape # 5845 X 8029
print grid
UPDATE: This is what I tried and I am close to what I want but not exactly. I want to change the original origin from 0 to the image's surrounding box as shown in expected output.
The updated code that I am using is this
from matplotlib.path import Path
nx, ny = 16886, 10079
sar_ver = [(16886, 1085), (15139, 2122), (14475, 5226), (8419, 5601), (14046, 6876), (14147, 10079), (16816, 3748), (16886, 1085)]
x, y = np.meshgrid(np.arange(nx), np.arange(ny))
x, y = x.flatten(), y.flatten()
points = np.vstack((x,y)).T
path = Path(sar_ver)
grid = path.contains_points(points)
grid.shape = (10079, 16886)
grid = np.multiply(grid,255)
int_grid = grid.astype(np.uint8)
grid_img = Image.fromarray(int_grid)
grid_img.save('grid_image.png') # ACTUAL OUTPUT IMAGE WITH ORIGIN NOT SHIFTED
Expected output is this: Doesn't matter if the image is rotated the other way round but will be a cherry on top if its aligned correctly.
However I am getting right now this so my ACTUAL OUTPUT from the updated code posted is this:
So I want to shift the origin around the box.
BOUNDING BOX PROBLEM DETAILS AFTER GETTING THE MASK: This code comes after the line posted in the second update grid_img.save('grid_image.png') # ACTUAL OUTPUT IMAGE WITH ORIGIN NOT SHIFTED
Here im
is the matrix of the actual image. What should be the x-y min, max of im
to have the same shape as mask and multiply both of them to get pixel values and the rest cancelled out with 0s.
img_x = 19541 # 0 - 19541
img_y = 14394 # 0 - 14394
im = np.fromfile(binary_file_path, dtype='>f4')
im = np.reshape(im.astype(np.float32), (img_x, img_y))
im = im[:10079, :16886]
bb_list = np.multiply(grid, im)
# slice and dice
slice_rows = np.any(bb_list, axis=1)
slice_cols = np.any(bb_list, axis=0)
ymin, ymax = np.where(slice_rows)[0][[0, -1]]
xmin, xmax = np.where(slice_cols)[0][[0, -1]]
answer = bb_list[ymin:ymax + 1, xmin:xmax + 1]
# convert to unit8
int_ans = answer.astype(np.uint8)
fin_img = Image.fromarray(int_ans)
fin_img.save('test_this.jpeg')
My GOAL is to cut out a polygon of a given geom out of a given image. So I am taking the mask out of that polygon and then using that mask to cut the same out of the original image. So multiplying mask's 1's and 0's with the pixel values in the image to just get 1*pixel values.
I tried the following to cut out the actual image to have the same dimensions so that I can multiply np.multiply(im, mask)
but it didn't work as image's shape is not cut into same shape as mask's. I tried your min and max below but didn't work!
im = im[xmin:xmax, ymin:ymax]
ipdb> im.shape
(5975, 8994)
ipdb> mask.shape
(8994, 8467)
Clearly I cannot multiple mask and im now.
Upvotes: 1
Views: 3030
Reputation: 5126
I tried the open cv2 library and it appears to be faster than meshgrid or mgrid on large images. Posting opencv2 solution:
import numpy as np
import cv2
import matplotlib.pyplot as plt
from matplotlib.path import Path
sar_ver = np.array([[[1688, 108], [1513, 212], [1447, 522], [841, 560], [1404, 687], [1414, 1007], [1681, 374], [1688, 108]]] , 'int32')
print sar_ver.shape
mask=np.zeros((1439, 1954))
cv2.fillPoly(mask, sar_ver, 255)
sar_ver = np.asarray([(1688, 108), (1513, 212), (1447, 522), (841, 560), (1404, 687), (1414, 1007), (1681, 374), (1688, 108)])
path = Path(sar_ver)
xmin, ymin, xmax, ymax = np.asarray(path.get_extents(), dtype=int).ravel()
plt.imshow(mask[ymin:ymax+1, xmin:xmax+1])
plt.show()
Also, posting mgrid solution helped by Filippo above and on online chat:
import cv2
from matplotlib.path import Path
from PIL import Image
import numpy as np
sar_ver = np.asarray([(1518, 2024), (2018, 2024), (1518, 2524), (1518, 2024)])
imag = cv2.imread('test_image.jpg')
img = cv2.cvtColor(imag, cv2.COLOR_BGR2GRAY)
h, w = img.shape
path = Path(sar_ver)
xmin, ymin, xmax, ymax = np.asarray(path.get_extents(), dtype=int).ravel()
# create a mesh grid of the shape of the final mask
x, y = np.mgrid[:w, :h]
# mesh grid to points
points = np.vstack((x.ravel(), y.ravel())).T
# mask for the point included in the path
mask = path.contains_points(points)
mask = mask.reshape(x.shape).T
im = np.array(img)
bb = np.multiply(im, mask)[ymin:ymax+1, xmin:xmax+1]
# saving image or we can do plt.show
int_ans = bb.astype(np.uint8)
fin = Image.fromarray(int_ans)
fin.save('crop_test.png')
Upvotes: 0
Reputation: 5294
I think you got it almost right in the first attempt, in the second one you're building a meshgrid
for the full image while you just want the shape mask, don't you?
import numpy as np
import matplotlib as mpl
from matplotlib.path import Path
from matplotlib import patches
import matplotlib.pyplot as plt
from PIL import Image
sar_ver = [(16886, 1085), (15139, 2122), (14475, 5226), (8419, 5601),
(14046, 6876), (14147, 10079), (16816, 3748), (16886, 1085)]
path = Path(sar_ver)
xmin, ymin, xmax, ymax = np.asarray(path.get_extents(), dtype=int).ravel()
x, y = np.mgrid[xmin:xmax, ymin:ymax]
points = np.transpose((x.ravel(), y.ravel()))
mask = path.contains_points(points)
mask = mask.reshape(x.shape).T
img = Image.fromarray((mask * 255).astype(np.uint8))
img.save('mask.png')
# plot shape and mask for debug purposes
fig = plt.figure(figsize=(8,4))
gs = mpl.gridspec.GridSpec(1,2)
gs.update(wspace=0.2, hspace= 0.01)
ax = plt.subplot(gs[0])
patch = patches.PathPatch(path, facecolor='orange', lw=2)
ax.add_patch(patch)
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax = plt.subplot(gs[1])
ax.imshow(mask, origin='lower')
plt.savefig("shapes.png", bbox_inches="tight", pad_inches=0)
It produces the mask:
And also plots both the mask and the path for debugging purposes:
The different orientation comes from the different origin position in matplotlib
plots and images, but it should be trivial enough to change it the way you want.
EDIT after latest question edits
Here's an updated script that takes an image, generates a mask for your path and cuts it out. I'm using a dummy image and scaling down shapes a bit so they're easier to work with.
import numpy as np
import matplotlib as mpl
from matplotlib.path import Path
from matplotlib import patches
import matplotlib.pyplot as plt
import skimage.transform
import skimage.data
from PIL import Image
sar_ver = np.asarray([(16886, 1085), (15139, 2122), (14475, 5226), (8419, 5601),
(14046, 6876), (14147, 10079), (16816, 3748), (16886, 1085)])
# reshape into smaller path for faster debugging
sar_ver = sar_ver // 20
# create dummy image
img = skimage.data.chelsea()
img = skimage.transform.rescale(img, 2)
# matplotlib path
path = Path(sar_ver)
xmin, ymin, xmax, ymax = np.asarray(path.get_extents(), dtype=int).ravel()
# create a mesh grid of the shape of the final mask
x, y = np.mgrid[:img.shape[1], :img.shape[0]]
# mesh grid to points
points = np.vstack((x.ravel(), y.ravel())).T
# mask for the point included in the path
mask = path.contains_points(points)
mask = mask.reshape(x.shape).T
# plots
fig = plt.figure(figsize=(8,6))
gs = mpl.gridspec.GridSpec(2,2)
gs.update(wspace=0.2, hspace= 0.2)
# image + patch
ax = plt.subplot(gs[0])
ax.imshow(img)
patch = patches.PathPatch(path, facecolor="None", edgecolor="cyan", lw=3)
ax.add_patch(patch)
# mask
ax = plt.subplot(gs[1])
ax.imshow(mask)
# filter image with mask
ax = plt.subplot(gs[2])
ax.imshow(img * mask[..., np.newaxis])
# remove mask from image
ax = plt.subplot(gs[3])
ax.imshow(img * ~mask[..., np.newaxis])
# plt.show()
plt.savefig("shapes.png", bbox_inches="tight", pad_inches=0)
Upvotes: 2