Python image mirroring - python

I've been making a picture mirroring in horizontal and vertical axes. Now I'm going to make the diagonal.
I had done the hori and verti width two for loops which in the hori scenario loops through all the pixels in the height and only the half of the pixels in the width. Then it gets the color of the pixel and set the same color to the pixel on the other side. Going from the getWidth(pic) to the center.
Then I have my mirror in the middle of the pic. How to do the diagonal way?
Edit:
img_src = makePicture(pickAFile())
W = getWidth(img_src)
H = getHeight(img_src)
for x in range(W):
for y in range(H):
p = getPixel(img_src, x, y)
colorInSrc = getColor( getPixel(img_src, x, y) )
destPixel = getPixel(img_src, H-y-1, W-x-1)
setColor(destPixel, colorInSrc)

Using PIL (the Python Imaging Library) this is a relatively straightforward task. Notice however, that the output image is square -- thus not the same size as the original image.
Here is the code:
from PIL import Image, ImageDraw
# load the image, create the mirrored image, and the result placeholder
img = Image.open('img.png')
mirror = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90)
sz = max(img.size + mirror.size)
result = Image.new(img.mode, (sz,sz))
result.paste(img, (0,0)+img.size)
# now paste the mirrored image, but with a triangular binary mask
mask = Image.new('1', mirror.size)
draw = ImageDraw.Draw(mask)
draw.polygon([0,0,0,sz,sz,sz], outline='white', fill='white')
result.paste(mirror, (0,0)+mirror.size, mask)
# clean up and save the result
del mirror, mask, draw
result.save('result.png')

If I understood correctly what you need is to "flip" the image by a diagonal. Since there are two of them I'll presume that you mean the one that goes from left bottom to right top.
In order to flip by this diagonal you need to transform each row from the source in columns in the destination. The left part of the rows will become the bottom part of the new columns. Also the topmost row will become the rightmost column. You will need to do this pixel by pixel on the whole image. Also keep in mind that the width and height of the image will be swapped.
Edit: A small example. Say you start with an image 5 pixels wide and 3 pixels high (5x3). You will need to create a new blank image 3 pixels wide and 5 pixels high.
If you start pixel numbering from left top corner with (0,0), then this pixel will end up at (2,4) in the new image, pixel (1,0) will end at (2,3) and so on.
If your original width and height are W and H then you should use something like this:
for x in xrange(W):
for y in xrange(H):
p = img_src.getpixel(x, y)
img_dest.setpixel(H-y-1, W-x-1)
This should work, but is not tested.

It's not really an Python question, is it?
The easiest solution would be to first mirror horizontal and then vertical.
Another one would be to switch pixel rows with columns.
Or to do your algorithm but switch the pixels from left-top to bottom-right...

Here's how to mirror diagonally in JES; It only works for a square image though:
def mirrorDiagonal(picture):
for sourceX in range(0,getWidth(picture)):
for sourceY in range (0,getHeight(picture)):
pex=getPixel(picture,sourceY,sourceX)
pix=getPixel(picture, sourceX,sourceY)
color=getColor(pix)
setColor(pex,color)
show(picture)

Related

How to eliminate pixel offset generated while try to draw points based on the color of the pixels [OpenCV]

