Create Air Canvas using Python Open CV

We know that artists create paintings on a canvas. But what if we can paint on air just by waving our hands. So, in this project, we are going to build an air canvas using OpenCV and Python.

OpenCV is an open-source computer vision library for performing various advanced image processing tasks.

We’ll use color detection and segmentation techniques to achieve this objective.

Here, we’re going to use a green object to simulate a pencil for the canvas.

So what is Color Detection and Segmentation in Image Processing?

  • Color detection is an image processing technique where we can detect any color in a given range of HSV color space.
  • Image segmentation is the process of labeling every pixel in an image, where each pixel shares the same certain characteristics.

Project Prerequisites:

1. Python – 3.x (We used 3.8.8 for this project)
2. OpenCV – 4.4

  • Run “pip install opencv-python opencv_contrib-python” to install the package

3. Numpy – 1.20.1

Download Air Canvas OpenCV Python Code

Please download the source code of python air canvas project: Air Canvas OpenCV Project Code

Steps to develop air canvas project using opencv:

  • Import necessary packages.
  • Read frames from a webcam
  • Create the canvas window
  • Detect the green color
  • Draw on the canvas

Step 1 – Import necessary packages and pre-define some settings:

To build this project, we need only two packages, OpenCV and Numpy. So first we’ve to import these.

# Import necessary packages.
import cv2
import numpy as np

Step 2 – Read frames from a webcam:

# Create videocapture object
cap = cv2.VideoCapture(0)

while True:
    # Read each frame from webcam
    success, frame = cap.read()

    # Flip the frame
    frame = cv2.flip(frame, 1)

    cv2.imshow("Frame", frame)

    # Open the OpenCV window until 'q' is pressed
    if cv2.waitKey(1) == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
  • First, we created a VideoCapture object and defined the object as cap.
  • cap.read() function reads each frame from the webcam.
  • cv2.flip() flips the frame. The arguments are input image/frame and axis. 0 is for flip vertically, and 1 for flip horizontally.
  • cv2.imshow() function shows frame in a new window.
  • cv2.waitKey(1) == ord(‘q’) keep open the window until ‘q’ is pressed.

Step 3 – Create the canvas window:

# Define various colors
colors = [(255, 0, 0), (255, 0, 255), (0, 255, 0), (0, 0, 255), (0, 255, 255)]
color = colors[0]
  • First, we define some colors that we’ll be using during this project.
    width = int(cap.get(3))
    height = int(cap.get(4))
    
    # Create a blank canvas 
    canvas = np.zeros((height, width, 3), np.uint8)
  • Then we create an empty blank canvas the same size as the camera frame.
  • np.zeros() create a matrix containing all 0’s.

We want to change the pencil color during painting, That’s why we have to create some buttons in the frame. Using those buttons we can select the pencil color during painting in real-time.

