How to present numpy array into pygame surface? - python

I'm writing a code that part of it is reading an image source and displaying it on the screen for the user to interact with. I also need the sharpened image data. I use the following to read the data and display it in pyGame
def image_and_sharpen_array(file_name):
#read the image data and return it, with the sharpened image
image = misc.imread(file_name)
blurred = ndimage.gaussian_filter(image,3)
edge = ndimage.gaussian_filter(blurred,1)
alpha = 20
out = blurred + alpha*(blurred - edge)
return image,out
#get image data
scan,sharpen = image_and_sharpen_array('foo.jpg')
w,h,c = scan.shape
#setting up pygame
pygame.init()
screen = pygame.display.set_mode((w,h))
pygame.surfarray.blit_array(screen,scan)
pygame.display.update()
And the image is displayed on the screen only rotated and inverted. Is this due to differences between misc.imread and pyGame? Or is this due to something wrong in my code?
Is there other way to do this? The majority of solution I read involved saving the figure and then reading it with ``pyGame''.

I often use the numpy swapaxes() method:
In this case we only need to invert x and y axis (axis number 0 and 1) before displaying our array :
return image.swapaxes(0,1),out

I thought technico provided a good solution - just a little lean on info. Assuming get_arr() is a function that returns the pixel array:
pixl_arr = get_arr()
pixl_arr = numpy.swapaxes(pixl_arr, 0, 1)
new_surf = pygame.pixelcopy.make_surface(pixl_arr)
screen.blit(new_surf, (dest_x, dest_y))
Alternatively, if you know that the image will always be of the same dimensions (as in iterating through frames of a video or gif file), it would be more efficient to reuse the same surface:
pixl_arr = get_arr()
pixl_arr = numpy.swapaxes(pixl_arr, 0, 1)
pygame.pixelcopy.array_to_surface(old_surf, pixl_arr)
screen.blit(old_surf, (dest_x, dest_y))
YMMV, but so far this is working well for me.

Every lib has its own way of interpreting image arrays. By 'rotated' I suppose you mean transposed. That's the way PyGame shows up numpy arrays. There are many ways to make it look 'correct'. Actually there are many ways even to show up the array, which gives you full control over channel representation and so on. In pygame version 1.9.2, this is the fastest array rendering that I could ever achieve. (Note for earlier version this will not work!).
This function will fill the surface with array:
def put_array(surface, myarr): # put array into surface
bv = surface.get_view("0")
bv.write(myarr.tostring())
If that is not working, use this, should work everywhere:
# put array data into a pygame surface
def put_arr(surface, myarr):
bv = surface.get_buffer()
bv.write(myarr.tostring(), 0)
You probably still get not what you want, so it is transposed or have swapped color channels. The idea is, manage your arrays in that form, which suites this surface buffer. To find out what is correct channel order and axes order, use openCV library (cv2.imread(filename)). With openCV you open images in BGR order as standard, and it has a lot of conversion functions. If I remember correctly, when writing directly to surface buffer, BGR is the correct order for 24 bit and BGRA for a 32 bit surface. So you can try to put the image array which you get out of file with this function and blit to the screen.
There are other ways to draw arrays e.g. here is whole set of helper functions http://www.pygame.org/docs/ref/surfarray.html
But I would not recommend using it, since surfaces are not for direct pixel manipulating, you will probably get lost in references.
Small tip: To do 'signalling test' use a picture, like this. So you will immediately see if something is wrong, just load as array and try to render.

My suggestion is to use the pygame.transform module. There are the flip and rotate methods, which you can use to however your transformation is. Look up the docs on this.
My recommendation is to save the output image to a new Surface, and then apply the transformations, and blit to the display.
temp_surf = pygame.Surface((w,h))
pygame.surfarray.blit(temp_surf, scan)
'''transform temp_surf'''
screen.blit(temp_surf, (0,0))
I have no idea why this is. It is probably something to do with the order in which the axes are transferred from a 2d array to a pygame Surface.

Related

Using PIL to draw individual pixels, but the image is blurry

