I'm using the physics simulator pybullet, and want to save an image from a 'virtual camera' within my simulation, using the following two lines of code.
camera1 = pybullet.getCameraImage(900,600)
im1 = Image.fromarray(camera1[2])
The first line uses pybullet's getCameraImage function to return an unprocessed image. camera1[2]
is a list of pixel colors in RGBA format, in range [0..255] for each color.
The second line should take that array and convert it into an image which I can then save and view.
When I run the code I get the following error message:
Traceback (most recent call last):
File "generate_dataset.py", line 43, in <module>
im1 = Image.fromarray(camera1[2], "RGBA")
File "/usr/lib/python3/dist-packages/PIL/Image.py", line 2140, in
fromarray
arr = obj.__array_interface__
AttributeError: 'tuple' object has no attribute '__array_interface__'
The code was working yesterday on Ubuntu 14.04, but today I upgraded to Ubuntu 16.04 and the code has stopped working. I have tried running it with Python 2.7.12 and Python 3.5.2, both versions get the same error.
Things I have tried:
Adding another line to convert the list into a numpy array:
camera1 = p.getCameraImage(900,600)
imarray = np.asarray(camera1[2])
im1 = Image.fromarray(imarray)
Resulted in:
Traceback (most recent call last):
File "generate_dataset.py", line 42, in <module>
im1 = Image.fromarray(imarray)
File "/usr/local/lib/python2.7/dist-packages/PIL/Image.py", line 2431, in fromarray
raise TypeError("Cannot handle this data type")
TypeError: Cannot handle this data type
Changing the last line to:
im1 = Image.fromarray(imarray.astype('uint8'))
Resulted in:
Traceback (most recent call last):
File "generate_dataset.py", line 42, in <module>
im1 = Image.fromarray(imarray.astype('uint8'))
File "/usr/lib/python3/dist-packages/PIL/Image.py", line 2165, in fromarray
size = shape[1], shape[0]
IndexError: tuple index out of range
Extra info, if needed
Pybullet documentation: https://docs.google.com/document/d/10sXEhzFRSnvFcl3XxNGhnD4N2SedqwdAvK3dsihxVUA/edit#heading=h.2ye70wns7io3
PIL documentation: https://pillow.readthedocs.io/en/3.1.x/reference/Image.html
My full code:
import pybullet as p
import pybullet_data
import time
from PIL import Image
from random import *
import numpy as np
nExamples = 200
for n in range(0, nExamples):
print ("Running example " + str(n))
physicsClient = p.connect(p.DIRECT) #or p.GUI for graphical version
p.setAdditionalSearchPath(pybullet_data.getDataPath()) #optionally
p.setGravity(0,0,-10)
planeId = p.loadURDF("ground.urdf")
p.resetDebugVisualizerCamera( cameraDistance=1, cameraYaw=0, cameraPitch=-30, cameraTargetPosition=[0,0,0])
x1 = uniform(-0.03,0.03)
y1 = uniform(-0.03,0.03)
x2 = x1 + uniform(-0.03,0.03)
y2 = y1 + uniform(-0.03,0.03)
cubeStartPos = [0.0,0,0.025]
cubeStartPos1 = [x1,y1,0.075]
cubeStartPos2 = [x2,y2,0.125]
yaw0 = uniform(0,np.pi/2)
yaw1 = uniform(0,np.pi/2)
yaw2 = uniform(0,np.pi/2)
cubeStartOrientation0 = p.getQuaternionFromEuler([0,0,yaw0])
cubeStartOrientation1 = p.getQuaternionFromEuler([0,0,yaw1])
cubeStartOrientation2 = p.getQuaternionFromEuler([0,0,yaw2])
boxId = p.loadURDF("red_block.urdf",cubeStartPos, cubeStartOrientation0)
boxId1 = p.loadURDF("green_block.urdf",cubeStartPos1, cubeStartOrientation1)
boxId2 = p.loadURDF("blue_block.urdf",cubeStartPos2, cubeStartOrientation2)
#saving the initial image...
camera1 = p.getCameraImage(900,600)
imarray = np.asarray(camera1[2])
im1 = Image.fromarray(imarray.astype('uint8'))
for i in range (250):
p.stepSimulation()
time.sleep(1./20.)
camera2 = p.getCameraImage(900,600)
#saving the image after blocks movement --> if stable this image is equal to the initial...
im2 = Image.fromarray(camera2[2])
#Are the images different? (Is it unstable?) --> if yes then diff is large, otherwise, diff is negligible
diff = (camera2[2] - camera1[2]).sum()
print("DIFFERENCE =", diff)
if abs(diff) < 100000:
im1.save("images/stable/image_%d.png" % n)
else:
im1.save("images/unstable/image_%d.png" % n)
#cropping images
cropped = im1.crop((350,200,550,400))
cropped.save("images/cropped/image_%d.png" % n)
p.disconnect()
print ("Reached end of loop\n")
It's not clear from your description whether camera1[2]
is a flat list of consecutive R, G, B, A values, or whether it's a list of RGBA tuples. So I'll show you how to read both options. ;)
Your main problem is that your data doesn't contain width and height information, so we need to supply that info, somehow. One way to do that would be to read the data into a 3D Numpy array of the correct shape. But we can also do it directly in PIL by using the appropriate Image methods.
For my demonstrations I use Python loops to create some simple RGBA data.
This script creates a list of RGBA tuples.
from PIL import Image
maxval = 255
width, height = 400, 300
# Display size info
size = width * height
fmt = 'Width: {}, Height: {}, Pixels: {}, Bytes: {}'
print(fmt.format(width, height, size, size * 4))
# Make a 2D gradient that starts at black in the top left corner,
# with red & green increasing horizontally, blue increasing vertically.
# This would be much faster using Numpy instead of Python loops.
pixels = []
# Make all pixels fully opaque
alpha = maxval
for y in range(height):
blu = maxval * y // height
for x in range(width):
red = gre = maxval * x // width
# Make a single RGBA pixel as a tuple
pix = red, gre, blu, alpha
# And save it
pixels.append(pix)
# Show that the size of `pixels` is correct and show the first few pixels
print('Size:', len(pixels))
print(pixels[:8])
# Make a new image object. All pixels are set to black.
img = Image.new('RGBA', (width, height))
# Copy the pixel data to the Image
img.putdata(pixels)
img.show()
img.save('test1.png')
output
Width: 400, Height: 300, Pixels: 120000, Bytes: 480000
Size: 120000
[(0, 0, 0, 255), (0, 0, 0, 255), (1, 1, 0, 255), (1, 1, 0, 255), (2, 2, 0, 255), (3, 3, 0, 255), (3, 3, 0, 255), (4, 4, 0, 255)]
test1.png
This script creates a flat list of R, G, B, A values. It uses a Python 3 bytes
object, so it won't work properly on Python 2.
from PIL import Image
maxval = 255
width, height = 400, 300
# Display size info
size = width * height
fmt = 'Width: {}, Height: {}, Pixels: {}, Bytes: {}'
print(fmt.format(width, height, size, size * 4))
# Make a 2D gradient that starts at black in the top left corner,
# with red & green increasing horizontally, blue increasing vertically.
# This would be much faster using Numpy instead of Python loops.
rgba = []
# Make all pixels fully opaque
alpha = maxval
for y in range(height):
blu = maxval * y // height
for x in range(width):
red = gre = maxval * x // width
# Make a single RGBA pixel as a tuple
pix = red, gre, blu, alpha
# And save each of red, gre, blu, alpha to rgba.
# By using `.extend` we create a flat list
rgba.extend(pix)
# Show that the size of `rgba` is correct and show the first few values.
print('Size:', len(rgba))
print(rgba[:32])
# Convert the rgba list to bytes.
rgba = bytes(rgba)
# Make a new image object from the bytes
img = Image.frombytes('RGBA', (width, height), rgba)
img.show()
img.save('test2.png')
output
Width: 400, Height: 300, Pixels: 120000, Bytes: 480000
Size: 480000
[0, 0, 0, 255, 0, 0, 0, 255, 1, 1, 0, 255, 1, 1, 0, 255, 2, 2, 0, 255, 3, 3, 0, 255, 3, 3, 0, 255, 4, 4, 0, 255]
The file 'test2.png' is identical to 'test1.png'.