I'm trying to draw a horizontal line across a shape (ellipse in this instance with only the centroid and the boundary of the ellipse on a black background) starting from the centroid of the shape (ellipse) . I started off checking each and every pixel along +x and -x axes from centroid and replacing each non-green pixel(boundary) to a white pixel (essentially drawing a line pixel by pixel) and stop converting as soon as I reach the first green pixel (boundary). Code is given at the end
According to my logic, the line (created using points) should stop as soon as it reaches the boundary aka first green pixel along a particular axis but there is a slight offset of the detected boundary. In the given image, you can clearly see the right and left most points calculated by checking each and every pixel is slightly off center from the actual line
Images are enlarged for better view
I checked my code multiple times and I freshly drew ellipses every time to make sure there is no stray green pixels left on the image but the offset is consistent for each try
So my question will be: How do I get rid of this offset and make my line incident on the boundary perfectly? Is this a visual glitch or Am I doing something wrong?
Note: I know there are rectFitting and MinAreaRect functions which I can use to draw perfect bounding boxes to get points but I wanted to know why this is happening. I'm not looking for optimal method instead I'm looking for the cause and solution for this issue.
If you can suggest better/accurate title, its much appreciated. I think I have explained everything for the time being.
Code:
import cv2
import numpy as np
import matplotlib.pyplot as plt
%inline matplotlib
#Function to plot images
def display_img(img,name):
fig = plt.figure(figsize=(4,4))
ax = fig.add_subplot(111)
ax.imshow(img,cmap ="gray")
plt.title(name)
#A black canvas
canvas = np.zeros((1600,1200,3),np.uint8)
#Value obtained after ellipse fitting an object
val = ((654, 664),(264, 266),80)
centroid = (val[0][0],val[0][1])
#Drawing the ellipse on the canvas(green)
ell = cv2.ellipse(canvas,val,(0,255,0),1)
centroid_ = cv2.circle(canvas,centroid,1,(255,0,0),10) #High thickness to see it visibly (Red)
display_img(canvas,"Canvas w/ ellipse and centroid")
#variables for centers
y_center = centroid[1]
#Variables which iterate over time
right_pt = centroid[0]
left_pt = centroid[0]
#Using while loops to find the distance from the center to the
#nearby first green pixel (leftmost and rightmost boundary)
while(np.any(canvas[right_pt,y_center] != [0,255,0])):
cv2.circle(canvas,(right_pt,y_center),1,(255,255,255),1)
right_pt += 1
while(np.any(canvas[left_pt,y_center] != [0,255,0])):
cv2.circle(canvas,(left_pt,y_center),1,(255,255,255),1)
left_pt -= 1
#Drawing the obtained points
canvas = cv2.circle(canvas,(right_pt,y_center),1,(0,255,0),2)
canvas = cv2.circle(canvas,(left_pt,y_center),1,(0,255,0),2)
display_img(canvas,"Finale")
There are couple of problems, one hiding neatly behind another.
The first issue is evident in this snippet of code extracted from your script:
# ...
val = ((654, 664),(264, 266),80)
centroid = (val[0][0],val[0][1])
y_center = centroid[1]
right_pt = centroid[0]
left_pt = centroid[0]
while(np.any(canvas[right_pt,y_center] != [0,255,0])):
cv2.circle(canvas,(right_pt,y_center),1,(255,255,255),1)
right_pt += 1
# ...
Notice that you use the X and Y coordinates of the point you want to process
(represented by right_pt and y_center respectively) in the same order
to do both of the following:
index a numpy array: canvas[right_pt,y_center]
specify point coordinate to an OpenCV function: (right_pt,y_center)
That is a problem, because each of those libraries expects a different order:
numpy indexing is by default row-major, i.e. img[y,x]
points and sizes in OpenCV are column-major, i.e. (x,y)
In this particular case, the error is in the order of indexes for the numpy array canvas.
To fix it, just switch them around:
while(np.any(canvas[y_center,right_pt] != [0,255,0])):
cv2.circle(canvas,(right_pt,y_center),1,(255,255,255),1)
right_pt += 1
# ditto for the second loop
Once you fix that, and run your script, it will crash with an error like
while(np.any(canvas[y_center,right_pt] != [0,255,0])):
IndexError: index 1200 is out of bounds for axis 1 with size 1200
Why didn't this happen before? Since the centroid was (654, 664)
and you had the coordinates swapped, you were looking 10 rows away
from where you were drawing.
The problem lies in the fact that you're drawing white circles
into the same image you're also searching for green pixels, combined
with perhaps mistaken interpretation of what the radius parameter of
cv2.circle does. I suppose the best way to show this with an image
(representing 5 rows of 13 pixels):
The red dots are centers of respective circles,
white squares are the pixels drawn,
black squares are the pixels left untouched
and the yellow arrows indicate the direction of iteration along the row.
On the left side, you can see circle with radius 1, on the right radius 0.
Let's say we're approaching the green area we want to detect:
And make another iteration:
Oops, with radius of 1, we just changed the green pixel we're looking for to white.
Hence we can never find any green pixels (with the exception of the first point tested, since at that point we haven't drawn anything yet, and only in the first loop), and the loop will run out of bounds of the image.
There are several options on how to resolve this problem. The simplest one, if you're fine with a thinner line, is to change the radius to 0 in both calls to cv2.circle. Another possibility would be to cache a copy of "the row of interest", so that any drawing you do on canvas won't effect the search:
target_row = canvas[y_center].copy()
while(np.any(target_row[right_pt] != [0,255,0])):
cv2.circle(canvas,(right_pt,y_center),1,(255,255,255),1)
right_pt += 1
or
target_row = canvas[y_center] != [0,255,0]
while(np.any(target_row[right_pt])):
cv2.circle(canvas,(right_pt,y_center),1,(255,255,255),1)
right_pt += 1
or even better
target_row = np.any(canvas[y_center] != [0,255,0], axis=1)
while(target_row[right_pt]):
cv2.circle(canvas,(right_pt,y_center),1,(255,255,255),1)
right_pt += 1
Finally, you could skip the drawing in the loops, and just use a single function call to draw a line connecting the two endpoints you found.
target_row = np.any(canvas[y_center] != [0,255,0], axis=1)
while(target_row[right_pt]):
right_pt += 1
while(target_row[left_pt]):
left_pt -= 1
#Drawing the obtained points
cv2.line(canvas, (left_pt,y_center), (right_pt,y_center), (255,255,255), 2)
cv2.circle(canvas, (right_pt,y_center), 1, (0, 255, 0), 2)
cv2.circle(canvas, (left_pt,y_center), 1, (0, 255, 0), 2)
Bonus: Let's get rid of the explicit loops.
left_pt, right_pt = np.where(np.all(canvas[y_center] == [0,255,0], axis=1))[0]
This will (obviously) work only if there are two matching pixels on the row of interest. However, it is trivial to extend this to find the first one from the ellipses center in each direction (you get an array of all X coordinates/columns that contain a green pixel for that row).
Cropped output (generated by cv2.imshow) of that implementation can be seen in the following image (the centroid is blue, since you used (255,0,0) to draw it, and OpenCV uses BGR order by default):

OpenCv Shape Detection To Shape Transformation (Pythhon)

How can I take a image of a square that has been detected using shape detection algorithm on openCV and "Transform" it to a triangle the quickest way possible?
For EXAMPLE say one of the images from google is a square and i want to see the fastsest way to turn it to a triangle. How would I go about researching this? I have looked up shape transformation for openCV but it mostly covers zooming in on the image and changing views.
One way to distort a rectangle to a triangle is to use a perspective transformation in Python/OpenCV.
Read the input
Get the input control points as the 4 corners of the input image
Define the output control points as to top 2 points close to the top center of the output (-+1 or whatever separation you want) and bottom 2 points the same as the input bottom two points
Compute the perspective transformation matrix from the control points
Warp the input to the output
Save the result.
Input:
import cv2
import numpy as np
# Read source image.
img_src = cv2.imread('lena.png')
hh, ww = img_src.shape[:2]
# Four corners of source image ordered clockwise from top left corner
# Coordinates are in x,y system with x horizontal to the right and y vertical downward
pts_src = np.float32([[0,0], [ww-1,0], [ww-1,hh-1], [0,hh-1]])
# Four corners of destination image.
pts_dst = np.float32([[ww/2-1, 0], [ww/2+1,0], [ww-1,hh-1], [0,hh-1]])
# Get perspecive matrix if only 4 points
m = cv2.getPerspectiveTransform(pts_src,pts_dst)
# Warp source image to destination based on matrix
img_out = cv2.warpPerspective(img_src, m, (ww,hh), cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255))
# Save output
cv2.imwrite('lena_triangle.png', img_out)
# Display result
cv2.imshow("Warped Source Image", img_out)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result (though likely not what you want).
If you separate the top two output points by a larger difference, then it will look more like the input.
For example, using ww/2-10 and ww/2+10 rather than ww/2-1 and ww/2+1 for the top two output points, I get:

