🖊️ Marcador o Lápiz virtual | OpenCV – Python

Por Administrador

Te doy la bienvenida a un nuevo post, en el que te mostraré como puedes realizar un marcador o lápiz virtual (similar a paint), con el que ¡podrás dibujar en el aire!. Además podrás escoger de entre algunos colores así como el grosor de línea, también veremos como borrar la pantalla completa, es decir borrar todos los trazos realizados.

CONTENIDO:

  • Declarando variables
  • Leemos frame y aplicamos cv2.flip
  • Transformamos de BGR a HSV
  • Creación de un lienzo
  • Construcción de la sección superior de la pantalla, en donde estarán las opciones de dibujo
  • Detección del color del objeto (marcador / lápiz virtual)
    • Encontrando contornos
    • Estableciendo un área mínima para la detección y obteniendo las coordenadas para realizar los trazos
  • Seleccionando colores para dibujar
  • Seleccionando el grosor de los trazos para dibujar
  • Seleccionando ‘Limpiar pantalla’
  • Dibujando los trazos
  • Dibujando sobre el videostreaming
  • Programación completa
  • Referencias

Lo primero que haremos es detectar un color, que corresponderá al color del objeto con el que vamos a dibujar, luego construiremos en la parte superior de la visualización un pequeño menú para elegir los colores con los cuales dibujar, limpiar la pantalla y elegir el grosor de los trazos.

NOTA: Te recomiendo visitar el link que tomé como referencia para la realización de este post.

Declarando variables

Empezaremos a declarar algunas variables que nos serán de ayuda para esta aplicación, veamos:

import cv2
import numpy as np

cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)

celesteBajo = np.array([75, 185, 88], np.uint8)
celesteAlto = np.array([112, 255, 255], np.uint8)

# Colores para pintar
colorCeleste = (255,113,82)
colorAmarillo = (89,222,255)
colorRosa = (128,0,255)
colorVerde = (0,255,36)
colorLimpiarPantalla = (29,112,246) # Solo se usará para el cuadro superior de 'Limpiar Pantalla'

# Grosor de línea recuadros superior izquierda (color a dibujar)
grosorCeleste = 6
grosorAmarillo = 2
grosorRosa = 2
grosorVerde = 2

# Grosor de línea recuadros superior derecha (grosor del marcador para dibujar)
grosorPeque = 6
grosorMedio = 1
grosorGrande = 1

#--------------------- Variables para el marcador / lápiz virtual -------------------------
color = colorCeleste  # Color de entrada, y variable que asignará el color del marcador
grosor = 3 # Grosor que tendrá el marcador
#------------------------------------------------------------------------------------------

x1 = None
y1 = None
imAux = None

Línea 1 y 2: Importamos OpenCV y Numpy.

Línea 4: Indicamos que vamos a realizar un video Streaming.

Línea 6 y 7: Declaramos las variables que corresponden al rango del color que deseamos detectar en el espacio de color HSV. En mi caso usaré la tapa de un bolígrafo de color celeste, por ello he determinado celesteBajo y celesteAlto que me ayudarán a detectar dicho color. Si tienes alguna duda con respecto a este procedimiento, puedes visitar estos posts: Detección de colores en OpenCV – Python (En 4 pasos) y DETECCIÓN DE COLORES Y Tracking en OpenCV – Parte2 en donde explico de forma más extendida este tema.

Línea 10 a 14: De la línea 10 a la 13 asignamos a variables los colores con los que deseamos dibujar, y en la línea 14 en cambio asignamos el color que va a obtener el rectángulo ‘Limpiar Ventana’.

Línea 17 a 20: Estas variables nos ayudarán con el grosor de los recuadros de la parte superior izquierda, en donde el usuario podrá escoger de entre los 4 colores: celeste, amarillo, rosa y verde. Además podremos resaltar el color seleccionado.

Línea 23 a 25: Estas variables en cambio nos ayidarán para los recuadros de la parte superior derecha, en donde se podrá el distinto tamaño o grosor de línea. Además podremos resaltar el recuadro seleccionado.