# Adding the colour buttons to the live frame for colour access
    cv2.rectangle(frame, (20,1), (120,65), (122,122,122), -1)
    cv2.rectangle(frame, (140,1), (220,65), colors[0], -1)
    cv2.rectangle(frame, (240,1), (320,65), colors[1], -1)
    cv2.rectangle(frame, (340,1), (420,65), colors[2], -1)
    cv2.rectangle(frame, (440,1), (520,65), colors[3], -1)
    cv2.rectangle(frame, (540,1), (620,65), colors[4], -1)
    cv2.putText(frame, "CLEAR ALL", (30, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, 
(255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "BLUE", (155, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
 (255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "VIOLET", (255, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
 (255,255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "GREEN", (355, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, 
(255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "RED", (465, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5,
 							(255, 255, 255), 2, cv2.LINE_AA)
    cv2.putText(frame, "YELLOW", (555, 33), cv2.FONT_HERSHEY_SIMPLEX, 0.5, 
(255,255,255), 2, cv2.LINE_AA)
  • Using cv2.rectangle(), we draw some rectangles of different colors that we’ve defined before.
  • cv2.putText() draws texts on a frame. Using this function we put each of the names of the colors to their corresponding rectangle box.

Output:

canvas

Step 4 – Detect the green color:

Here we’re using a green object to simulate the pencil of our canvas. OpenCV reads frames as BGR color space. But to detect colors, we have to convert the frame to the HSV color space.

But what is HSV color-space?

HSV stands for HUE, SATURATION, and VALUE (or brightness). It is basically a cylindrical color space.

Note: This image is taken from google.

  • HUE – The hue encodes color information in angular dimension.
  • SATURATION – Saturation encodes the intensity of color.
  • VALUE – Value represents the brightness of the color.

So let’s detect the green object:

# Color range for detecting green color
   lower_bound = np.array([50,100,100])
   upper_bound = np.array([90,255,255])

     # Convert the frame BGR to HSV color space
   hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
   
   # Create a binary segmented mask of green color
   mask = cv2.inRange(hsv, lower_bound, upper_bound)

   # Add some dialation to increase segmented area
   mask = cv2.dilate(mask, kernel, iterations=1)
  • cv2.cvtCOLOR() function converts the color-space of an image.
  • cv2.inRange() function returns a binary segmented mask containing a white region where the color is detected and the rest of the region as black.
  • Cv2.dilate() increases the area of the segmented white region.

Output:

mask

 # Find all the contours of the segmented mask
    contours, h    = cv2.findContours(mask, cv2.RETR_TREE, 
cv2.CHAIN_APPROX_SIMPLE)
  • cv2.findContours() finds all the continuous points along the boundary having the same intensity or color. In our case, we are finding points along the segmented white region of the mask.

Now, let’s find the center of the detected object. The center point will be the reference point with which we’ll be painting something on the canvas.

# Checking if any contour is detected then run the following statements
    if len(contours) > 0:
        
        # Get the biggest contour from all the detected contours
        cmax = max(contours, key = cv2.contourArea)

    else:
        previous_center_point= 0
  • First, we check if any contour is detected or not.
  • If any contour is detected then we pick a contour that has maximum area among all other contours in the list.
  • Otherwise, we set previous_center_pointto 0. The previous_center_pointbasically stores the center point of a contour in a frame.
# Find the area of the contour
        area = cv2.contourArea(cmax)
  • After picking the biggest contour we get the area of the contour using cv2.contourArea() function.

It may be possible that the main object is not present in the frame but some noises are detected. So to fix this we need to filter that as well.

min_area = 1000
          # Checking if the area of the contour is greater than a threshold
        if area > min_area:

            # Find center point of the contour
            M = cv2.moments(cmax)
            cX = int(M["m10"] / M["m00"])
            cY = int(M["m01"] / M["m00"])
            
            # Drawing a circle in the center of the contour area
            cv2.circle(frame, (cX, cY), 10, (0, 0, 255), 2)
  • First, we check if the area of the contour is greater than the defined min_area.
  • cv2.moments() function finds the center coordinate of the contour.
  • Using cv2.circle we draw a circle in the center of the detected object.

Output:

with-circle

Now we’ve successfully detected our pencil. So let’s do the most exciting thing of the project.

Step 5 – Draw on the canvas:

To draw on the canvas first we’ve to select a color for drawing.

# Selecting the color for drawing in the canvas
            if previous_center_point== 0:
                if cY < 65:
                    # Clear all
                    if cX > 20 and cX < 120:
                        canvas = np.zeros((height, width, 3), np.uint8)
                    
                    elif cX > 140 and cX < 220:
                        color = colors[0]

                    elif cX > 240 and cX < 320:
                        color = colors[1]
                    
                    elif cX > 340 and cX < 420:
                        color = colors[2]
                    
                    elif cX > 440 and cX < 520:
                        color = colors[3]
                    
                    elif cX > 540 and cX < 620:
                        color = colors[4]
  • First, we check if the previous_center_point is 0, which means nothing is detected in the previous frame.
  • But in the current frame, if something is detected, we already know the coordinates of the color selection buttons. So we checked in which button the center point of the contour is detected first. And then set the color accordingly.
  • The first button is for clearing the canvas, that’s why if the center point is detected in the first button, then we reset the canvas to a blank canvas using np.zeros() function.
# If drawing is started then draw a line between each frames detected contour 
#  center point
            if previous_center_point!= 0:
                cv2.line(canvas, previous_center_point, (cX, cY), color, 2)

            # Update the center point
            previous_center_point= (cX, cY)
  • Now we check if the previous_center_point is not 0, which means a contour is detected in the previous frame, then we draw a line between the previous_center_point and the current center point.
  • After that, we update the previous_center_point to the current center point.

But we want to show the drawings in the original frame. So let’s see how we can do that also.

# Adding the canvas mask to the original frame
            canvas_gray = cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY)

            _, canvas_binary = cv2.threshold(canvas_gray, 20, 255,
 cv2.THRESH_BINARY_INV)

            canvas_binary = cv2.cvtColor(canvas_binary, cv2.COLOR_GRAY2BGR)

            frame = cv2.bitwise_and(frame, canvas_binary)
            frame = cv2.bitwise_or(frame, canvas)
  • Using cv2.cvtCOLOR() function we convert the canvas to grayscale image.
  • Then using cv2.threshold() we create a binary mask of the canvas.
  • Then again we convert the binary mask to BGR color space. Because in order to add two images, the shape of the two images needs to be the same.
  • cv2.bitwise_and() is true if both pixels are greater than zero.
  • cv2.bitwise_or() is true if one of the pixels is greater than zero.

Air Canvas OpenCV Output

air canvas opencv output

Summary:

In this project, we’ve built an Air canvas using OpenCV – Python. We’ve learned about color detection and segmentation techniques, thresholding techniques, some logical operations, and some other image processing techniques through this project.