I am trying to create an image made up of coloured squares. I only need each square to be one pixel large, as it is just a single block colour. However, when I use this code, the image generated is extremely blurry. Is there anyway to make the boarders sharp?
def fancycolnw2(seq,m):
data=numbwall(seq,m)
#print(data)
for i in range(len(data)):
for j in range(len(data[i])):
if data[i][j]==' ':
data[i][j]=-1
im = Image.new('RGBA', (len(data[0]),len(data))) # create the Image of size 1 pixel
#print(data)
for i in range(len(data)-1):
for j in range(len(data[i])-1):
#print(i,j)
if data[i][j]==-1:
im.putpixel((j,i), ImageColor.getcolor('black', 'RGBA'))
if data[i][j]==0:
#print('howdy')
im.putpixel((j,i), ImageColor.getcolor('red', 'RGBA'))
if data[i][j]==1:
im.putpixel((j,i), ImageColor.getcolor('blue', 'RGBA'))
if data[i][j]==2:
im.putpixel((j,i), ImageColor.getcolor('grey', 'RGBA'))
im.show()
im.save('simplePixel.png') # or any image format
The result I get looks like this:
Image
It is the correct image, I just wish the boundaries between pixels were sharp. Any help would be greatly appreciated!
The image is perfectly sharp, but rather small. I suspect that you are "zooming in" to view it clearer, and that whatever program you are zooming with is filtering the image, because with most images this looks better. You need to find a viewing program that uses "nearest neighbour" resampling when zooming in, or generate a larger image to start with, for example by setting a 4-by-4 pixel block rather than individual pixels.
(Also, the code says "# or any other image format". Don’t use JPEG for this, as the lossy compression will likely wreck your image.)

Programming a picture maker template in Python possible?

I'm looking for a library that enables to "create pictures" (or even videos) with the following functions:
Accepting picture inputs
Resizing said inputs to fit given template / scheme
Positioning the pictures in pre-set up layers or coordinates
A rather schematic approach to look at this:
whereas the red spots are supposed to represent e.g. text, picture (or if possible video) elements.
The end goal would be to give the .py script multiple input pictures and the .py creating a finished version like mentioned above.
Solutions I tried were looking into Python PIL, but I wasn't able to find what I was looking for.
Yes, it is possible to do this with Python.
The library you are looking for is OpenCV([https://opencv.org][1]/).
Some basic OpenCV python tutorials (https://docs.opencv.org/master/d9/df8/tutorial_root.html).
1) You can use imread() function to read images from files.
2) You can use resize() function to resize the images.
3) You can create a empty master numpy array matching the size and depth(color depth) of the black rectangle in the figure you have shown, resize your image and copy the contents into the empty array starting from the position you want.
Below is a sample code which does something close to what you might need, you can modify this to suit your actual needs. (Since your requirements are not clear I have written the code like this so that it can at least guide you.)
import numpy as np
import cv2
import matplotlib.pyplot as plt
# You can store most of these values in another file and load them.
# You can modify this to set the dimensions of the background image.
BG_IMAGE_WIDTH = 100
BG_IMAGE_HEIGHT = 100
BG_IMAGE_COLOR_DEPTH = 3
# This will act as the black bounding box you have shown in your figure.
# You can also load another image instead of creating empty background image.
empty_background_image = np.zeros(
(BG_IMAGE_HEIGHT, BG_IMAGE_WIDTH, BG_IMAGE_COLOR_DEPTH),
dtype=np.int
)
# Loading an image.
# This will be copied later into one of those red boxes you have shown.
IMAGE_PATH = "./image1.jpg"
foreground_image = cv2.imread(IMAGE_PATH)
# Setting the resize target and top left position with respect to bg image.
X_POS = 4
Y_POS = 10
RESIZE_TARGET_WIDTH = 30
RESIZE_TARGET_HEIGHT = 30
# Resizing
foreground_image= cv2.resize(
src=foreground_image,
dsize=(RESIZE_TARGET_WIDTH, RESIZE_TARGET_HEIGHT),
)
# Copying this into background image
empty_background_image[
Y_POS: Y_POS + RESIZE_TARGET_HEIGHT,
X_POS: X_POS + RESIZE_TARGET_WIDTH
] = foreground_image
plt.imshow(empty_background_image)
plt.show()

Analyzing pixel RGB values with PIL

I'm working on a project where I need to find the RGB values of each pixel in a picture. How could I do this using PIL? I know that Pillow is better, but since I only need to do this one thing I thought I could just use PIL. If this won't work as well please tell me.
from PIL import Image
img = Image.open("filename.png")
pixels = img.load()
#get the B value of the pixel at x=23, y=42
print pixels[23, 42][2]
The previous answer is a good solution but just suggesting another way which is one line:
from scipy import misc;
imgData = misc.imread('./image.png');
You can then easily get the colors at every pixels you need.
Kevin got it pretty much spot on, you can also use getdata() to return a list of tuples.
I may have got this totally wrong, but load() might work better if you need particular pixels, and getdata() if you need all of them. Also, it's a good idea to convert to RGB if it's just a normal image, I've had errors before by not doing that.
image = Image.open('filename').convert('RGB')
width, height = image.size
#Get pixels in a list of tuples
pixels = image_input.getdata()
#If you need a flat list containing all the colours
bytes = [j for i in pixels for j in i]
If you needed to do stuff to the pixels and rebuild the image after, that's where the image size comes in useful.