How to skew an image by moving its vertex?

I'm trying to find a way to transform an image by translating one of its vertexes.
I have already found various methods for transforming an image like rotation and scaling, but none of the methods involved skewing like so:
There is shearing, but it's not the same since it can move two or more of the image's vertex while I only want to move one.
What can I use that can perform such an operation?
I took your "cat-thing" and resized it to a nice size, added some perfectly vertical and horizontal white gridlines and added some extra canvas in red at the bottom to give myself room to transform it. That gave me this which is 400 pixels wide and 450 pixels tall:
I then used ImageMagick to do a "Bilinear Forward Transform" in Terminal. Basically you give it 4 pairs of points, the first pair is where the top-left corner is before the transform and then where it must move to. The next pair is where the top-right corner is originally followed by where it ends up. Then the bottom-right. Then the bottom-left. As you can see, 3 of the 4 pairs are unmoved - only the bottom-right corner moves. I also made the virtual pixel black so you can see where pixels were invented by the transform in black:
convert cat.png -matte -virtual-pixel black -interpolate Spline -distort BilinearForward '0,0 0,0 399,0 399,0 399,349 330,430 0,349 0,349' bilinear.png
I also did a "Perspective Transform" using the same transform coordinates:
convert cat.png -matte -virtual-pixel black -distort Perspective '0,0 0,0 399,0 399,0 399,349 330,430 0,349 0,349' perspective.png
Finally, to illustrate the difference, I made a flickering comparison between the 2 images so you can see the difference:
I am indebted to Anthony Thyssen for his excellent work here which I commend to you.
I understand you were looking for a Python solution and would point out that there is a Python binding to ImageMagick called Wand which you may like to use - here.
Note that I only used red and black to illustrate what is going on (atop the Stack Overflow white background) and where aspects of the result come from, you would obviously use white for both!
The perspective transformation is likely what you want, since it preserves straight lines at any angle. (The inverse bilinear only preserves horizontal and vertical straight lines).
Here is how to do it in ImageMagick, Python Wand (based upon ImageMagick) and Python OpenCV.
Input:
ImageMagick
(Note the +distort makes the output the needed size to hold the full result and is not restricted to the size of the input. Also the -virtual-pixel white sets color of the area outside the image pixels to white. The points are ordered clockwise from the top left in pairs as inx,iny outx,outy)
convert cat.png -virtual-pixel white +distort perspective \
"0,0 0,0 359,0 359,0 379,333 306,376 0,333 0,333" \
cat_perspective_im.png
Python Wand
(Note the best_fit=true makes the output the needed size to hold the full result and is not restricted to the size of the input.)
#!/bin/python3.7
from wand.image import Image
from wand.display import display
with Image(filename='cat.png') as img:
img.virtual_pixel = 'white'
img.distort('perspective', (0,0, 0,0, 359,0, 359,0, 379,333, 306,376, 0,333, 0,333), best_fit=True)
img.save(filename='cat_perspective_wand.png')
display(img)
Python OpenCV
#!/bin/python3.7
import cv2
import numpy as np
# Read source image.
img_src = cv2.imread('cat.png')
# Four corners of source image
# Coordinates are in x,y system with x horizontal to the right and y vertical downward
pts_src = np.float32([[0,0], [359,0], [379,333], [0,333]])
# Four corners of destination image.
pts_dst = np.float32([[0, 0], [359,0], [306,376], [0,333]])
# Get perspecive matrix if only 4 points
m = cv2.getPerspectiveTransform(pts_src,pts_dst)
# Warp source image to destination based on matrix
# size argument is width x height
# compute from max output coordinates
img_out = cv2.warpPerspective(img_src, m, (359+1,376+1), cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(255, 255, 255))
# Save output
cv2.imwrite('cat_perspective_opencv.png', img_out)
# Display result
cv2.imshow("Warped Source Image", img_out)
cv2.waitKey(0)
cv2.destroyAllWindows()

