0

Given an image with a mirror line (gradient m and intercept c), like below,

enter image description here

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?

  • 1
    does this answer your question? https://stackoverflow.com/questions/14774230/mirror-image-diagonally-in-python – sehan2 Jul 30 '21 at 13:36
  • @sehan2 not quite, seems like that takes the mirror line as the diagonal (bottom-left pixel to top-right pixel) ... I want to use a general mirror line (given its equation) – Vainmonde De Courtenay Jul 30 '21 at 13:49
  • 1
    well then you want to look into resampling your image so that your line is either vertical, horizontal or diagonal and then apply one of the mirroring methods. Here is a method to do that https://pythontic.com/image-processing/pillow/rotate – sehan2 Jul 30 '21 at 13:51

1 Answers1

2

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)

example picture with onb

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))

mirrored picture

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 mirroring 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.

Lukas S
  • 3,212
  • 2
  • 13
  • 25
  • Amazing answer!! Perfectly working. Just wondering if there is any way to include colour i.e. avoid grayscaling? – Vainmonde De Courtenay Aug 05 '21 at 14:43
  • @VainmondeDeCourtenay Now my code works on colored images, I added a paragraph on how to mirror in the other direction and I addressed a small but unavoidable problem. – Lukas S Aug 05 '21 at 15:34
  • When I try using with coloured data I get `Invalid shape (3, 1320, 1440) for image data` - something to do with `np.swapaxes` maybe? – Vainmonde De Courtenay Aug 05 '21 at 17:00
  • @VainmondeDeCourtenay that sounds like you transpose your image like I did in 2d. Are you 100% sure you copied all the code and not just one block? Also I find a point where I depend on my image being 100x100 and corrected that. So please copy the code again and retry. – Lukas S Aug 05 '21 at 17:14
  • If it's then still not working I suggest we talk there: https://chat.stackoverflow.com/rooms/info/235670/mirror-image-in-line – Lukas S Aug 05 '21 at 17:17
  • Thanks I got it! (just silly cv2 ordering problems...) Answer is perfect, thanks so much, I can only be sorry that such great work doesn't get as much exposure as it deserves – Vainmonde De Courtenay Aug 05 '21 at 17:21
  • @VainmondeDeCourtenay that's great. You're welcome and have a nice day. – Lukas S Aug 05 '21 at 17:34