DEV Community

Markus Koppensteiner
Markus Koppensteiner

Posted on

Making a simple pointillism painting using OpenCv.

Introduction
The Open Source Computer Vision Library (OpenCV) offers freely available programming tools to handle visual input such as images or video files. It contains many ready-to-use functions, which can be accessed via different programming languages. The example I posted here uses python. Therefore, if you want to understand the code you need a basic knowledge of python and NumPy at least. If you're looking for an introduction to OpenCV, this link may be quite valuable: [https://dev.to/arpitmandliya/opencv-python-tutorial-3dac].

How pixels make an image
In most cases a computer image is based on the RGB (BGR in Opencv) model. This means that pixel colour is a mix of the components Red, Green and Blue. There are other models as well (e.g., Hue, Saturation and Value) and vector graphics (SVG or PDF) but I will not them explain them here.

An image on a computer can be depicted as a collection of pixels, which contain colour information. In more technical terms, an image is three- dimensional array (or a matrix of pixels with three colour channels) with the first two dimensions determining the size (height and width) of the image and the third dimension containing the values of red, green and blue (each colour with values between 0 to 255) . If an image has only one colour channel (8 bit image) it is grey-scale image with different values of grey ranging from 0 (black) to 255 (white). Figure 1 illustrates that.

Image description
Figure 1: Images are represented as arrays. On the right is an example of a colour image, where the values for Red, Green, and Blue range from 0 to 255 (0,0,255 is blue). On the left is a grayscale image with a single channel representing different shades of grey.

Turning colour information into different sized dots
The principles discussed above can be applied to perform image editing in Python using the NumPy and OpenCV libraries. In this example, I use loops to process an image represented as a NumPy array. The loop doesn't iterate over every pixel in the image but skips pixels at regular intervals ( e.g., it processes every 10th pixel). The grayscale value at each processed pixel is used to determine the size of a dot (e.g., a grayscale value of 100 corresponds to a specific dot size). These dots are then drawn on an empty copy of the original image, using the colour information from the original image. In summary, I create an image copy where dots of varying sizes are drawn based on the colour information of the original pixels (see Figure 2).

Image description
Figure 2: To draw a dot the colour information of a pixel in the original image is used. To determine the size of the dot the greyscale version of the original image is used.

Below you find the code and a possible outcome is shown in Figure 3.

import numpy as np
import cv2

# load an image; image has to be in working directory when giving no path information 
img = cv2.imread('FlowerPower.jpg',cv2.IMREAD_UNCHANGED)
# show the dimensions of the image array
print(img.shape)

# choose a resizing factor for the whole image; to depict it on computer screen
resizing = .2
#convert original image to greyscale image
img_grey = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# make a copy of the orignal image 
img_output = img.copy()

# make a white canvas by assigning the color white (255,255, 255) to each pixel
# [:,:] covers all values of the first and second array dimension
img_output[:,:] = [255,255,255] # or with black [0,0,0] or any other color

# Settings for looping over the image
step_width = 40 # steps of loop; here: every 30th pixel
# - 1 fills circle that is drawn onto output image; positive value define
# line thickness of circle
thickness = -1 
perc = .2 # size factor for drawing circles/dots onto output image

# for loops running over the first two dimensions of the array (width and height) 
# step_width defines which pixels are included
for i in range(2,  img.shape[0] - step_width,  step_width):
    for u in range(2,  img.shape[1] - step_width,  step_width):        
        # radius (dot size) is based on the value of greyscale version of original image
        # at the current index; e.g., pixel at i = 10, u = 30 might have 123
        # perc variable modifies dot size 
        radius = int((255-img_grey[i,u])*perc) +1 
        if radius <= 0:
            radius +=1
        # take color from pixel at position [i,u] of original image
        # e.g., i = 10, u = 30 might have [123,0,61] 
        color = img[i,u].astype(int).tolist()
        # draw a circle on output image using dot size based on greyscale 
        # value with color of original image   
        cv2.circle(img_output, (u,i), radius, color, thickness)

# resize images, so they are not too big for computerscreen
# based on the resizing variable defined at the top of the page        
img_size = img.shape        
img_sm =  cv2.resize(img,(int(img_size[1]*resizing), int(img_size[0]
                         * resizing)), interpolation = cv2.INTER_CUBIC)
# open window that shows original image
cv2.imshow("Original", img_sm)
img_output_sm =  cv2.resize(img_output,(int(img_size[1]*resizing), int(img_size[0]*
                              resizing)), interpolation = cv2.INTER_CUBIC)
# show the dotted image
cv2.imshow("Dotted Image", img_output_sm)
Enter fullscreen mode Exit fullscreen mode

Image description
Figure 3: On the right hand side the original image is shown and on the left hand side a dotted version based on the code presented here is shown.

I hope I presented the code in a comprehensive manner and that someone might find it useful. Play around with it, if you like to. Replace the circles by rectangles, select different sizes of circles, change the values in the step width of the loops etc. and see what happens.

Top comments (0)