Línea 28: Declaramos la variable color, a este le asignaremos por defecto el color azul/celeste para que dibuje este en un inicio. A esta variable además se le asignará los colores del 10 al 13, dependiendo de lo que haya elegido el usuario.

Línea 29: Declaramos la variable grosor, esta nos servirá para asignar el grosor que tendrá la línea a dibujar. A esta variable además se le asignarán otros valores, de acuerdo a lo que elija el usuario.

Línea 32 y 33: Declaramos x1 y y1 como None, a estas dos variables luego se les asignará la posición anterior, de acuerdo a como se vaya moviendo el objeto.

Línea 34: Declaramos imAux como None, esta variable nos ayudará luego, para construir un lienzo en donde se dibujarán los trazos.

Leemos frame y aplicamos cv2.flip

Lo primero que haremos es leer los fotogramas, luego aplicaremos cv2.flip para obtener el efecto de espejo.

while True:

    ret,frame = cap.read()
    if ret==False: break

    frame = cv2.flip(frame,1)

Línea 38: Se leen los fotogramas y se almacenan en frame.

Línea 41: Aplicamos cv2.flip, a la variable frame, para poder obtener el efecto espejo (puedes visitar este post: cv2.flip y Efecto ESPEJO en Python – OpenCV si deseas más información sobre esta función).

Transformar de BGR a HSV

Para realizar la transformación de espacios de color, necesitaremos de la función cv2.cvtColor.

frameHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

Creación de un lienzo

Vamos a crear un imagen o matriz de ceros, del mismo tamaño de frame, sobre ella luego realizaremos los trazos.

if imAux is None: imAux = np.zeros(frame.shape,dtype=np.uint8)

Figura 1: Visualización de imAux al ser creado.

Construcción de la sección superior de la pantalla, en donde estarán las opciones de dibujo

Ahora necesitamos distribuir las opciones de dibujo en la parte superior de la pantalla, veamos:

#------------------------ Sección Superior ------------------------------------------
# Cuadrados dibujados en la parte superior izquierda (representan el color a dibujar)
cv2.rectangle(frame,(0,0),(50,50),colorAmarillo,grosorAmarillo)
cv2.rectangle(frame,(50,0),(100,50),colorRosa,grosorRosa)
cv2.rectangle(frame,(100,0),(150,50),colorVerde,grosorVerde)
cv2.rectangle(frame,(150,0),(200,50),colorCeleste,grosorCeleste)

# Rectángulo superior central, que nos ayudará a limpiar la pantalla
cv2.rectangle(frame,(300,0),(400,50),colorLimpiarPantalla,1)
cv2.putText(frame,'Limpiar',(320,20),6,0.6,colorLimpiarPantalla,1,cv2.LINE_AA)
cv2.putText(frame,'pantalla',(320,40),6,0.6,colorLimpiarPantalla,1,cv2.LINE_AA)

# Cuadrados dibujados en la parte superior derecha (grosor del marcador para dibujar)
cv2.rectangle(frame,(490,0),(540,50),(0,0,0),grosorPeque)
cv2.circle(frame,(515,25),3,(0,0,0),-1)
cv2.rectangle(frame,(540,0),(590,50),(0,0,0),grosorMedio)
cv2.circle(frame,(565,25),7,(0,0,0),-1)
cv2.rectangle(frame,(590,0),(640,50),(0,0,0),grosorGrande)
cv2.circle(frame,(615,25),11,(0,0,0),-1)
#-----------------------------------------------------------------------------------

Línea 47 a 50: Creamos 4 cuadrados en la parte superior izquierda, que corresponderán a los 4 colores que podremos escoger para dibujar. Su orden será: cuadrado de color amarillo, luego rosa, verde y por último celeste.

Línea 53 a 55: Creamos un rectángulo en la sección central superior de la pantalla, dentro de este se visualizará 'Limpiar Pantalla'.

