Monday, February 15, 2021

Generating a Gradient Programmatically

I've posted many times on this blog, my experiments in image generation through Python. I've been working on what I call my "Noisefield" project, where I generated a bitmap with random black and white pixels. I have now done something similar, except this time I generated a black to white gradient, using the PIL library in Python.

import time
import random
from PIL import Image
LINE = []
for i in range(256):
LINE.append(i)
MATRIX = LINE*256
nixel = iter(MATRIX)
def noisefield():
im = Image.new("L", (256, 256))
for x in range(256):
for y in range(256):
im.putpixel((x, y), next(nixel))
im.save('<FILEPATH>'+time.strftime('%Y%m%d%H%M%S')+".png")
im.show()
noisefield()
view raw gradient.py hosted with ❤ by GitHub

The idea is pretty simple. You create a vector or array of values from 0 to 255, then you repeat that 256 times, to make a square matrix, or bitmap (because Python puts in the pixels from top-left downwards and towards the right, line by line). So that way you get a gradient. I took the "LINE" variable that I created with a for loop and range() function and then multiplied it by 256 in a variable called MATRIX. I then used an iterator function on the Matrix which I called in the image-generation function by calling next(MATRIX). The result is a smooth matrix.

ADDENDUM: I have found a simpler way of generating a black to white gradient, using the np.linspace function in Numpy.

import numpy as np
from PIL import Image
# gradient between 0 and 1 for 256*256
array = np.linspace(0,1,256*256)
# reshape to 2d
mat = np.reshape(array,(256,256))
# Creates and saves PIL image
img = Image.fromarray(np.uint8(mat * 255) , 'L')
img.save('/FILEPATH/Gradient.jpg')
# OR img.show()
'''
# If you want to create a 512 pixel by 512 pixel image instead:
>>> array = np.linspace(0,1,256*256)
>>> array = np.repeat(array, 4)
>>> len(array)
262144
>>> mat = np.reshape(array,(512,512))
>>> img = Image.fromarray(np.uint8(mat*255), 'L')
>>> img.save('/FILEPATH/Gradient.jpg')
# OR img.show()
'''
view raw gradient.py hosted with ❤ by GitHub

FOR YOUR INFORMATION: I have written a few blog posts in the past about generating bitmaps with random black and white pixel values, or grey values between 0 and 255. I found a much quicker way to do this using the OpenCV library (cv2). (In passing, this np.random.randint() function gives the same results as the uniform_noise function below, in the second Gist below:

import cv2
import numpy as np
noise = np.random.randint(0,256, size= (512, 512), dtype=np.uint8)
cv2.imwrite("Noise.jpg", noise)
'''
# OR JUST USE THE FOLLOWING:
import matplotlib.pyplot as plt
plt.imsave("Noise.jpg", noise, cmap='Greys')
'''
view raw noise.py hosted with ❤ by GitHub

Here's another way to generate noise, Gaussian noise in this case, via OpenCV (cv2). The Gaussian distribution is "smoother" in appearance, as it has mean 128 and standard deviation 20:

from PIL import Image
import cv2
import numpy as np
image = Image.new(mode = "L", size = (512,512))
image = np.array(image)
gaussian_noise = np.zeros((image.shape[0], image.shape[1]),dtype=np.uint8)
cv2.randn(gaussian_noise, 128, 20)
cv2.imwrite("GaussianRandomNoise.jpg",gaussian_noise)
'''
# OR FOR DIFFERENT DISTRIBUTION OF NOISE, WITH MEAN 0 AND STANDARD DEVIATION 256, USE:
>>> cv2.randn(gaussian_noise, 0, 256)
uniform_noise = np.zeros((image.shape[0], image.shape[1]),dtype=np.uint8)
# OR:
>>> cv2.randu(uniform_noise,0,255)
>>> cv2.imwrite("Uniform random noise.jpg",uniform_noise)
# OR:
>>> ret,impulse_noise = cv2.threshold(uniform_noise,250,255,cv2.THRESH_BINARY)
>>> cv2.imwrite("Impulse noise.jpg",impulse_noise)
'''
view raw noise.py hosted with ❤ by GitHub

With mean 0 and standard deviation 256, we get a "coarser" noise distribution:

As stated, the uniform_noise "randu" function gives the same result as the previous np.random.randint() function, a uniform noise distribution:

If I add the following, "ret,thresh1 = cv2.threshold(uniform_noise,64,255,cv2.THRESH_BINARY)" and then write "cv2.imwrite("Noise.jpg",thresh1)", I get a noisy distribution with many more whites, because of the threshold value being at 64. For every pixel, the same threshold value is applied. If the pixel value is smaller than the threshold, it is set to 0, otherwise it is set to a maximum value, in this case 255, which is pure white. This way it is possible to adjust the parseness if you will of the noisy distribution using the threshold function:

The opposite would be true if we set the treshold higher, to say 250. Then it's mostly black, or at a pixel value of 0:

No comments:

Post a Comment