This Python script can be used to create a batch of images of white segments on a black background, with varying, random shapes (triangles, ellipses and rectangles) and placement. Download (Python 3.11 script).
# Knowledgedump.org - random_shape_generation
# Creates 1000 images of shapes, consisting of triangles, ellipses and rectangles.
# The shapes are white on a black 800x800 pixel background by default.
# Each shape has minimum and maximum size requirements, so that they can't be too small/large.
# Required packages: numpy, matplotlib, scikit-image.
import numpy as np
import matplotlib.pyplot as pypl
import os
import random
from skimage.draw import polygon, ellipse
import zipfile
# Function to generate a random "well-formed" triangle (not too small/large/close to a line)
def generate_triangle(image_size=(800, 800)):
# Maximum and minimum sizes are 20% and 5% of the smallest img dimension
max_size = round(min(image_size) * 0.2)
min_size = round(min(image_size) * 0.05)
# Randomly choose first point
x1, y1 = random.randint(0, image_size[0]), random.randint(0, image_size[1])
# Randomly choose second point within distance of [min_size, max_size]
x2, y2 = random.randint(max(0, x1 - max_size), min(image_size[0], x1 + max_size)), random.randint(max(0, y1 - max_size), min(image_size[1], y1 + max_size))
# To ensure that the triangle is not misshapen, the third point can't be too close to the line formed
# between points 1 and 2. This can for instance be implemented, by calculating the orthogonal vector
# to the vector between pts 1 and 2, normalizing it and then applying the size requirement for the "height"
# of our triangle. The third point is then chosen as the vector we thus constructed, added to some point on the line
# between points 1 and 2 (here, we simply pick the midpoint between 1 and 2).
# Calculate the orthogonal vector to the vector between points 1 and 2, given by np.array([x2 - x1, y2 - y1])
v_orth = np.array([y1 - y2, x2 - x1])
# Normalize the orthogonal
v_orth_norm = v_orth / np.linalg.norm(v_orth)
# Random distance for the third point from the line between points 1 and 2 (within parameters)
height = random.randint(min_size, max_size)
# Calculate midpoint and place third point.
midpoint = np.array([x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2])
x3, y3 = midpoint + height * v_orth_norm
# Ensure the third point is within the image bounds. This could still result in "misshapen" triangles,
# when point 1 and 2 are close to the image border and the orthogonal vector points "outside",
# but it's unlikely that there will be many of them, so we don't bother adding more complexity to this
# and simply ensure that point 3 is inside the image borders.
x3, y3 = int(np.clip(x3, 0, image_size[0])), int(np.clip(y3, 0, image_size[1]))
# Initialize a black image (array of zeros)
image = np.zeros(image_size, dtype=np.uint8)
# Create the triangle using the polygon function from skimage.draw (scikit-image).
# It returns the pixel coordinates for the triangle, so we can set those to white (255) on our black image.
# (see pypl.imsave colormap settings below)
rr, cc = polygon([y1, y2, y3], [x1, x2, x3], shape=image_size)
image[rr, cc] = 255
return image
# Function to generate a random ellipse with a size cap and minimum size
def generate_ellipse(image_size=(800, 800)):
# Maximum and minimum diameters are set to 20% and 5% of the smallest img dimension
max_radius = round(min(image_size) * 0.1)
min_radius = round(min(image_size) * 0.025)
# Set random horizontal and vertical radius of ellipse
rx = random.randint(min_radius, max_radius)
ry = random.randint(min_radius, max_radius)
# Random center
cx = random.randint(rx, image_size[0] - rx)
cy = random.randint(ry, image_size[1] - ry)
# Initialize a black image
image = np.zeros(image_size, dtype=np.uint8)
# Create the ellipse using the ellipse function from skimage.draw, setting the values to 255 for white.
rr, cc = ellipse(cx, cy, rx, ry)
image[rr, cc] = 255
return image
# Function to generate a random rectangle within size limits.
def generate_rectangle(image_size=(800, 800)):
# Maximum and minimum sizes are 20% and 5% of the smallest img dimension
max_size = round(min(image_size) * 0.2)
min_size = round(min(image_size) * 0.05)
# Random width and height within the size range
rect_width = random.randint(min_size, max_size)
rect_height = random.randint(min_size, max_size)
# Random position for the top-left corner of the rectangle
x1 = random.randint(0, image_size[0] - rect_width)
y1 = random.randint(0, image_size[1] - rect_height)
# Initialize a black image
image = np.zeros(image_size, dtype=np.uint8)
# Draw the rectangle (fill the area) in white colour (255).
image[y1:y1+rect_height, x1:x1+rect_width] = 255
return image
if __name__ == "__main__":
# Create folder at the location of the python script to save images.
current_dir = os.path.dirname(os.path.abspath(__file__))
output_dir = os.path.join(current_dir, "random_segments")
os.makedirs(output_dir, exist_ok=True)
# Function to generate and save randomly shaped segments (triangles, ellipses, rectangles).
def generate_random_segments(image_size=(800, 800), num_segments=1000):
image_files = []
for i in range(num_segments):
shape_type = random.choice(["triangle", "ellipse", "rectangle"])
if shape_type == "triangle":
segment = generate_triangle(image_size)
elif shape_type == "ellipse":
segment = generate_ellipse(image_size)
else:
segment = generate_rectangle(image_size)
# Save the segment as a PNG image, using matplotlib.imsave function with "gray" colormap.
# The grayscale colormap goes from 0 for black to 255 for white.
image_filename = os.path.join(output_dir, f"segment_{i+1}.png")
pypl.imsave(image_filename, segment, cmap="gray", format="png")
image_files.append(image_filename)
# Return the list of saved image filenames
return image_files
# Generate num_segment (by default 1000) random segments and save them as images.
image_files = generate_random_segments()
# Compress the images into a zip file.
zip_filename = os.path.join(current_dir, "random_segments.zip")
# Using "with" keyword for cleaner opening the file and closing it after all files are written.
# Call class constructor to create "Zipfile" object and add images, using its ".write" method.
with zipfile.ZipFile(zip_filename, "w") as zipfi:
for file in image_files:
zipfi.write(file, os.path.basename(file))
print(f"All images have been successfully created and compressed into {zip_filename}.")