Línea 58 a 63: Creamos 3 cuadrados en la parte superior derecha de la pantalla, dentro de estos se podrán visualizar círculos correspondientes a los distintos grosores a escoger.

Figura 2: Visualización de la sección superior de la pantalla (menú).

Detección del color del objeto (marcador / lápiz virtual)

Como te decía, voy a usar la tapa de un lapicero celeste para poder realizar los trazos, por lo que ahora necesitamos encontrar en frameHSV el color celeste, para ello usamos el rango que habíamos establecido en las líneas 6 y 7.

# Detección del color celeste
maskCeleste = cv2.inRange(frameHSV, celesteBajo, celesteAlto)

Figura 3: Obtención de la imagen binaria.

Como puedes ver en la figura 3 al lado izquierdo, tenemos una imagen binaria en donde la región en blanco representa la presencia del color celeste que buscamos detectar, sin embargo no solo se ha detectado la tapa sino que hay más regiones en blanco debajo de ella, entonces para mejorar la detección podemos aplicar algunas transformaciones morfológicas, veamos:

maskCeleste = cv2.erode(maskCeleste,None,iterations = 1)
maskCeleste = cv2.dilate(maskCeleste,None,iterations = 2)
maskCeleste = cv2.medianBlur(maskCeleste, 13)

En las líneas 68 y 69 estoy aplicando erosión y dilatación finalmente he aplicado cv2.medianBlur para suavizar un poco los bordes del área blanca.

Figura 4: Mejorando la imagen binaria.

Como vemos en la figura 4, la detección del color celeste ha mejorado.

Encontrando contornos

Ahora que hemos detectado el color celeste y tenemos en maskCeleste una imagen binaria en donde la región en blanco representa la presencia de dicho color, mientras en color negro la ausencia del mismo, vamos a proceder a encontrar los contornos, parra luego analizarlos de acuerdo a su área.

cnts,_ = cv2.findContours(maskCeleste, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:1]

Línea 71: Nos ayudamos de cv2.findContours para encontrar los contornos presentes en maskCeleste (puedes profundizar más esta función aquí).

