Issue
Given an image with a mirror line (gradient m
and intercept c
), like below,
I want to mirror the left side of the image i.e. the portion left of the mirror line should be reflected and the original right side replaced. The resulting output of course need not have the same dimensions as the input image. Is there a reasonably fast way to do this in Python?
Solution
A convenient way to think of a reflection is in an orthonormal basis (for short onb) where one of the basis vectors is pointing in the mirror axis direction. See picture below. If you have a point represented in that basis mirroring it becomes flipping the sign of the component corresponding to the other axis. So our approach here is to take the coordinates of each pixel, representing it in the onb, flipping the sign and then go back to the standard basis.
As an example I am going to use the Wikipedia-logo-v2 by Nohat. It is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license.
First we plot the example picture together with our onb.
image = cv2.imread('wikipedia.jpg')
image = np.swapaxes(image,0,1)/255
origin = np.array([48,55], dtype='float')
b1 = np.array([12,-2], dtype='float')
b2 = np.array([-2,-12], dtype='float')
b1 /= -np.linalg.norm(b1)
b2 /= np.linalg.norm(b2)
plt.imshow(np.swapaxes(image,0,1))
plt.scatter(origin[0], origin[1])
plt.arrow(*origin,*b1*20,head_length = 10, head_width = 5)
plt.arrow(*origin,*b2*20,head_length = 10, head_width = 5)
Now we calculate the old coordinates, the coefficients of all coordinates in the onb and the coordinates after the reflection. Here it comes handy that our basis is not just orthogonal but also normed so we can calculate the coefficients as a scalar product. Notice that the actual reflection happens where we take the absolute value of c1
i.e. the coefficients of the first basis vector which is pointing to the right in the picture above. That means that the coefficients that used to be negative i.e. are left of the origin now are to the right.
points = np.moveaxis(np.indices(image.shape[:2]),0,-1).reshape(-1,2)
b1.shape,(points-origin.reshape(1,2)).shape, ((points-origin.reshape(1,2))@b1).shape
c1 = (points-origin.reshape(1,2))@b1
c2 = (points-origin.reshape(1,2))@b2
coordinates = ((b1.reshape(2,1)*np.abs(c1)+b2.reshape(2,1)*c2)+origin.reshape(2,1))
The problem now is just that we can't index the picture by the new coordinates since they are not integers. We could round them but that could produce unwanted effects. Instead we take a weighed average of the sounding pixels in a knn like method.
class Image_knn():
def fit(self, image):
self.image = image.astype('float')
def predict(self, x, y):
image = self.image
weights_x = [(1-(x % 1)).reshape(*x.shape,1), (x % 1).reshape(*x.shape,1)]
weights_y = [(1-(y % 1)).reshape(*x.shape,1), (y % 1).reshape(*x.shape,1)]
start_x = np.floor(x)
start_y = np.floor(y)
return sum([image[np.clip(np.floor(start_x + x), 0, image.shape[0]-1).astype('int'),
np.clip(np.floor(start_y + y), 0, image.shape[1]-1).astype('int')] * weights_x[x]*weights_y[y]
for x,y in itertools.product(range(2),range(2))])
And finally we can apply our coordinates and get the mirrored picture.
image_model = Image_knn()
image_model.fit(image)
transformed_image = image_model.predict(*coordinates).reshape(*image.shape)
plt.imshow(np.swapaxes(transformed_image,0,1))
Q: Can I mirror in the other direction?
Yea sure just put a minus in front of the first basis vector i.e. replace
b1 = np.array([12,-2], dtype='float')
with
b1 = -np.array([12,-2], dtype='float')
Possible Issue
When I mirrored in the other direction for testing I got this little.artifact
The artifact in the top right that looks like one needs to clean the screen comes from the following problem: We now mirror a smaller space in to a slightly bigger therefore we don't have enough pixels to paint the upper right. What we do by default in image_knn
is to clip the coordinates to an area where we have information. That means when we ask image knn
for pixels for the upper right coming from outside the lower left it gives us the pixels at the boundary of the image. This looks good if there is a background but if an object touches the edge of the picture it looks odd like here. Just something to keep in mind when using this.
Answered By - user2640045
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.