Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing random pixels in a picture, python

I want to write a function that will create a random number (between m and n, inclusive) of stars in the sky of this picture (http://tinypic.com/r/34il9hu/6). I want the stars should be randomly composed of either a single white pixel, or a square of 4 adjacent white pixels.I also do not want to place a 'star' (of 1 pixel) over the tree branches, the moon or the bird though

How would I do this in python? Can anyone help? Thanks!

I have this so far:

I have started and have come out with this so far, I don't know if any of it is right or even if I am on the right track though:

def randomStars(small, large):
   import random
   file = pickAFile()
   pic = makePicture(myPic)
   #x = random.randrange(getWidth(pic))
   #y = random.randrange(getHeight(pic))
   for pixel in pic.getAllPixels():
      if random.random() < 0.25:
         pixel.red = random.randint(256)
         pixel.green = random.randint(256)
         pixel.blue = random.randint(256) 
   show(pic)

I have no clue what I am doing :(

like image 687
Oliver Avatar asked Jan 14 '23 02:01

Oliver


2 Answers

This looks like a nice example to try the superpixels, as implemented by skimage. You can probably do this in an easier way for your problem.

import urllib
import random
import io
import matplotlib.pyplot as plt
import skimage.segmentation
import pandas

# Read the image
f = io.BytesIO(urllib.urlopen('http://oi46.tinypic.com/34il9hu.jpg').read())
img = plt.imread(f, format='jpg')

# Prefer to keep pixels together based on location
# But not too much, so we still get some branches.
superpixel = skimage.segmentation.slic(img, n_segments=200, ratio=20)
plt.imshow(superpixel%7, cmap='Set2')

Superpixels

Now that we have superpixels we can do classification a bit easier, by doing it per superpixel. You could use some fancy classification here, but this example is quite simple, with a blueish sky, let's do it by hand.

# Create a data frame with the relative blueish of every super pixel

# Convert image to hsv 
hsv = matplotlib.colors.rgb_to_hsv(img.astype('float32')/255)
# Define blueish as the percentage of pixels in the blueish range of the hue space
df =pandas.DataFrame({'superpixel':superpixel.ravel(), 
    'blue':((hsv[:,:,0] > 0.4) & (hsv[:,:,0]<0.8)).astype('float32').ravel(), 
    'value':hsv[:,:,2].ravel()})    
grouped = df.groupby('superpixel').mean()    
# Lookup the superpixels with the least blue
blue = grouped.sort('blue', ascending=True).head(100)
# Lookup the darkest pixels
light = grouped.sort('value', ascending=True).head(50)

# If superpixels are too dark or too blue, get rid of them
mask = (np.in1d(superpixel, light.index ).reshape(superpixel.shape) | 
    np.in1d(superpixel, blue.index ).reshape(superpixel.shape))

# Now we can put the stars on the blueish, not too darkish areas
def randomstar(img, mask):
    """random located star"""
    x,y = random.randint(1,img.shape[0]-1), random.randint(1,img.shape[1]-1)
    if not mask[x-1:x+1, y-1:y+1].any():
        # color not so random
        img[x,y,:] = 255
        img[x-1,y,:] = 255
        img[x+1,y,:] = 255
        img[x,y-1,:] = 255
        img[x,y+1,:] = 255

for i in range(100):
    randomstar(img, mask)
plt.imshow(img)

stars in the sky

like image 183
SiggyF Avatar answered Jan 19 '23 11:01

SiggyF


Python's standard library doesn't come with any powerful-enough image-manipulation code, but there are a few alternatives that are easy to install and use. I'll show how to do this with PIL.

from PIL import Image

def randomStars(small, large):
    import random
    filename = pickAFile()
    pic = Image.open(filename)
    max_x, max_y = pic.size
    pixels = im.load()
    x = random.randrange(max_x)
    y = random.randrange(max_y)
    for i in range(max_x):
        for j in range(max_y):
            if random.random() < 0.25:
                red = random.randint(256)
                green = random.randint(256)
                blue = random.randint(256) 
                pixels[i, j] = (red, green, blue, 1)
    im.show()

The show function doesn't display the image in your app (for that, you'd need some kind of GUI with an event loop, like tkinter or PySide); it saves a file to a temporary directory and runs a platform-specific program like Preview or xv to display it.

I assume you're also going to want to save the file. That's easy too:

    name, ext = os.path.splitext(filename)
    outfilename = '{}-with-stars.{}'.format(name, ext)
    im.save(outfilename)

This will save it back to a .jpg with default JPEG settings, relying on PIL guessing what you want from the filename. (Which means, yes, you can save it as a PNG just by using '{}-with-stars.png'.format(name).) If you want more control, PIL can do that too, specifying an explicit format, and format-specific options.


So far, this is just how to turn your existing code into something that works, that you can play with and start debugging; it doesn't actually answer the original problem.

I want to write a function that will create a random number (between m and n, inclusive) of stars in the sky of this picture

So first, you need this as your loop, instead of a loop over all pixels:

for _ in random.randint(m, n):

Now:

I want the stars should be randomly composed of either a single white pixel, or a square of 4 adjacent white pixels.

    x, y = random.randrange(max_x), random.randrange(max_y)
    if random.random() < .5:
        # draw white pixel at [x, y]
        pixels[x, y] = (1, 1, 1, 1)
    else:
        # draw square at [x, y], making sure to handle edges

I also do not want to place a 'star' (of 1 pixel) over the tree branches, the moon or the bird though

You need to define how you know what's part of a tree branch, the moon, or the bird. Can you define that in terms of pixel colors?

From a quick glance, it looks like you might be able to. The moon's pixels are all brighter, more saturated, more red-biased, etc. than anything else (except the AP logo in the corner, which is even brighter). The bird and the branches are darker than anything else. In fact, they're so distinct that you probably don't even have to worry about doing correct colorspace math; it may be as simple as something like this:

r, g, b, a = pixels[x, y]
fake_brightness = r+g+b+a
if fake_brightness < 0.2:
    # Tree or bird, pick a new random position
elif 1.2 < fake_brightness < 2.8:
    # Moon, pick a new random position
else:
    # Sky or API logo, scribble away

(Those numbers are obviously just pulled out of thin air, but a bit of trial and error should give you usable values.)

Of course if you're doing this as a learning exercise, you probably want to learn the correct colorspace math, and maybe even write an edge-detection algorithm, rather than relying on this image being so simply-parseable.

like image 39
abarnert Avatar answered Jan 19 '23 10:01

abarnert