NOTA: Si tienes la versión 3 de OpenCV, y obtienes un error en la línea 71, por favor prueba de este modo: _,cnts,_ = cv2.findContours(maskCeleste, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

Línea 72: Vamos a ordenar los contornos en forma descendente, y escogeremos el más grande que se almacenará en cnts.

Estableciendo un área mínima para la detección y obteniendo las coordenadas para realizar los trazos

Una vez que tenemos el contorno más grande presente dentro de la imagen vamos a realizar otro filtrado, para saber que en realidad se trata del objeto que deseemos detectar. Para ello estableceremos un área mínima, objetos menores a esta área serán descartados.

for c in cnts:
    area = cv2.contourArea(c)
    if area > 1000:
        x,y2,w,h = cv2.boundingRect(c)
        x2 = x + w//2

Línea 75 y 76: Obtenemos el área del contorno y a continuación comparamos para que solo objetos celestes que tengan un área mayor a 1000 pixeles sean tomados en cuenta. Cuando el área del contorno sea menor se asignará a x1,y1 = None, como lo podrás ver más adelante en la línea 133.

Línea 77: A través de cv2.boundingRect encontramos las coordendas, ancho y alto del rectángulo que rodee el objeto celeste. Como puedes darte cuenta he declarado y2, esto debido a que será la coordenada en y que me ayudará a dibujar los trazos.

Línea 78: Tomamos la coordenada en x y la sumamos a la mitad del ancho del rectángulo que rodea el objeto. Esto permitirá encontrar la coordenada  x2 . Ahora con la línea 77 y 78 ya tenemos las coordenadas de los puntos que nos permitirán realizar los trazos.

Seleccionando colores para dibujar

Ahora basados en las coordenadas x2,y2 podemos darle funcionalidad a nuestra aplicación para que el usuario pueda elegir de entre distintos colores, para ello usaremos un par de if.

if x1 is not None:
    if 0 < x2 < 50 and 0 < y2 < 50:
        color = colorAmarillo # Color del lápiz/marcador virtual
        grosorAmarillo = 6
        grosorRosa = 2
        grosorVerde = 2
        grosorCeleste = 2
    if 50 < x2 < 100 and 0 < y2 < 50:
        color = colorRosa # Color del lápiz/marcador virtual
        grosorAmarillo = 2
        grosorRosa = 6
        grosorVerde = 2
        grosorCeleste = 2
    if 100 < x2 < 150 and 0 < y2 < 50:
        color = colorVerde # Color del lápiz/marcador virtual
        grosorAmarillo = 2
        grosorRosa = 2
        grosorVerde = 6
        grosorCeleste = 2
    if 150 < x2 < 200 and 0 < y2 < 50:
        color = colorCeleste # Color del lápiz/marcador virtual
        grosorAmarillo = 2
        grosorRosa = 2
        grosorVerde = 2
        grosorCeleste = 6

Línea 80: Si x1 no es igual a None, es decir cuando se haya almacenado alguna coordenada en x1, se procederá con la selección de los colores y/o dibujo de los trazos.

Línea 81 a 86: Para seleccionar el color amarillo necesitaremos que el objeto se encuentre en el eje x, de 0 a 50, mientras en en y de 0 a 50, por ello hemos puesto el if de la línea 81. Cuando esto pase, la variable color tomará el colorAmarillo y para resaltar el recuadro del color seleccionado en la sección superior izquierda se actualiza grosorAmarillo = 6, mientras que los otros recuadros permanecen con grosor 2.

Línea 87 a 92: Ahora procedemos con la posible selección del color rosa, para ello el objeto debe encotnrarse en x de 50 a 100, mientras que en y de 0 a 50. Cuando esto suceda a variable color tomará el colorRosa y para resaltar el recuadro del color seleccionado en la sección superior izqueirda se actualiza grosorRosa = 6 mientras que los otros recuadros permanecerapn en grosor 2.

Línea 93 a 104: Como te pudiste haber dado cuenta, seguimos el mismo procedimiento tanto para el color verde y celeste.

Seleccionando el grosor de los trazos para dibujar

Seguiremos un procedimiento similiar, para poder seleccionar de entre los 3 tamaños de los trazos, que se ubican en la parte superior derecha.

if 490 < x2 < 540 and 0 < y2 < 50:
    grosor = 3 # Grosor del lápiz/marcador virtual
    grosorPeque = 6
    grosorMedio = 1
    grosorGrande = 1
if 540 < x2 < 590 and 0 < y2 < 50:
    grosor = 7 # Grosor del lápiz/marcador virtual
    grosorPeque = 1
    grosorMedio = 6
    grosorGrande = 1
if 590 < x2 < 640 and 0 < y2 < 50:
    grosor = 11 # Grosor del lápiz/marcador virtual
    grosorPeque = 1
    grosorMedio = 1
    grosorGrande = 6

Línea 105 a 109: Para seleccionar el grosor más pequeño, el objeto debe estar ubicado en el eje x de 490 a 540, mientras en y de 0 a 50. Cuando esto pase, la variable grosor tomará el valor de 3  y para resaltar el cuadrado del grosor seleccionado en la sección superior derecha se actualiza grosorPeque = 6, mientras que los otros recuadros permanecen con grosor 1.

Línea 110 a 114: Ahora para seeleccionar el grosor mediano, el objeto debe estar ubicado en el eje x de 540 a 590, mientras que en y de 0 a 50. Cuando esto pase, la variable grosor tomará el valor de 7  y para resaltar el cuadrado del grosor seleccionado en la sección superior derecha se actualiza grosorMedio = 6, mientras que los otros recuadros permanecen con grosor 1.

Tendremos que realizar el mismo procedimiento de la línea 115 a 119.

Seleccionando ‘Limpiar pantalla’

Para seleccionar ‘Limpiar pantalla’ bastará que el objeto se encuentre sobre dicho recuadro, veamos como hacerlo:

if 300 < x2 < 400 and 0 < y2 < 50:
    cv2.rectangle(frame,(300,0),(400,50),colorLimpiarPantalla,2)
    cv2.putText(frame,'Limpiar',(320,20),6,0.6,colorLimpiarPantalla,2,cv2.LINE_AA)
    cv2.putText(frame,'pantalla',(320,40),6,0.6,colorLimpiarPantalla,2,cv2.LINE_AA)
    imAux = np.zeros(frame.shape,dtype=np.uint8)

Línea 120: Para limpiar la pantalla, el objeto debe estar ubicado en el eje x de 300 a 4000, mientras en y de 0 a 50. Cuando esto pase, se resaltará el rectángulo y el texto, mientras que para limpliar la pantalla emplearemos la línea 124, asignando a imAux = np.zeros(frame.shape,dtype=np.uint8), ¿te diste cuenta que esta línea es similar a la línea 43?, con esto podremos limpiar los trazos que realicemos.

Dibujando los trazos

Ahora pasemos al dibujo de los trazos, según las coordenadas que presente el objeto.

        if 0 < y2 < 60 or 0 < y1 < 60 :
            imAux = imAux
        else:
            imAux = cv2.line(imAux,(x1,y1),(x2,y2),color,grosor)
    cv2.circle(frame,(x2,y2),grosor,color,3)
    x1 = x2
    y1 = y2
else:
    x1, y1 = None, None

Línea 125 a 126: He añadido estas líneas para que no se dibuje en la sección superior de la imagen, en donde se selecciona el color, el grosor del trazo, además de limpiar la pantalla.

Hasta ahora habíamos estado trabajando sobre la parte superior de la pantalla, es hora de que sedibujen los trazos sobre el resto de dicha pantalla.

Línea 127 y 128: Si el objeto no está en la sección superior, pasaremos a la línea 128, en donde dibujamos una línea que conecte las coordenadas x1,y1 con x2,y2. Aquí se usarán las variables color y grosor que habíamos usado en líneas anteriores.

Línea 129: Procedemos a dibujar un círculo, para indicar la coordenada x2,y2.

Línea 130 y 131: Acualizamos los valores x1,y1 asignándoles los valores de x2,y2, esto nos servirá para poder dibujar los trazos, conforme se vaya moviendo el objeto. Esto lo tenemos en la línea 127.

Línea 132 y 133: Como te había dicho anteriormente, si el contorno no cumple un área mínima de 100, entonces x1,y1 serán asignados con None.

Figura 5: Visualización del dibujo de trazos sobre imAux.

En la figura 5, podemos ver que con el proceso que hemos realizado hasta aquí, ya se dibujan los trazos, pero estos solo se están dibujando en imAux , es por ello que a continuación veremos el procedimiento para que se dibujen sobre los fotogramas.

Dibujando sobre el VideoStreaming

Para poder dibujar sobre los fotogramas podemos pensar en usar cv2.add, y así sumar frame eimAux, sin embargo te mostraré a continuación los resultados de realizar este proceso:

Figura 6: imAux (Izq). Resultado de aplicar: frame = cv2.add(frame,imAux). (Der)

Como puedes ver en la parte derecha de la figura 6, al aplicar frame = cv2.add(frame,imAux), los trazos no aparecen de color azul/celeste, sino que toman colores más claros, esto es porque se está sumando la intensidad de los pixeles de las imagenes. Es por esta razón que antes de aplicar cv2.add necesitamos de un par de procesos más, veamos:

    imAuxGray = cv2.cvtColor(imAux,cv2.COLOR_BGR2GRAY)
    _, th = cv2.threshold(imAuxGray,10,255,cv2.THRESH_BINARY)
    thInv = cv2.bitwise_not(th)
    frame = cv2.bitwise_and(frame,frame,mask=thInv)
    frame = cv2.add(frame,imAux)
    
    #cv2.imshow('maskCeleste', maskCeleste)
    cv2.imshow('imAux',imAux)
    cv2.imshow('frame', frame)
    
    k = cv2.waitKey(1)
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

Línea 135: Transformamos imAux de BGR a escala de grises.

Línea 136: Aplicamos umbralización simple aimAuxGray para obtener una imagen binaria (puedes visitar este post, para más información sobre la umbralización simple o simple thresholding).

Línea 137: Aplicamos el operador not, con cv2.bitwise_not a la imagen binaria.

Figura 7: Visualización de imAux, th e invTh.

Línea 138: Nos ayudaremos de cv2.bitwise_and para construir una nueva imagen, en donde esté el fotograma, y los trazos dibujados tomen color negro o valor de cero.

NOTA: Para más información sobre los operadores bit a bit puedes visitar el post Operadores BITWISE (AND-OR-NOT-XOR) con OpenCV y Python.

Línea 139: Ahora con la nueva imagen obtenida en la línea 138, podemos aplicar cv2.add y ver que resultados obtenemos.

Figura 8: Resultados de la programación realizada.

Como puedes apreciar en la figura 7, ¡ya podemos dibujar en el aire!. ¿Qué te pareció?.

Por cierto, puedes pegar un papel o cinta de otro color en la parte trasera del objeto que vas a utilizar para realizar los trazos, de este modo al voltear el objeto puedes dibujar y dejar de dibujar, ya que a ciertos momentos detectará el color y en otros no.

Programación completa

Repositorio de github.

import cv2
import numpy as np

cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)