opencv zoom function strange results

i am trying to write a zoom function which looks something like this:
centre = ((im.width-1)/2, (im.height-1)/2)
width = int(im.width/(2.0*level))
height = int(im.height/(2.0*level))
rect = (centre[0]-width, centre[1]-height, width*2, height*2)
dst = cv.GetSubRect(im, rect)
cv.Resize(dst, im)
when I use exactly what is written above, I get an odd result where the bottom half of the resultant image is distorted and blurry. However when I replace the line cv.Resize(dst, im) with
size = cv.CloneImage(im)
cv.Resize(dst, size)
im = size
it works fine. Why is this? is there something fundamentally wrong with the way i am performing the zoom?
cv.Resize requires source and destination to be separate memory locations.
Now in the first snippet of your code, you are using cv.GetSubRect to generate an object pointing to area of image which you wish to zoom in. Here the new object is NOT pointing to a new memory location. It is pointing to a memory location which is a subset of original object.
Since cv.Resize requires both the memory locations to be different, what you are getting is a result of undefined behavior.
In the second part of your code you are fulfilling this criteria by using cv.CloneImage.
you are first creating a copy of im (i.e. size. however you could have used a blank image aswell) and then you are using cv.Resize to resize dst and write the resulting image in size.
My advice is to go through the function documentation before using them.

Using PIL to fill empty image space with nearby colors (aka inpainting)

I create an image with PIL:
I need to fill in the empty space (depicted as black). I could easily fill it with a static color, but what I'd like to do is fill the pixels in with nearby colors. For example, the first pixel after the border might be a Gaussian blur of the filled-in pixels. Or perhaps a push-pull type algorithm described in The Lumigraph, Gortler, et al..
I need something that is not too slow because I have to run this on many images. I have access to other libraries, like numpy, and you can assume that I know the borders or a mask of the outside region or inside region. Any suggestions on how to approach this?
UPDATE:
As suggested by belisarius, opencv's inpaint method is perfect for this. Here's some python code that uses opencv to achieve what I wanted:
import Image, ImageDraw, cv
im = Image.open("u7XVL.png")
pix = im.load()
#create a mask of the background colors
# this is slow, but easy for example purposes
mask = Image.new('L', im.size)
maskdraw = ImageDraw.Draw(mask)
for x in range(im.size[0]):
for y in range(im.size[1]):
if pix[(x,y)] == (0,0,0):
maskdraw.point((x,y), 255)
#convert image and mask to opencv format
cv_im = cv.CreateImageHeader(im.size, cv.IPL_DEPTH_8U, 3)
cv.SetData(cv_im, im.tostring())
cv_mask = cv.CreateImageHeader(mask.size, cv.IPL_DEPTH_8U, 1)
cv.SetData(cv_mask, mask.tostring())
#do the inpainting
cv_painted_im = cv.CloneImage(cv_im)
cv.Inpaint(cv_im, cv_mask, cv_painted_im, 3, cv.CV_INPAINT_NS)
#convert back to PIL
painted_im = Image.fromstring("RGB", cv.GetSize(cv_painted_im), cv_painted_im.tostring())
painted_im.show()
And the resulting image:
A method with nice results is the Navier-Stokes Image Restoration. I know OpenCV has it, don't know about PIL.
Your example:
I did it with Mathematica.
Edit
As per your reuquest, the code is:
i = Import["http://i.stack.imgur.com/uEPqc.png"];
Inpaint[i, ColorNegate#Binarize#i, Method -> "NavierStokes"]
The ColorNegate# ... part creates the replacement mask.
The filling is done with just the Inpaint[] command.
Depending on how you're deploying this application, another option might be to use the Gimp's python interface to do the image manipulation.
The doc page I linked to is oriented more towards writing GIMP plugins in python, rather than interacting with a background gimp instance from a python app, but I'm pretty sure that's also possible (it's been a while since I played with the gimp/python interface, I'm a little hazy).
You can also create the mask with the function CreateImage(), for instance:
inpaint_mask = cv.CreateImage(cv.GetSize(im), 8, 1)

Categories