Hand-Drawn Shape Simulation
Published on July 5, 2022 | Last Modified on February 11, 2025
Shape recognition is a fundamental aspect of human cognition. Even at just 2–3 years old, children can effortlessly recognize objects despite variations in size, position, orientation, or even when the objects are significantly deformed. For instance, they can easily identify heart shapes in both the images above and below.
These intentionally distorted forms, known as “hand-drawn shapes,” are invaluable for testing various shape recognition technologies. In the following sections, we’ll demonstrate how to generate these shapes using the hdss software from GitHub, requiring just two simple Python functions.
About the Method
Every shape can be deconstructed into one or more smooth curves. For example, the heart shape below consists of two such curves, highlighted in green and red. Each smooth curve can be defined by a small set of control points along its path, while the rest of the curve is reconstructed through Bézier interpolation. Consequently, a shape can be fully described using one or more text files containing the coordinates of these control points.
Local Distortion: Any slight alteration to the position of a control point introduces a localized distortion near that point. By randomly adjusting all control points — essentially adding random noise — you can generate numerous variations of the original shape that appear hand-drawn.
Global Distortion: Alternatively, you can apply a global distortion by performing a random perspective transformation on all control points simultaneously. After transforming their positions, the shape is reconstructed using Bézier interpolation.
To apply this transformation, define the coordinates of two corresponding quadrilaterals:
1. Src: The positions of four crosses randomly placed within the blue zones.
2. Dst: The corners of the orange square (left image).
Tip: While you can apply random local or global distortions independently, combining both methods produces more natural-looking, hand-drawn shapes.
Generating Shapes with Code
"""
https://github.com/boriskravtsov/hand-drawn-shape-simulation
"""
from pathlib import Path
from hdss.src import cfg
from hdss.src.utils import init_directory
from hdss.src.set_params import set_params
from hdss.src.create_shapes import create_shapes
import colors
dir_name = 'data_hearts'
init_directory(dir_name)
set_params(
200, # shape_size = 200 x 200
5, # number_of_shapes
True, # perspective_flag
7, # bezier_noise_param
6) # line_thickness
cfg.flag_fill = False
cfg.fill_color = colors.black
cfg.contour_color = colors.maraschino
shape_name = 'heart'
dir_curves = Path.cwd() / '_CURVES_'
path_curve1 = dir_curves / 'heart_curve1.txt'
path_curve2 = dir_curves / 'heart_curve2.txt'
create_shapes(dir_name, shape_name, path_curve1, path_curve2)
The code provided generates five “hand-drawn” heart shapes at a resolution of 200x200 pixels. These images are saved in a newly created directory named data_hearts, with the following filenames:
heart_0.png, heart_1.png, heart_2.png, heart_3.png, heart_4.png.
The results are shown in Fig. 1 above.
Note: Control point coordinates are expressed as percentages, based on a default shape size of 100x100 pixels. When generating shapes of different sizes, the hdss package automatically recalculates these coordinates.
Additional Examples
Popular geometric shapes, such as stars, can also be created using Bézier splines. The following figures illustrate how control points are used to define these shapes.
Moreover, the hdss package provides flexible coloring options. Below are examples showcasing various coloring styles applied to the generated shapes.