I have a script that's supposed to open a png image and then resize it and then save it as an jpg in numerical sequence. But the code for the number sequencing I copied from the internet isn't working with PIL. It gives me the exception "KeyError: 'W'"
import os
from PIL import Image
os.chdir('C:\\Users\\paul\\Downloads')
# open canvas.png
original = Image.open('canvas.png')
# resize image height to 2160
size = (3000, 2160)
original.thumbnail(size)
# convert to RGB
RGB = original.convert('RGB')
# save image as sequence
i = 0
while os.path.exists("image%s.jpg" % i):
i += 1
RGB.save("image%s.jpg" % i, "w")
Is there another way to do this?
Edit based on Haken Lid's comment
The PIL documentation says that the function save accepts these argument:
Image.save(fp, format=None, **params)
The parameter w you passed is not within the set of accepted file format.
Here you can see which formats are accepted. To make it works, just drop the w argument and substitute the %s with %d (i is an integer, not a string):
RGB.save("image%d.jpg" % i)
Note: from your tags it is not clear if you're using python2 or python3. If you are using python 3, I suggest to use the new method to format string:
RGB.save("image{}.jpg".format(i))
You can even specify a padding so that you can sort your file by name later on:
RGB.save("image{:04d}.jpg".format(i))
where 4 means that your number will be padded with zeros as to have length of at least 4.
Related
I was working on a project that requires me to add csv file in two places of the code. I have seen kinda similar problem here at stackoverflow. But their problem was due to old python version 2.5. But my python version is 3.8.
import csv
from tensorflow.keras.datasets import mnist
import numpy as np
def load_az_dataset("C:\A_Z_Handwritten_Data\A_Z_Handwritten_Data.csv"):
# initialize the list of data and labels
data = []
labels = []
# loop over the rows of the A-Z handwritten digit dataset
for row in open("C:\A_Z_Handwritten_Data\A_Z_Handwritten_Data.csv"):
# parse the label and image from the row
row = row.split(",")
label = int(row[0])
image = np.array([int(x) for x in row[1:]], dtype="uint8")
# images are represented as single channel (grayscale) images
# that are 28x28=784 pixels -- we need to take this flattened
# 784-d list of numbers and repshape them into a 28x28 matrix
image = image.reshape((28, 28))
# update the list of data and labels
data.append(image)
labels.append(label)
# convert the data and labels to NumPy arrays
data = np.array(data, dtype="float32")
labels = np.array(labels, dtype="int")
# return a 2-tuple of the A-Z data and labels
return (data, labels)
It's showing this syntax error
The syntax error is caused by the fact that the file path is in the parameter list in the function definition. This is the culprit:
def load_az_dataset("C:\A_Z_Handwritten_Data\A_Z_Handwritten_Data.csv"):
You have no parameters listed in the function definition. You just have a literal string.
Furthermore, you should also either be using raw strings: r"..." or escaping your backslashes, as others have mentioned.
Finally, you should be using the with open(file_path) as f: pattern to open your file.
The syntax error is caused since you are passing the literal string in the method declaration of load_az_dataset.
You need to define the parameter to the function as:
def load_az_dataset(fileName):
Further, if you want to add that file as the default value for the parameter then use:
def load_az_dataset(fileName="C:\\A_Z_Handwritten_Data\\A_Z_Handwritten_Data.csv"):
Also, unrelated to the problem, you need to escape the \ with another \.
Try:
open("C:\\A_Z_Handwritten_Data\\A_Z_Handwritten_Data.csv")
This question already has answers here:
Is there a built in function for string natural sort?
(23 answers)
Closed 3 years ago.
I need to create a video out of sequence of images in python. I found this code online that works fine but I am having a small problem with the ordering of the images when being read in python. Even though the ordering in the folder is ok.
E.x frame100.jpg , frame101.jpg , frame102.jpg,....., frame1000, frame1001, ....
when I read them with python inside the loop , after debuging I see the following
'frame100.jpg', 'frame1000.jpg', 'frame1001.jpg', 'frame1002.jpg',.....,frame101,frame1010 , frame1011....
This is the code
def images_to_video():
image_folder = 'data_out'
images = [img for img in os.listdir(image_folder) if img.endswith(".jpg")]
frame = cv2.imread(os.path.join(image_folder, images[0]))
height, width, layers = frame.shape
video = cv2.VideoWriter('project.avi',cv2.VideoWriter_fourcc(*'DIVX'), 15, (width,height))
for image in images:
video.write(cv2.imread(os.path.join(image_folder, image)))
cv2.destroyAllWindows()
video.release()
You need to sort the filenames using the natural sorting that knows how to sort the numbers:
import re
def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
return [
int(text)
if text.isdigit() else text.lower()
for text in _nsre.split(s)]
sorted_images = sorted(images, key=natural_sort_key)
First of all, files in the folder are not "ordered" in any way on the OS level (at least in the most popular operating systems I am aware of). If you see them "ordered" in some file browser, it is purely done by the file browser app itself (finder/file explorer/etc) but python does not use it.
Second, if you read the documentation of os.listdir it is clearly specified that files are returned in an arbitrary order. You should order them yourself.
Third, your files are named without zero padding. This means that if you sort the file names in alphabetical order (that is the default for strings in python), you will get the order you specified above (frame101.jpg, frame1010.jpg). If they were zero padded, you could get the right order even if sorted as strings (frame0101.jpg, frame0102.jpg, ..., frame1010.jpg).
The solution is the extract the frame number from the file names, convert them to integer, and sort the file names based on this number. The simplest way to achieve this is:
sorted_images = sorted(images, key=lambda x:int(x[5:-4]))
where 5 is the length of the prefix frame and -4 is the length of .jpg, the prefix and suffix I wish to cut off from the filename. Check out python slicing and documentation of the key parameter of sorted for more info.
You're not explicitly ordering the frames, but are relying on os.listdir to order it for you. This may cause a problem because there is no guarantee the list will be in alphabetical order.
Reference: os.listdir() Documentation
Return a list containing the names of the entries in the directory given by path. The list is in arbitrary order
Solution 1
Explicitly order your list before processing, e.g.
images = images.sort()
or
for image in images.sort():
However, to do this you have to modify the names of your file first, or it would sort like 100 > 1001 > 1002 ... 101 > 1010 like you described. To do that, you can e.g. add a zero before those filenames fewer than 1000:
images = [img if len(img)==13 else img[:7] + "0" + img[-7:] for img in images]
Solution 2
If you're sure ALL the files are in the format of "frame + number + .jpg" then you can do this:
images = ["frame" + str(i) + ".jpg" for i in range(100, 1234)] ## replace 1234 with (last frame number + 1)
Folks,
I am using this link as starting point to convert my CompressedDepth (image of type: "32FC1; compressedDepth," in meters) image to OpenCV frames:
Python CompressedImage Subscriber Publisher
I get an empty data when I try to print, or I get a NonType when I see the result of my array, etc.
What is the right way to convert a compressedDepth image?
Republishing is not gonna work do to wifi/router bandwidth and speed constraints.
The right way to decode compressedDepth is to first remove the header from the raw data and then convert the remaining data.
This is documented in image_transport_plugins/compressed_depth_image_transport/src/codec.cpp.
On my machine the header size is 12 bytes. This might however be different on other architectures since the size of an enum is not defined.
The following python code snippet exports compressed 16UC1 and 32FC1 depth images as png file:
# 'msg' as type CompressedImage
depth_fmt, compr_type = msg.format.split(';')
# remove white space
depth_fmt = depth_fmt.strip()
compr_type = compr_type.strip()
if compr_type != "compressedDepth":
raise Exception("Compression type is not 'compressedDepth'."
"You probably subscribed to the wrong topic.")
# remove header from raw data
depth_header_size = 12
raw_data = msg.data[depth_header_size:]
depth_img_raw = cv2.imdecode(np.fromstring(raw_data, np.uint8), cv2.CV_LOAD_IMAGE_UNCHANGED)
if depth_img_raw is None:
# probably wrong header size
raise Exception("Could not decode compressed depth image."
"You may need to change 'depth_header_size'!")
if depth_fmt == "16UC1":
# write raw image data
cv2.imwrite(os.path.join(path_depth, "depth_" + str(msg.header.stamp) + ".png"), depth_img_raw)
elif depth_fmt == "32FC1":
raw_header = msg.data[:depth_header_size]
# header: int, float, float
[compfmt, depthQuantA, depthQuantB] = struct.unpack('iff', raw_header)
depth_img_scaled = depthQuantA / (depth_img_raw.astype(np.float32)-depthQuantB)
# filter max values
depth_img_scaled[depth_img_raw==0] = 0
# depth_img_scaled provides distance in meters as f32
# for storing it as png, we need to convert it to 16UC1 again (depth in mm)
depth_img_mm = (depth_img_scaled*1000).astype(np.uint16)
cv2.imwrite(os.path.join(path_depth, "depth_" + str(msg.header.stamp) + ".png"), depth_img_mm)
else:
raise Exception("Decoding of '" + depth_fmt + "' is not implemented!")
I want to extract multiple parts of an image with Wand.
I've just found a function for cropping (in-place) the image img.crop(left, top, right, bottom) but note the slicing one as they say in the doc.
Note
If you want to crop the image but not in-place, use slicing
operator.
Take a look at the test_slice_crop method in the test directory for examples.
with Image(filename='source.jpg') as img:
with img[100:200, 100:200] as cropped:
# The `cropped' is an instance if wand.image.Image,
# and can be manipulated independently of `img' instance.
pass
Edit
For completion, slice is a built-in function in python to represent a set of iterations (i.e. a[start:stop:step]). In wand, this is used to allow short-hand matrix iterations
wand_instance[x:width, y:height]
Here's an example of generating 10px columns...
from wand.image import Image
with Image(filename="rose:") as rose:
x = 0
chunk_size = 10
while True:
try:
with rose[x:x+chunk_size, 0:rose.height] as chunk:
chunk.save(filename='rose_{0}.png'.format(x))
x += chunk_size
except IndexError:
break
I'm looking for a fast way to apply a new palette to an existing 8-bit .png image. How can I do that? Is the .png re-encoded when I save the image? (Own answer: it seems so)
What I have tried (edited):
import Image, ImagePalette
output = StringIO.StringIO()
palette = (.....) #long palette of 768 items
im = Image.open('test_palette.png') #8 bit image
im.putpalette(palette)
im.save(output, format='PNG')
With my testimage the save function takes about 65 millis. My thought: without the decoding and encoding, it can be a lot faster??
If you want to change just the palette, then PIL will just get in your way. Luckily, the PNG file format was designed to be easy to deal with when you only are interested in some of the data chunks. The format of the PLTE chunk is just an array of RGB triples, with a CRC at the end. To change the palette on a file in-place without reading or writing the whole file:
import struct
from zlib import crc32
import os
# PNG file format signature
pngsig = '\x89PNG\r\n\x1a\n'
def swap_palette(filename):
# open in read+write mode
with open(filename, 'r+b') as f:
f.seek(0)
# verify that we have a PNG file
if f.read(len(pngsig)) != pngsig:
raise RuntimeError('not a png file!')
while True:
chunkstr = f.read(8)
if len(chunkstr) != 8:
# end of file
break
# decode the chunk header
length, chtype = struct.unpack('>L4s', chunkstr)
# we only care about palette chunks
if chtype == 'PLTE':
curpos = f.tell()
paldata = f.read(length)
# change the 3rd palette entry to cyan
paldata = paldata[:6] + '\x00\xff\xde' + paldata[9:]
# go back and write the modified palette in-place
f.seek(curpos)
f.write(paldata)
f.write(struct.pack('>L', crc32(chtype+paldata)&0xffffffff))
else:
# skip over non-palette chunks
f.seek(length+4, os.SEEK_CUR)
if __name__ == '__main__':
import shutil
shutil.copyfile('redghost.png', 'blueghost.png')
swap_palette('blueghost.png')
This code copies redghost.png over to blueghost.png and modifies the palette of blueghost.png in-place.
->
im.palette is not callable -- it's an instance of the ImagePalette class, in mode P, otherwise None. im.putpalette(...) is a method, so callable: the argument must be a sequence of 768 integers giving R, G and B value at each index.
Changing palette's without decoding and (re)encoding does not seem possible. The method in the question seems best (for now). If performance is important, encoding to GIF seems a lot faster.