python: PIL.Image.fromarray() is totally bugging out - python

I'm trying to solve this CTF challenge Image Magic:
https://ctflearn.com/challenge/89
My code:
from PIL import Image
import numpy as np
out=np.asarray(Image.open("out copy.jpg"))
original_pixels=np.zeros([92,304], dtype=tuple)
newcol=-1
for out_col in range(0,27968):
newrow=out_col%92
if out_col%92==0:
newcol+=1
original_pixels[newrow,newcol]=out[0,out_col]
original=Image.fromarray(original_pixels,"RGB")
original.save("original.png")
The logic and everything should be correct, as it matches with a solution I found on the Internet, but for some reason, my code is producing gibberish:
The solution I found ( https://medium.com/nerd-for-tech/write-up-ctflearn-image-magic-2caf5d651913 ):
from PIL import Image
img = Image.open('out copy.jpg')
new_img = Image.new('RGB', (400, 400))
n_x = n_y = 0
for i in range(0, img.size[0]):
r, g, b = img.getpixel((i, 0))
n_x = (i % 92)
if i % 92 == 0:
n_y = n_y + 1
new_img.putpixel((n_x, n_y), (r, g, b))
new_img.save('answer.jpg')
Which produces the correct image:
As you can see, the logic is basically the same, the only difference is that I used np arrays and the Image.fromarray method to shift the pixels and create the original image. The solution just created a blank image and used the putpixel method to fill the pixels in.
I think the problem is with the fromarray function, but I can't figure out why/how, and existing questions/answers are not helping. This seems like a simple solve, but I'm not really familiar with these stuff. Any suggestions?

The main reason this does not work, is that you are creating your original_pixels array as an array of Python objects. I’m frankly not sure what exactly this does in the end, but it is needlessly complicated. If you create your array as a (width, height, 3) array of bytes instead, it works:
original_pixels=np.zeros([92, 304, 3], dtype=np.uint8)
Now, as an "exercise for the reader": Have a look at the reshape method of numpy’s ndarray objects. Could that be put to good use here?

Related

skeletonization (thinning) of small images not giving expected results - python

I am trying to implement a skeletonization of small images. But I am not getting an expected results. I tried also thin() and medial_axis() but nothing seems to work as expected. I am suspicious that this problem occurs because of the small resolutions of images. Here is the code:
import cv2
from numpy import asarray
import numpy as np
# open image
file = "66.png"
img_grey = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
afterMedian = cv2.medianBlur(img_grey, 3)
thresh = 140
# threshold the image
img_binary = cv2.threshold(afterMedian, thresh, 255, cv2.THRESH_BINARY)[1]
# make binary image
arr = asarray(img_binary)
binaryArr = np.zeros(asarray(img_binary).shape)
for i in range(0, arr.shape[0]):
for j in range(0, arr.shape[1]):
if arr[i][j] == 255:
binaryArr[i][j] = 1
else:
binaryArr[i][j] = 0
# perform skeletonization
from skimage.morphology import skeletonize
cv2.imshow("binary arr", binaryArr)
backgroundSkeleton = skeletonize(binaryArr)
# convert to non-binary image
bSkeleton = np.zeros(arr.shape)
for i in range(0, arr.shape[0]):
for j in range(0, arr.shape[1]):
if backgroundSkeleton[i][j] == 0:
bSkeleton[i][j] = 0
else:
bSkeleton[i][j] = 255
cv2.imshow("background skeleton", bSkeleton)
cv2.waitKey(0)
The results are:
I would expect something more like this:
This applies to similar shapes also:
Expectation:
Am I doing something wrong? Or it will truly will not be possible with such small pictures, because I tried skeletonization on bigger images and it worked just fine. Original images:
You could try the skeleton in DIPlib (dip.EuclideanSkeleton):
import numpy as np
import diplib as dip
import cv2
file = "66.png"
img_grey = cv2.imread(file, cv2.IMREAD_GRAYSCALE)
afterMedian = cv2.medianBlur(img_grey, 3)
thresh = 140
bin = afterMedian > thresh
sk = dip.EuclideanSkeleton(bin, endPixelCondition='three neighbors')
dip.viewer.Show(bin)
dip.viewer.Show(sk)
dip.viewer.Spin()
The endPixelCondition input argument can be used to adjust how many branches are preserved or removed. 'three neighbors' is the option that produces the most branches.
The code above produces branches also towards the corners of the image. Using 'two neighbors' prevents that, but produces fewer branches towards the object as well. The other way to prevent it is to set edgeCondition='object', but in this case the ring around the object becomes a square on the image boundary.
To convert the DIPlib image sk back to a NumPy array, do
sk = np.array(sk)
sk is now a Boolean NumPy array (values True and False). To create an array compatible with OpenCV simply cast to np.uint8 and multiply by 255:
sk = np.array(sk, dtype=np.uint8)
sk *= 255
Note that, when dealing with NumPy arrays, you generally don't need to loop over all pixels. In fact, it's worth trying to avoid doing so, as loops in Python are extremely slow.
It seems the scikit-image is much better choice than cv2 here.
since the package define Bit functions, if you are playing with BW images, then try this ready to use code:
skeletonize
note: if process pass the image details, then don’t upsample the input at first until you tried other functions:again use skimage morphology functions to enhance details which in such case your code will be work on bigger area of images too. You could look here.

Trying to make naive numpy image processing code faster

I'm trying to transform an image containing colored symbols into pixel art as featured on the right (see image below), where each colored symbol (taking up multiple pixels) would be changed into one pixel of the symbol's color.
Example of what I'm trying to achieve
So far I've written a pretty naive algorithm that just loops through all the pixels, and is pretty sluggish. I believe I could make it faster, for instance using native numpy operations, but I've been unable to find how. Any tips?
(I also started by trying to simply resize the image, but couldn't find a resampling algorithm that would make it work).
def resize(img, new_width):
width, height = img.shape[:2]
new_height = height*new_width//width
new_image = np.zeros((new_width, new_height,4), dtype=np.uint8)
x_ratio, y_ratio = width//new_width, height//new_height
for i in range(new_height):
for j in range(new_width):
sub_image = img[i*y_ratio:(i+1)*y_ratio, j*x_ratio:(j+1)*x_ratio]
found = False
for row in sub_image:
for pixel in row:
if any(pixel!=[0,0,0,0]):
new_image[i,j]=pixel
break
if found:
break
return new_image
A larger example
import cv2
import numpy as np
img=cv2.imread('zjZA8.png')
h,w,c=img.shape
new_img=np.zeros((h//7,w//7,c), dtype='uint8')
for k in range(c):
for i in range(h//7):
for j in range(w//7):
new_img[i,j,k]=np.max(img[7*i:7*i+7,7*j:7*j+7,k])
cv2.imwrite('out3.png', new_img)
Left is result with np.mean, center - source image, right - result with np.max
Please test this code:
img=cv2.imread('zjZA8.png')
h,w,c=img.shape
bgr=[0,0,0]
bgr[0], bgr[1],bgr[2] =cv2.split(img)
for k in range(3):
bgr[k].shape=(h*w//7, 7)
bgr[k]=np.mean(bgr[k], axis=1)
bgr[k].shape=(h//7, 7, w//7)
bgr[k]=np.mean(bgr[k], axis=1)
bgr[k].shape=(h//7,w//7)
bgr[k]=np.uint8(bgr[k])
out=cv2.merge((bgr[0], bgr[1],bgr[2]))
cv2.imshow('mean_image', out)
Modifying my code to use the native np.nonzero operation did the trick!
I went down from ~8s to ~0.32s on a 1645x1645 image (with new_width=235).
I also tried using numba on top of that, but the overhead ends up making it slower.
def resize(img, new_width):
height, width = img.shape[:2]
new_height = height*new_width//width
new_image = np.ones((new_height, new_width,3), dtype=np.uint8)
x_ratio, y_ratio = width//new_width, height//new_height
for i in range(new_height):
for j in range(new_width):
sub_image = img[i*y_ratio:(i+1)*y_ratio, j*x_ratio:(j+1)*x_ratio]
non_zero = np.nonzero(sub_image)
if non_zero[0].size>0:
new_image[i, j]=sub_image[non_zero[0][0],non_zero[1][0]][:3]
return new_image

PIL - apply the same operation to every pixel

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.

Finding small image inside large (PIL + OpenCV)

I'm trying to do as described here: Finding a subimage inside a Numpy image to be able to search an image inside screenshot.
The code looks like that:
import cv2
import numpy as np
import gtk.gdk
from PIL import Image
def make_screenshot():
w = gtk.gdk.get_default_root_window()
sz = w.get_size()
pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, sz[0], sz[1])
pb = pb.get_from_drawable(w, w.get_colormap(), 0, 0, 0, 0, sz[0], sz[1])
width, height = pb.get_width(), pb.get_height()
return Image.fromstring("RGB", (width, height), pb.get_pixels())
if __name__ == "__main__":
img = make_screenshot()
cv_im = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
template = cv_im[30:40, 30:40, :]
result = cv2.matchTemplate(cv_im, template, cv2.TM_CCORR_NORMED)
print np.unravel_index(result.argmax(), result.shape)
Depending on method selected (instead of cv2.TM_CCORR_NORMED) I'm getting completely different coordinates, but none of them is (30, 30) as in example.
Please, teach me, what's wrong with such approach?
Short answer: you need to use the following line to locate the corner of the best match:
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result)
The variable maxLoc will hold a tuple containing the x, y indices of the upper lefthand corner of the best match.
Long answer:
cv2.matchTemplate() returns a single channel image where the number at each index corresponds to how well the input image matched the template at that index. Try visualizing result by inserting the following lines of code after your call to matchTemplate, and you will see why numpy would have a difficult time making sense of it.
cv2.imshow("Debugging Window", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
minMaxLoc() turns the result returned by matchTemplate into the information you want. If you cared to know where the template had the worst match, or what value was held by result at the best and worst matches, you could use those values too.
This code worked for me on an example image that I read from file. If your code continues to misbehave, you probably aren't reading in your images the way you want to. The above snippet of code is useful for debugging with OpenCV. Replace the argument result in imshow with the name of any image object (numpy array) to visually confirm that you are getting the image you want.

Using putpixel() doesn't write pixel values to image

I am using putpixel on an image (srcImage) which is w = 134 and h = 454.
The code here gets the r,g,b value of a part of the font which is 0,255,0 (which I found through debugging, using print option).
image = letters['H']
r,g,b = image.getpixel((1,1)) #Note r g b values are 0, 255,0
srcImage.putpixel((10,15),(r,g,b))
srcImage.save('lolmini2.jpg')
This code does not throw any error. However, when I check the saved image I cannot spot the pure green pixel.
Instead of using putpixel() and getpixel() you should use indexing instead. For getpixel() you can use pixesl[1, 1] and for putpixel you can use pixels[1, 1] = (r, g, b). It should work the same but it's much faster. pixels here is image.load()
However, I don't see why it wouldn't work. It should work without a problem. Perhaps the jpeg compression is killing you here. Have you tried saving it as a png/gif file instead? Or setting more than 1 pixel.
I know it is a very old post but, for beginners who'd want to stick to putpixels() for a while, here's the solution:
initialize the image variable as:
from PIL import Image
img = Image.new('RGB', [200,200], 0x000000)
Make sure to initialize it as 'RGB' if you want to manipulate RGB values.
Sometimes people initialize images as:
img = Image.new('I', [200, 200], 0x000000)
and then try to work with RGB values, which doesn't work.

Categories

Resources