my output my input Hi I am using this python code to generate an shuffle pixel image is there any way to make this process opposite ? for example I give this code output's photo to the program and it reproduce the original photo again.
I am trying to generate an static style image and reverse it back into the original image and I am open into any other ideas for replacing this code
from PIL import Image
import numpy as np
orig = Image.open('lena.jpg')
orig_px = orig.getdata()
orig_px = np.reshape(orig_px, (orig.height * orig.width, 3))
np.random.shuffle(orig_px)
orig_px = np.reshape(orig_px, (orig.height, orig.width, 3))
res = Image.fromarray(orig_px.astype('uint8'))
res.save('out.jpg')
Firstly, bear in mind that JPEG is lossy - so you will never get back what you write with JPEG - it changes your data! So, use PNG if you want to read back losslessly exactly what you started with.
You can do what you ask like this:
#!/usr/bin/env python3
import numpy as np
from PIL import Image
def shuffleImage(im, seed=42):
# Get pixels and put in Numpy array for easy shuffling
pix = np.array(im.getdata())
# Generate an array of shuffled indices
# Seed random number generation to ensure same result
np.random.seed(seed)
indices = np.random.permutation(len(pix))
# Shuffle the pixels and recreate image
shuffled = pix[indices].astype(np.uint8)
return Image.fromarray(shuffled.reshape(im.width,im.height,3))
def unshuffleImage(im, seed=42):
# Get shuffled pixels in Numpy array
shuffled = np.array(im.getdata())
nPix = len(shuffled)
# Generate unshuffler
np.random.seed(seed)
indices = np.random.permutation(nPix)
unshuffler = np.zeros(nPix, np.uint32)
unshuffler[indices] = np.arange(nPix)
unshuffledPix = shuffled[unshuffler].astype(np.uint8)
return Image.fromarray(unshuffledPix.reshape(im.width,im.height,3))
# Load image and ensure RGB, i.e. not palette image
orig = Image.open('lena.png').convert('RGB')
result = shuffleImage(orig)
result.save('shuffled.png')
unshuffled = unshuffleImage(result)
unshuffled.save('unshuffled.png')
Which turns Lena into this:
It's impossible to do that reliably as far as I know. Theoretically you could brute force it by shuffling the pixels over and over and feeding the result into Amazon Rekognition, but you would end up with a huge AWS bill and probably only something that is approximately the original picture.
Related
I've been trying to add reproducible Gaussian Noise by fixing a random seed, saving the image, reading the image and regenerate the Gaussian Noise, and 'subtracting' it to recover the original image. Here's the (pseudo-)code for what I've tried so far:
SEED = 1234
np.random.seed(SEED)
img = cv2.imread(path, -1) # (32,32,3)
noise = np.random.normal(loc=0, scale=20, size=(32,32,3)).astype(np.uint8)
temp = img + noise
# ignore the noise if value exceeds 255 or is below 0
temp = np.where(temp<255, temp, img)
temp = np.where(temp>0, temp, img)
cv2.imwrite(some_path_and_file_name, temp)
Then, I read the image file with Gaussian Noise in the same way. When I 'ignore' the noise, I keep track of the matrix index of when the 'ignoring' happened, and use this information to recover the original data:
img = cv2.imread(path_to_noise_img, -1)
SEED = 1234
np.random.seed(SEED)
noise = np.random.normal(loc=0, scale=20, size=(32,32,3)).astype(np.uint8)
temp = img - noise
# flag is the matrix with the indices of 'ignoring'
recovered_img = np.where(flag == 0, temp, img)
cv2.imwrite(some_path_and_file_name, recovered_img)
However, when I open the two images, they are different. I have checked that the Gaussian Noise is the same all the time, and it feels like something is going wrong (some sort of irreversible conversion is happening) when I read or write the image file.
However, I am having trouble debugging this since I am new to Python.
Any help would be appreciated. Thanks!
Edit
I tried saving the image and loading the image using OpenCV function calls without modifying anything, and compared the values. From what I see, the values being read in are different. What should I fix in my code to prevent this from happening?
Solution
The code below worked like a charm (Thanks to all the comments and the answer).
np.random.seed(SEED)
original = cv2.imread("C:/Users/user/project/1.png", cv2.IMREAD_UNCHANGED)
n = np.random.normal(loc=0, scale=20, size=original.shape).astype(np.int32)
n = original.astype(np.int32) + n.astype(np.int32)
floor = np.zeros_like(n)
ceil = np.zeros_like(n)
floor = np.where(n<255, 0, 1)
t = np.where(n < 255, n, original)
# record when rounding occurs
ceil = np.where(t>0, 0, 1)
arr = np.where(t>0, t, original)
flag = floor + ceil
arr = arr.astype(np.uint8)
cv2.imwrite("C:/Users/user/project/1-1.png", arr.astype(np.uint8))
np.random.seed(SEED)
recover = cv2.imread("C:/Users/user/project/1-1.png", cv2.IMREAD_UNCHANGED).astype(np.int32)
noise = np.random.normal(loc=0, scale=20, size=recover.shape).astype(np.int32)
ans = np.where(flag==0, recover-noise, recover)
assert(ans.astype(np.uint8) == original.astype(np.uint8)).all()
Multiple issues...
Compression
JPEG is a lossy compression, which means you will surely not be getting the exact same values back as you had before compression.
PNG is lossless, so it will give you the exact values you had before compression.
Integer math
Adding two uint8 values results in an uint8 again.
That means your math will always result in values in the range of 0..255. uint8 values can never be <0 or >255.
Your np.where checks are useless because the values are uint8, and even after addition they stay uint8, and they can never be <0 or >255.
Further, whenever you add/subtract values, if the result exceeds the range, that has to be handled in some way. Numpy simply wraps the values around, as is usual with integer math. Another option is to saturate, meaning to clip. OpenCV functions tend to do that. You can produce either with either library, with some care.
If there is any saturating math in your code, you will definitely not be able to subtract the noise and recover the original image. If there is merely wrapping math in your code, you can recover the original image by subtracting the noise.
Debugging
You are discarding so much information, reducing the answer to "are they bit-exact equal or not?"
You should use a small image, 3x3 pixels or something, and then look at the values, when you print those numpy arrays.
Demo
import numpy as np
import cv2 as cv
img = cv.imread("image.png", cv.IMREAD_UNCHANGED)
SEED = 1234
np.random.seed(SEED)
noise = np.random.normal(loc=0, scale=20, size=img.shape).astype(np.uint8)
img_with_noise = img + noise # this will wrap around
cv.imwrite("image_with_noise.png", img_with_noise)
import numpy as np
import cv2 as cv
img = cv.imread("image.png", cv.IMREAD_UNCHANGED)
img_with_noise = cv.imread("image_with_noise.png", cv.IMREAD_UNCHANGED)
# generate noise the exact same way
SEED = 1234
np.random.seed(SEED)
noise = np.random.normal(loc=0, scale=20, size=img.shape).astype(np.uint8)
img_recovered = img_with_noise - noise # wrapping around again, backwards
cv.imwrite("image_recovered.png", img_recovered)
# images should be equal in all values of all pixels
assert (img_recovered == img).all()
I have written some code to read the RGB values for each pixel of ~150 images (1000px by 720px, cropped and sized).
import os
from PIL import Image
print("STACKING IMAGES...")
os.chdir('cropped')
images=os.listdir() #list all images present in directory
print("GETTING IMAGES...")
channelR=[]
channelG=[]
channelB=[]
print("GETTING PIXEL INFORMATION...") #runs reasonably fast
for image in images: #loop through each image to extract RGB channels as separate lists
with Image.open(image) as img:
if image==images[0]:
imgSize=img.size
channelR.append(list(img.getdata(0)))
channelG.append(list(img.getdata(1)))
channelB.append(list(img.getdata(2)))
print("PIXEL INFORMATIION COLLECTED.")
print("AVERAGING IN CHANNEL RED.") #average for each pixel in each channel
avgR=[round(sum(x)/len(channelR)) for x in zip(*channelR)] #unzip the each pixel from all ~250 images, average it, store in tuple, starts to slow
print("AVERAGING IN CHANNEL GREEN.")
avgG=[round(sum(x)/len(channelG)) for x in zip(*channelG)] #slower
print("AVERAGING IN CHANNEL BLUE.")
avgB=[round(sum(x)/len(channelB)) for x in zip(*channelB)] #progressively slower
print("MERGING DATA ACROSS THREE CHANNELS.")
mergedData=[(x) for x in zip(avgR, avgG, avgB)] #merge averaged colour channels pixel by pixel, doesn't seem to end, takes eternity
print("GENERATING IMAGE.")
stacked=Image.new('RGB', (imgSize)) #create image
stacked.putdata(mergedData) #generate image
stacked.show()
os.chdir('..')
stacked.save('stacked.tif', 'TIFF') #save file
print("FINISHED STACKING !")
Running it on my modestly equipped computer (Core2Duo, 4GB RAM, Linux Mint OS) took close to an hour for the averaging across the three channels to complete and a further one hour to merge the individual averaged pixels (did not complete, and I aborted the process). I have read that list comprehensions are slow and zip() function takes up too much memory, but tinkering with those resulted in further bugs. I have even read that partitioning the program into functions might speed it up.
For comparable performances, I would kindly request the person answering the question to run the code on the images from https://github.com/rlvaugh/Impractical_Python_Projects/tree/master/Chapter_15/video_frames.
Any help on speeding-up the program would be gratefully accepted. Does it hold any chance of improving its speed drastically on shifting to more powerful systems?
Thank you in advance for any help.
Appending to lists is slow. As is having multiple list comprehensions for something you could do in a single loop. You could also use numpy arrays to speed it up using SIMD operations instead of iterating over list.
Here's some sample code for a few images. You can extend it as per your requirements.
import os
import numpy as np
import PIL
os.chdir('cropped')
imgfiles = ['MVI_6450 001.jpg', 'MVI_6450 002.jpg', 'MVI_6450 003.jpg', 'MVI_6450 004.jpg']
allimgs = None
for imgnum, imgfile in enumerate(imgfiles):
img = PIL.Image.open(imgfile)
imgdata = np.array(img.getdata()) # Nx3 array. columns: R, G, B channels
if allimgs is None:
allshape = list(imgdata.shape) # Size of one image
allshape.append(len(imgfiles)) # Append number of images
# allshape is now [num_pixels, num_channels, num_images]
# so making an array of this shape will allow us to store all images
# Axis 0: pixels. Axis 1: channels. Axis 2: images
allimgs = np.zeros(allshape)
allimgs[:, :, imgnum] = imgdata # Set the imgnum'th image data
# Get the mean along the last axis
# average same pixel across all images for each channel
imgavg = np.mean(allimgs, axis=-1)
# normalize so that max value is 255
# Also convert to uint8
imgavg = np.uint8(imgavg / np.max(imgavg) * 255)
imgavg_tuple = tuple(map(tuple, imgavg))
stacked = PIL.Image.new("RGB", img.size)
stacked.putdata(imgavg_tuple)
stacked.show()
os.chdir('..')
Note: We create a numpy array to hold all images at the start instead of appending as we load more images because it's a bad, bad idea to append to numpy arrays as Jacob mentions in a comment below. This is because numpy array append actually creates a new array and then copies the contents of both arrays, so it's an O(n^2) operation.
I create an image and fill the pixels:
img = Image.new( 'RGB', (2000,2000), "black") # create a new black image
pixels = img.load() # create the pixel map
for i in range(img.size[0]): # for every pixel:
for j in range(img.size[1]):
#do some stuff that requires i and j as parameter
Can this be done more elegant (and may be faster, since theoretically the loops are parallelizable)?
Note: I will first answer the question, then propose an, in my opinion, better alternative
Answering the question
It is hard to give advice without knowing what changes you intend to apply and whether the loading of the image as a PIL image is part of the question or a given.
More elegant in Python-speak typically means using list comprehensions
For parallelization, you would look at something like the multiprocessing module or joblib
Depending on your method of creating / loading in images, the list_of_pixels = list(img.getdata()) and img.putdata(new_list_of_pixels) functions may be of interest to you.
An example of what this might look like:
from PIL import Image
from multiprocessing import Pool
img = Image.new( 'RGB', (2000,2000), "black")
# a function that fixes the green component of a pixel to the value 50
def update_pixel(p):
return (p[0], 50, p[2])
list_of_pixels = list(img.getdata())
pool = Pool(4)
new_list_of_pixels = pool.map(update_pixel, list_of_pixels)
pool.close()
pool.join()
img.putdata(new_list_of_pixels)
However, I don't think that is a good idea... When you see loops (and list comprehensions) over thousands of elements in Python and you have performance on your mind, you can be sure there is a library that will make this faster.
Better Alternative
First, a quick pointer to the Channel Operations module,
Since you don't specify the kind of pixel operation you intend to do and you clearly already know about the PIL library, I'll assume you're aware of it and it doesn't do what you want.
Then, any moderately complex matrix manipulation in Python will benefit from pulling in Pandas, Numpy or Scipy...
Pure numpy example:
import numpy as np
import matplotlib.pyplot as plt
#black image
img = np.zeros([100,100,3],dtype=np.uint8)
#show
plt.imshow(img)
#make it green
img[:,:, 1] = 50
#show
plt.imshow(img)
Since you are just working with a standard numpy.ndarray, you can use any of the available functionalities, such as np.vectorize, apply, map etc. To show a similar solution as above with the update_pixel function:
import numpy as np
import matplotlib.pyplot as plt
#black image
img = np.zeros([100,100,3],dtype=np.uint8)
#show
plt.imshow(img)
#make it green
def update_pixel(p):
return (p[0], 50, p[2])
green_img = np.apply_along_axis(update_pixel, 2, img)
#show
plt.imshow(green_img)
One more example, this time calculating the image content directly from the indexes, instead of from existing image pixel content (no need to create an empty image first):
import numpy as np
import matplotlib.pyplot as plt
def calc_pixel(x,y):
return np.array([100-x, x+y, 100-y])
img = np.frompyfunc(calc_pixel, 2, 1).outer(np.arange(100), np.arange(100))
plt.imshow(np.array(img.tolist()))
#note: I don't know any other way to convert a 2D array of arrays to a 3D array...
And, low and behold, scipy has methods to read and write images and inbetween, you can just use numpy to manipulate them as "classic" mult-dimensional arrays. (scipy.misc.imread depends on PIL, by the way)
More example code.
I have this in python:
import Image
import numpy as np
import random
img = Image.open('img.jpg')
#turn img to list of rgb tuples and scramble
pixels = list(img.getdata())
pixels.reverse()
random.shuffle(pixels)
#make new image using scrambled pixels
img2 = Image.new(img.mode, img.size)
img2.putdata(pixels)
img2.save('newimg.png')
I figured I should be working in c++ to keep stuff I learned last semester fresh in my head and to prepare for the class I have next semester which also revolves around c++. So, I found CImg and got a bit overwhelmed by the documentation. So, what would be CImg's equivalent of line 8?
My end goal is to be able to scramble an image using a known pattern, then use that pattern to unscramble later. I don't know if this is possible though. To me its a bit like asking the following:
given:
srand(x);
int rand_num = rand() % 10;
and
rand_num = 7
find x.
As far as know CImg provides iterators to loop through every pixel. As such and provided that your compiler support C++11, you could use std::shuffle to shuffle the pixels of your image (see example below).
CImg<float> img("lena.jpg"); // Load image from file.
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::shuffle(img.begin(), img.end(), std::default_random_engine(seed));
Here's the scenario, I want to create a set of random, small jpg's - anywhere between 50 bytes and 8k in size - the actual visual content of the jpeg is irrelevant as long as they're valid. I need to generate a thousand or so, and they all have to be unique - even if they're only different by a single pixel. Can I just write a jpeg header/footer and some random bytes in there? I'm not able to use existing photos or sets of photos from the web.
The second issue is that the set of images has to be different for each run of the program.
I'd prefer to do this in python, as the wrapping scripts are in Python.
I've looked for python code to generate jpg's from scratch, and didn't find anything, so pointers to libraries are just as good.
If the images can be only random noise, so you could generate an array using numpy.random and save them using PIL's Image.save.
This example might be expanded, including ways to avoid a (very unlikely) repetition of patterns:
import numpy
from PIL import Image
for n in range(10):
a = numpy.random.rand(30,30,3) * 255
im_out = Image.fromarray(a.astype('uint8')).convert('RGB')
im_out.save('out%000d.jpg' % n)
These conditions must be met in order to get jpeg images:
The array needs to be shaped (m, n, 3) - three colors, R G and B;
Each element (each color of each pixel) has to be a byte integer (uint, or unsigned integer with 8 bits), ranging from 0 to 255.
Additionaly, some other way besides pure randomness might be used in order to generate the images in case you don't want pure noise.
If you do not care about the content of a file, you can create valid JPEG using Pillow (PIL.Image.new [0]) this way:
from PIL import Image
width = height = 128
valid_solid_color_jpeg = Image.new(mode='RGB', size=(width, height), color='red')
valid_solid_color_jpeg.save('red_image.jpg')
[0] https://pillow.readthedocs.io/en/latest/reference/Image.html#PIL.Image.new
// EDIT: I thought OP wants to generate valid images and does not care about their content (that's why I suggested solid-color images). Here's a function that generates valid images with random pixels and as a bonus writes random string to the generated image. The only dependency is Pillow, everything else is pure Python.
import random
import uuid
from PIL import Image, ImageDraw
def generate_random_image(width=128, height=128):
rand_pixels = [random.randint(0, 255) for _ in range(width * height * 3)]
rand_pixels_as_bytes = bytes(rand_pixels)
text_and_filename = str(uuid.uuid4())
random_image = Image.frombytes('RGB', (width, height), rand_pixels_as_bytes)
draw_image = ImageDraw.Draw(random_image)
draw_image.text(xy=(0, 0), text=text_and_filename, fill=(255, 255, 255))
random_image.save("{file_name}.jpg".format(file_name=text_and_filename))
# Generate 42 random images:
for _ in range(42):
generate_random_image()
If you are looking for a way to do this without numpy this worked for me
(python 3.6 for bytes, you still need Pillow)
import random as r
from PIL import Image
dat = bytes([r.randint(1,3) for x in range(4500000)])
i = Image.frombytes('1', (200,200), dat)