Reputation: 3096
Given a numpy array that contains a grey scale image of a segmented object. The numpy array has dimensions (32,32)
. The background of this object is coded as zeros, the object itself has a number between (0,255].
Example (with dimensions (7,7)
):
# Input
> np.array([[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,1,2,3,0,0],[0,0,2,2,2,0,0],[0,0,1,2,3,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]], dtype=np.uint8)
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 2, 3, 0, 0],
[0, 0, 2, 2, 2, 0, 0],
[0, 0, 1, 2, 3, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
The object in the numpy array can have an arbitrary shape. I'd like to stretch (distort) the object such that it becomes a circle, regardless of its previous shape. The circle should fill the entire 32x32 array. The values should be linearly interpolated. Example (simplified, without linear interpolation):
# Desired output = Circle with linearly interpolated values
array([[0, 0, 0, 2, 0, 0, 0],
[0, 1, 1, 2, 3, 3, 0],
[0, 1, 1, 2, 3, 3, 0],
[2, 2, 2, 2, 2, 2, 2],
[0, 1, 2, 2, 3, 3, 0],
[0, 1, 2, 2, 3, 3, 0],
[0, 0, 0, 2, 0, 0, 0]], dtype=uint8)
How would I do that. Is there maybe an OpenCV function to distort objects to circles?
Upvotes: 2
Views: 965
Reputation: 1162
Lets divide your problem in 3 steps:
Resize your image to 32x32
You can use the cv2.resize()
to make the job for you. It will do all the necessary interpolation to make the image the size you want
square = np.array([[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,1,2,3,0,0],[0,0,2,2,2,0,0],[0,0,1,2,3,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]], dtype=np.uint8)
resized_image = cv2.resize(square, (32, 32))
Remap your matrix from a square to circle
Mathematically speaking there are many ways to create a transformation from a square matrix to a circle. There is a very interesting topic about this here. Basically, you have to define a function to go from a set of points [x, y]
to another set of points [u, v]
, one possible solution mentioned by Ch Fong is:
u = x √(x² + y² - x²y²) / √(x² + y²)
v = y √(x² + y² - x²y²) / √(x² + y²)
You can use this equation with cv2.remap()
function to obtain the transformation you want. However there is a tool called squircle
that do all the work for you! By reading their doc, you can make a simple code to make this transformation:
Import with:
from squircle import to_circle
from PIL import Image
Call the transformation after opening the image:
circle = to_circle(square)
Trim edges and resize if necessary
Now, you just have to trim all zeros from your matrix, and resize it again so all 32x32 matrix have content inside it. A solution you can use is described here where circle
is your remapped image. After you cut all initial and final lines and columns that are all zeros, you have to resize it again to 32x32.
coords = np.argwhere(circle)
x_min, y_min = coords.min(axis=0)
x_max, y_max = coords.max(axis=0)
b = cropped = circle[x_min:x_max+1, y_min:y_max+1]
resized_cropped_image = cv2.resize(cropped, (32, 32))
Example 1 - Using your array:
Example 2 - Using another image:
You can find a Jupyter Notebook for this example in my Github page. The full code can be seen below
import numpy as np
import cv2
import matplotlib.pyplot as plt
from squircle import to_circle, to_square
from PIL import Image
square = np.asarray(Image.open('Picture.png').convert('L'))
#square = np.array([[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,1,2,3,0,0],[0,0,2,2,2,0,0],[0,0,1,2,3,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]], dtype=np.uint8)
resized_image = cv2.resize(square, (32, 32))
circle = to_circle(resized_image)
coords = np.argwhere(circle)
x_min, y_min = coords.min(axis=0)
x_max, y_max = coords.max(axis=0)
b = cropped = circle[x_min:x_max+1, y_min:y_max+1]
resized_cropped_image = cv2.resize(cropped, (32, 32))
fig, axs = plt.subplots(2, 2, figsize=(10,10))
axs[0, 0].imshow(square)
axs[0, 0].set_title('Original')
axs[0, 1].imshow(resized_image)
axs[0, 1].set_title('Resized to 32x32')
axs[1, 0].imshow(circle)
axs[1, 0].set_title('Remapped to cicle')
axs[1, 1].imshow(resized_cropped_image)
axs[1, 1].set_title('Trimmed')
Upvotes: 3