Method for cropping an image along color borders?

Images such as this one (https://imgur.com/a/1B7nQnk) should be cropped into individual cells. Sadly, the vertical distance is not static, so a more complex method needs to be applied. Since the cells have alternating background colors (grey and white, maybe not visible at low contrast monitors), I thought it might be possible to get the coordinates of the boundaries between white and grey, with which accurate cropping can be accomplished. Is there a way to, e.g., transform the image into a giant two dimensional array, with digits corresponding to the color of the pixel ?... so basically:
Or is there another way?
Here's a snippet that shows how to access the individual pixels of an image. For simplicity, it first converts the image to grayscale and then prints out the first three pixels of each row. It also indicates where the brightness of the first pixel is different from that pixel in that column on the previous row—which you could use to detect the vertical boundaries.
You could do something similar over on the right side to determine where the boundaries are on that side ( you've determined the vertical ones).
from PIL import Image
IMAGE_FILENAME = 'cells.png'
WHITE = 255
img = Image.open(IMAGE_FILENAME).convert('L') # convert image to 8-bit grayscale
WIDTH, HEIGHT = img.size
data = list(img.getdata()) # convert image data to a list of integers
# convert that to a 2D list (list of lists of integers)
data = [data[offset:offset+WIDTH] for offset in range(0, WIDTH*HEIGHT, WIDTH)]
prev_pixel = WHITE
for i, row in enumerate(range(HEIGHT)):
possible_boundary = ' boundary?' if data[row][0] != prev_pixel else ''
print(f'row {i:5,d}: {data[row][:3]}{possible_boundary}')
prev_pixel = data[row][0]

Mirror Image but wrong size

I am trying to input an image (image1) and flip it horizontally and then save to a file (image2). This works but not the way I want it to
currently this code gives me a flipped image but it just shows the bottom right quarter of the image, so it is the wrong size. Am I overwriting something somewhere? I just want the code to flip the image horizontally and show the whole picture flipped. Where did I go wrong?
and I cannot just use a mirror function or reverse function, I need to write an algorithm
I get the correct window size but the incorrect image size
def Flip(image1, image2):
img = graphics.Image(graphics.Point(0, 0), image1)
X, Y = img.getWidth(), img.getHeight()
for y in range(Y):
for x in range(X):
r, g, b = img.getPixel(x,y)
color = graphics.color_rgb(r, g, b)
img.setPixel(X-x, y, color)
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
img.draw(win)
img.save(image2)
I think your problem is in this line:
win = graphics.GraphWin(img, img.getWidth(), img.getHeight())
The first argument to the GraphWin constructor is supposed to be the title, but you are instead giving it an Image object. It makes me believe that maybe the width and height you are supplying are then being ignored. The default width and height for GraphWin is 200 x 200, so depending on the size of your image, that may be why only part of it is being drawn.
Try something like this:
win = graphics.GraphWin("Flipping an Image", img.getWidth(), img.getHeight())
Another problem is that your anchor point for the image is wrong. According to the docs, the anchor point is where the center of the image will be rendered (thus at 0,0 you are only seeing the bottom right quadrant of the picture). Here is a possible solution if you don't know what the size of the image is at the time of creation:
img = graphics.Image(graphics.Point(0, 0), image1)
img.move(img.getWidth() / 2, img.getHeight() / 2)
You are editing your source image. It would be
better to create an image copy and set those pixels instead:
create a new image for editing:
img_new = img
Assign the pixel values to that:
img_new.setPixel(X-x, y, color)
And draw that instead:
win = graphics.GraphWin(img_new, img_new.getWidth(), img_new.getHeight())
img_new.draw(win)
img_new.save(image2)
This will also check that your ranges are correct. if they are not, you will see both flipped and unflipped portions in the final image, showing which portions are outside of your ranges.
If you're not opposed to using an external library, I'd recommend the Python Imaging Library. In particular, the ImageOps module has a mirror function that should do exactly what you want.

Categories

Resources