celesteBajo = np.array([75, 185, 88], np.uint8)
celesteAlto = np.array([112, 255, 255], np.uint8)

# Colores para pintar
colorCeleste = (255,113,82)
colorAmarillo = (89,222,255)
colorRosa = (128,0,255)
colorVerde = (0,255,36)
colorLimpiarPantalla = (29,112,246) # Solo se usará para el cuadro superior de 'Limpiar Pantalla'

# Grosor de línea recuadros superior izquierda (color a dibujar)
grosorCeleste = 6
grosorAmarillo = 2
grosorRosa = 2
grosorVerde = 2

# Grosor de línea recuadros superior derecha (grosor del marcador para dibujar)
grosorPeque = 6
grosorMedio = 1
grosorGrande = 1

#--------------------- Variables para el marcador / lápiz virtual -------------------------
color = colorCeleste  # Color de entrada, y variable que asignará el color del marcador
grosor = 3 # Grosor que tendrá el marcador
#------------------------------------------------------------------------------------------

x1 = None
y1 = None
imAux = None

while True:

    ret,frame = cap.read()
    if ret==False: break

    frame = cv2.flip(frame,1)
    frameHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    if imAux is None: imAux = np.zeros(frame.shape,dtype=np.uint8)

    #------------------------ Sección Superior ------------------------------------------
    # Cuadrados dibujados en la parte superior izquierda (representan el color a dibujar)
    cv2.rectangle(frame,(0,0),(50,50),colorAmarillo,grosorAmarillo)
    cv2.rectangle(frame,(50,0),(100,50),colorRosa,grosorRosa)
    cv2.rectangle(frame,(100,0),(150,50),colorVerde,grosorVerde)
    cv2.rectangle(frame,(150,0),(200,50),colorCeleste,grosorCeleste)

    # Rectángulo superior central, que nos ayudará a limpiar la pantalla
    cv2.rectangle(frame,(300,0),(400,50),colorLimpiarPantalla,1)
    cv2.putText(frame,'Limpiar',(320,20),6,0.6,colorLimpiarPantalla,1,cv2.LINE_AA)
    cv2.putText(frame,'pantalla',(320,40),6,0.6,colorLimpiarPantalla,1,cv2.LINE_AA)

    # Cuadrados dibujados en la parte superior derecha (grosor del marcador para dibujar)
    cv2.rectangle(frame,(490,0),(540,50),(0,0,0),grosorPeque)
    cv2.circle(frame,(515,25),3,(0,0,0),-1)
    cv2.rectangle(frame,(540,0),(590,50),(0,0,0),grosorMedio)
    cv2.circle(frame,(565,25),7,(0,0,0),-1)
    cv2.rectangle(frame,(590,0),(640,50),(0,0,0),grosorGrande)
    cv2.circle(frame,(615,25),11,(0,0,0),-1)
    #-----------------------------------------------------------------------------------
    
    # Detección del color celeste
    maskCeleste = cv2.inRange(frameHSV, celesteBajo, celesteAlto)
    maskCeleste = cv2.erode(maskCeleste,None,iterations = 1)
    maskCeleste = cv2.dilate(maskCeleste,None,iterations = 2)
    maskCeleste = cv2.medianBlur(maskCeleste, 13)
    cnts,_ = cv2.findContours(maskCeleste, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:1]

    for c in cnts:
        area = cv2.contourArea(c)
        if area > 1000:
            x,y2,w,h = cv2.boundingRect(c)
            x2 = x + w//2
            
            if x1 is not None:
                if 0 < x2 < 50 and 0 < y2 < 50:
                    color = colorAmarillo # Color del lápiz/marcador virtual
                    grosorAmarillo = 6
                    grosorRosa = 2
                    grosorVerde = 2
                    grosorCeleste = 2
                if 50 < x2 < 100 and 0 < y2 < 50:
                    color = colorRosa # Color del lápiz/marcador virtual
                    grosorAmarillo = 2
                    grosorRosa = 6
                    grosorVerde = 2
                    grosorCeleste = 2
                if 100 < x2 < 150 and 0 < y2 < 50:
                    color = colorVerde # Color del lápiz/marcador virtual
                    grosorAmarillo = 2
                    grosorRosa = 2
                    grosorVerde = 6
                    grosorCeleste = 2
                if 150 < x2 < 200 and 0 < y2 < 50:
                    color = colorCeleste # Color del lápiz/marcador virtual
                    grosorAmarillo = 2
                    grosorRosa = 2
                    grosorVerde = 2
                    grosorCeleste = 6
                if 490 < x2 < 540 and 0 < y2 < 50:
                    grosor = 3 # Grosor del lápiz/marcador virtual
                    grosorPeque = 6
                    grosorMedio = 1
                    grosorGrande = 1
                if 540 < x2 < 590 and 0 < y2 < 50:
                    grosor = 7 # Grosor del lápiz/marcador virtual
                    grosorPeque = 1
                    grosorMedio = 6
                    grosorGrande = 1
                if 590 < x2 < 640 and 0 < y2 < 50:
                    grosor = 11 # Grosor del lápiz/marcador virtual
                    grosorPeque = 1
                    grosorMedio = 1
                    grosorGrande = 6
                if 300 < x2 < 400 and 0 < y2 < 50:
                    cv2.rectangle(frame,(300,0),(400,50),colorLimpiarPantalla,2)
                    cv2.putText(frame,'Limpiar',(320,20),6,0.6,colorLimpiarPantalla,2,cv2.LINE_AA)
                    cv2.putText(frame,'pantalla',(320,40),6,0.6,colorLimpiarPantalla,2,cv2.LINE_AA)
                    imAux = np.zeros(frame.shape,dtype=np.uint8)
                if 0 < y2 < 60 or 0 < y1 < 60 :
                    imAux = imAux
                else:
                    imAux = cv2.line(imAux,(x1,y1),(x2,y2),color,grosor)
            cv2.circle(frame,(x2,y2),grosor,color,3)
            x1 = x2
            y1 = y2
        else:
            x1, y1 = None, None
    
    imAuxGray = cv2.cvtColor(imAux,cv2.COLOR_BGR2GRAY)
    _, th = cv2.threshold(imAuxGray,10,255,cv2.THRESH_BINARY)
    thInv = cv2.bitwise_not(th)
    frame = cv2.bitwise_and(frame,frame,mask=thInv)
    frame = cv2.add(frame,imAux)
    
    #cv2.imshow('maskCeleste', maskCeleste)
    cv2.imshow('imAux',imAux)
    cv2.imshow('frame', frame)
    
    k = cv2.waitKey(1)
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

REFERENCIAS: