如果在空中挥挥笔,就可以在屏幕上画出来,这不是很酷吗?如果我们不使用任何特殊的硬件来实现这一点,那将会更加有趣,仅仅是简单的计算机视觉就可以做到,事实上,我们甚至不需要使用机器学习或深度学习来实现这一点。

所以在这篇文章中,你将学习如何创建自己的虚拟笔和虚拟橡皮擦。整个应用将从根本上建立在轮廓检测的基础上。你可以把轮廓看作是有相同颜色或强度的闭合曲线,它就像一个blob,你可以在这里读到更多关于轮廓的内容。

1.它是如何工作的

我们将如何实现这一目标,首先,我们将使用颜色掩膜来获得目标彩色笔的二值掩膜,(我将使用蓝色的记号笔作为虚拟笔),然后我们将使用轮廓检测探测和跟踪笔在屏幕上的位置。然后将前一帧的x,y坐标与当前帧的x,y坐标相连接,这样就拥有一个虚拟笔了。

2.结构

当然,现在有预处理要做,还有一些其他的功能要添加,这里是应用程序每一步的分解。

  • Step 1:找到目标对象的颜色范围并保存它。
  • Step 2:应用正确的形态学运算来降低视频中的噪声
  • Step 3:利用轮廓检测对有色物体进行检测和跟踪。
  • Step 4:找到要在屏幕上绘制的对象的x,y坐标。
  • Step 5:增加雨刷功能,擦拭整个屏幕。
  • Step 6:添加橡皮擦功能,以擦除部分图纸。

我已经以这样一种方式设计了这个应用程序的管道,它可以很容易地为其他项目重用,例如,如果你要做涉及跟踪一个彩色的对象的任何项目,然后你可以使用步骤1-3。此外,当您自己运行这个程序时,这个分解使得调试步骤变得更加容易,因为您将确切地知道哪一步出错了。每个步骤都可以独立运行。

注意,我们在步骤4准备好虚拟笔,所以我在步骤5-6添加了一些更多的功能。例如在步骤5中有一个可以像笔一样擦去屏幕上的笔印的虚拟雨刷,然后在步骤6中,我们将添加一个切换器,允许您切换笔与橡皮擦。让我们开始吧。

完整代码

# 链接:https://pan.baidu.com/s/1cE80vRIybI6dYGjlWFTopA 提取码:123a

# 创建一个虚拟笔和橡皮擦
#!/usr/bin/env python
# coding: utf-8
import cv2
import numpy as np
import time

# 第一步:找到目标笔的颜色范围并保存

# 进入轨迹栏函数的一个必需的回调方法。
def nothing(x):
    pass

# 初始化网络摄像头
cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

# 创建一个名为Trackbars的窗口。
cv2.namedWindow("Trackbars")

# 现在创建6个滚动条,将控制H,S和V通道的上下范围。
# 参数是这样的:滚动条名称,窗口名称,范围,回调函数。
# 对于Hue,范围是0-179,对于S,V范围是0-255。
cv2.createTrackbar("L - H", "Trackbars", 0, 179, nothing)
cv2.createTrackbar("L - S", "Trackbars", 0, 255, nothing)
cv2.createTrackbar("L - V", "Trackbars", 0, 255, nothing)
cv2.createTrackbar("U - H", "Trackbars", 179, 179, nothing)
cv2.createTrackbar("U - S", "Trackbars", 255, 255, nothing)
cv2.createTrackbar("U - V", "Trackbars", 255, 255, nothing)
 
while True:
    
    # 开始一帧一帧地读取摄像头。
    ret, frame = cap.read()
    if not ret:
        break
    # 水平翻转帧图片(不要求)
    frame = cv2.flip( frame, 1 ) 
    
    # 将BGR图像转换为HSV图像。
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 当用户更改滚动条时,实时获取它们的新值
    l_h = cv2.getTrackbarPos("L - H", "Trackbars")
    l_s = cv2.getTrackbarPos("L - S", "Trackbars")
    l_v = cv2.getTrackbarPos("L - V", "Trackbars")
    u_h = cv2.getTrackbarPos("U - H", "Trackbars")
    u_s = cv2.getTrackbarPos("U - S", "Trackbars")
    u_v = cv2.getTrackbarPos("U - V", "Trackbars")
 
    # 根据轨迹条选择的值设置HSV的上下范围
    lower_range = np.array([l_h, l_s, l_v])
    upper_range = np.array([u_h, u_s, u_v])
    
    # 过滤图像并得到二值掩膜,其中白色代表你的目标颜色
    mask = cv2.inRange(hsv, lower_range, upper_range)
 
    # 你也可以可视化目标颜色的实部(可选)
    res = cv2.bitwise_and(frame, frame, mask=mask)
    
    # 将二值掩膜转换为3通道图像,这只是为了我们可以将它与其他的堆叠
    mask_3 = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    
    # 将掩码、原始帧和过滤后的结果堆叠起来
    stacked = np.hstack((mask_3,frame,res))
    
    # 以40%的尺寸展示这个堆叠的图像。
    cv2.imshow('Trackbars',cv2.resize(stacked,None,fx=0.4,fy=0.4))
    
    # 如果用户按ESC然后退出程序
    key = cv2.waitKey(1)
    if key == 27:
        break
    
    # 如果用户按' s ',则输出该数组。
    if key == ord('s'):
        
        thearray = [[l_h,l_s,l_v],[u_h, u_s, u_v]]
        print(thearray)
        
        # 也将这个数组保存为penval.npy
        np.save('penval',thearray)
        break
    
# 释放摄像头对象,关闭所有窗户。
cap.release()
cv2.destroyAllWindows()


# 第二步:最大化检测掩膜,去除噪声 

# 这个变量决定了我们是想从内存中加载颜色范围还是使用这里定义的颜色范围。
load_from_disk = True

# 如果为真,则从内存中加载颜色范围
if load_from_disk:
    penval = np.load('penval.npy')

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

# 创建一个用于形态操作的5x5核
kernel = np.ones((5,5),np.uint8)

while(1):
    
    ret, frame = cap.read()
    if not ret:
        break
        
    frame = cv2.flip( frame, 1 )

    # 将BGR转换为HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 如果你从内存中读取,那么就从那里加载上下范围
    if load_from_disk:
            lower_range = penval[0]
            upper_range = penval[1]
            
    # 否则,请定义您自己的上下范围。
    else:             
       lower_range  = np.array([26,80,147])
       upper_range = np.array([81,255,255])
    
    mask = cv2.inRange(hsv, lower_range, upper_range)
    
    # 执行形态操作以去除噪声。
    mask = cv2.erode(mask,kernel,iterations = 1)
    mask = cv2.dilate(mask,kernel,iterations = 2)

    res = cv2.bitwise_and(frame,frame, mask= mask)

    mask_3 = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    
    # 堆叠所有帧并显示它
    stacked = np.hstack((mask_3,frame,res))
    cv2.imshow('Trackbars',cv2.resize(stacked,None,fx=0.4,fy=0.4))
    
    k = cv2.waitKey(5) & 0xFF
    if k == 27:
        break

cv2.destroyAllWindows()
cap.release()


# 第三步:追踪目标笔

# 这个变量决定了我们是要从内存中加载颜色范围还是使用在笔记本中定义的颜色范围。
load_from_disk = True

# 如果为真,则从内存中加载颜色范围
if load_from_disk:
    penval = np.load('penval.npy')

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

# 形态运算核
kernel = np.ones((5,5),np.uint8)

# 将窗口设置为自动大小,这样我们就可以全屏观看了。
cv2.namedWindow('image', cv2.WINDOW_NORMAL)

# 该阈值用于滤波噪声,轮廓面积必须大于该阈值才能符合实际轮廓。
noiseth = 500

while(1):
    
    _, frame = cap.read()
    frame = cv2.flip( frame, 1 )

    # 将BGR转换为HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 如果你从内存中读取,那么就从那里加载上下范围
    if load_from_disk:
            lower_range = penval[0]
            upper_range = penval[1]
            
    # 否则,请定义自定义值。
    else:             
       lower_range  = np.array([26,80,147])
       upper_range = np.array([81,255,255])
    
    mask = cv2.inRange(hsv, lower_range, upper_range)
    
    # 执行形态操作以去除噪声
    mask = cv2.erode(mask,kernel,iterations = 1)
    mask = cv2.dilate(mask,kernel,iterations = 2)
    
    # 找到轮廓。
    contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL,
                                           cv2.CHAIN_APPROX_SIMPLE)
    
    # 确保有一个轮廓存在,并确保其大小大于噪声阈值。
    if contours and cv2.contourArea(max(contours, 
                                        key = cv2.contourArea)) > noiseth:
        
        # 获取面积最大的轮廓
        c = max(contours, key = cv2.contourArea)
        
        # 得到轮廓周围的边界框坐标
        x,y,w,h = cv2.boundingRect(c)
        
        # 绘制边界框
        cv2.rectangle(frame,(x,y),(x+w,y+h),(0,25,255),2)        

    cv2.imshow('image',frame)
    
    k = cv2.waitKey(5) & 0xFF
    if k == 27:
        break

cv2.destroyAllWindows()
cap.release()


# 第四步:用笔画画

load_from_disk = True
if load_from_disk:
    penval = np.load('penval.npy')

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

kernel = np.ones((5,5),np.uint8)

# 初始化我们将绘制的画布
canvas = None

# 初始化 x1,y1 点
x1,y1=0,0

# 噪声阈值
noiseth = 800

while(1):
    _, frame = cap.read()
    frame = cv2.flip( frame, 1 )
    
    # 将画布初始化为与帧图像大小相同的黑色图像。
    if canvas is None:
        canvas = np.zeros_like(frame)

    # 将BGR转换为HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 如果你从内存中读取,那么就从那里加载上下范围
    if load_from_disk:
            lower_range = penval[0]
            upper_range = penval[1]
            
    # 否则,请定义自定义值。
    else:             
       lower_range  = np.array([26,80,147])
       upper_range = np.array([81,255,255])
    
    mask = cv2.inRange(hsv, lower_range, upper_range)
    
    # 执行形态操作以去除噪声
    mask = cv2.erode(mask,kernel,iterations = 1)
    mask = cv2.dilate(mask,kernel,iterations = 2)
    
    # 找到轮廓
    contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # 确保有一个轮廓存在,而且它的尺寸大于噪声阈值。
    if contours and cv2.contourArea(max(contours, key = cv2.contourArea)) > noiseth:
                
        c = max(contours, key = cv2.contourArea)    
        x2,y2,w,h = cv2.boundingRect(c)
        
        # 如果之前没有点,那么将检测到的x2,y2坐标保存为x1,y1。
        # 当我们第一次写作或当笔从视野中消失时再次写作时,这是True。
        if x1 == 0 and y1 == 0:
            x1,y1= x2,y2
            
        else:
            # 在画布上画一条线
            canvas = cv2.line(canvas, (x1,y1),(x2,y2), [255,0,0], 4)
        
        # 画完直线后,新的点就变成了以前的点。
        x1,y1= x2,y2

    else:
        # 如果没有检测到轮廓,则令x1,y1 = 0
        x1,y1 =0,0
    
    # 合并画布和帧图像。
    frame = cv2.add(frame,canvas)
    
    # 可以选择堆叠两个帧并显示它。
    stacked = np.hstack((canvas,frame))
    cv2.imshow('Trackbars',cv2.resize(stacked,None,fx=0.6,fy=0.6))

    k = cv2.waitKey(1) & 0xFF
    if k == 27:
        break
        
    # 当按下c时,清除画布
    if k == ord('c'):
        canvas = None

cv2.destroyAllWindows()
cap.release()


# 步骤5:添加一个雨刷

load_from_disk = True
if load_from_disk:
    penval = np.load('penval.npy')

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

kernel = np.ones((5,5),np.uint8)

# 使窗口大小可调
cv2.namedWindow('image', cv2.WINDOW_NORMAL)

# 这就是我们要在上面作画的画布
canvas=None

# 初始化x1,y1点
x1,y1=0,0

# 噪声阈值
noiseth = 800

# 雨刷的阈值,轮廓的尺寸必须大于我们清除画布的尺寸
wiper_thresh = 40000

# 一个变量,它告诉我们什么时候清除画布,如果它是True,我们就清除画布
clear = False

while(1):
    _ , frame = cap.read()
    frame = cv2.flip( frame, 1 )
    
    # 将画布初始化为黑色图像
    if canvas is None:
        canvas = np.zeros_like(frame)

    # 将BGR转换为HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 如果你从内存中读取,那么就从那里加载上下范围
    if load_from_disk:
            lower_range = penval[0]
            upper_range = penval[1]
            
    # 否则,请定义自定义值。
    else:             
       lower_range  = np.array([26,80,147])
       upper_range = np.array([81,255,255])
    
    mask = cv2.inRange(hsv, lower_range, upper_range)
    
    # 执行形态操作以去除噪声
    mask = cv2.erode(mask,kernel,iterations = 1)
    mask = cv2.dilate(mask,kernel,iterations = 2)
    
    # 找到轮廓。
    contours, hierarchy = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    
    # 确保有一个轮廓存在,而且它的尺寸大于噪声阈值。
    if contours and cv2.contourArea(max(contours, key = cv2.contourArea)) > noiseth:
                
        c = max(contours, key = cv2.contourArea)    
        x2,y2,w,h = cv2.boundingRect(c)
        
        # 得到轮廓的面积
        area = cv2.contourArea(c)
        
        # 如果之前没有点,那么将检测到的x2,y2坐标保存为x1,y1。
        if x1 == 0 and y1 == 0:
            x1,y1= x2,y2
            
        else:
            # 在画布上画一条线
            canvas = cv2.line(canvas, (x1,y1),(x2,y2), [255,0,0], 5)
        
        # 画完直线后,新的点就变成了以前的点。
        x1,y1= x2,y2
        
        # 现在,如果面积大于雨刷阈值,则将clear变量设置为True并警告用户。
        if area > wiper_thresh:
           cv2.putText(canvas,'Clearing Canvas',(100,200), cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 5, cv2.LINE_AA)
           clear = True 

    else:
        # 如果没有检测到轮廓,则令x1,y1 = 0
        x1,y1 =0,0
    
   
    # 现在这段代码只是为了平滑绘图。(可选)
    _ ,mask = cv2.threshold(cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY), 20, 255, cv2.THRESH_BINARY)
    foreground = cv2.bitwise_and(canvas, canvas, mask = mask)
    background = cv2.bitwise_and(frame, frame, mask = cv2.bitwise_not(mask))
    frame = cv2.add(foreground,background)

    cv2.imshow('image',frame)
    
    k = cv2.waitKey(5) & 0xFF
    if k == 27:
        break
    
    # 如果Clear变量为true,则在1秒后清除画布
    if clear == True:
        
        time.sleep(1)
        canvas = None
        
        # 然后设clear为false
        clear = False
        
cv2.destroyAllWindows()
cap.release()


# 步骤6:添加橡皮擦功能

load_from_disk = True
if load_from_disk:
    penval = np.load('penval.npy')

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

# 加载这两张图片,并将其调整为相同的大小。
pen_img = cv2.resize(cv2.imread('pen.png',1), (50, 50))
eraser_img = cv2.resize(cv2.imread('eraser.jpg',1), (50, 50))

kernel = np.ones((5,5),np.uint8)

# 使窗口大小可调
cv2.namedWindow('image', cv2.WINDOW_NORMAL)

# 这就是我们要在上面作画的画布
canvas = None

# 创建一个背景消除器对象
backgroundobject = cv2.createBackgroundSubtractorMOG2( detectShadows = False )

# 这个阈值决定了背景中断的数量。
background_threshold = 600

# 一个变量,它告诉你是使用钢笔还是橡皮擦。
switch = 'Pen'

# 使用这个变量,我们将监视上一次切换之间的时间。
last_switch = time.time()

# 初始化x1,y1
x1,y1=0,0

# 噪声阈值
noiseth = 800

# 雨刷的阈值,轮廓的尺寸必须大于这个,我们才能清除画布
wiper_thresh = 40000

# 一个变量,告诉何时清除画布
clear = False

while(1):
    _, frame = cap.read()
    frame = cv2.flip(frame, 1 )
    
    # 将画布初始化为黑色图像
    if canvas is None:
        canvas = np.zeros_like(frame)
        
    # 在帧图像的左上方应用背景消除法   
    top_left = frame[0: 50, 0: 50]
    fgmask = backgroundobject.apply(top_left)
    
    # 注意白色像素的数量,这是破坏的程度。
    switch_thresh = np.sum(fgmask==255)
    
    # 如果中断大于背景阈值,并且在上次切换后已经有一段时间了,那么您可以更改对象类型。
    if switch_thresh > background_threshold  and (time.time() - last_switch) > 1:
        
        # 保存交换机时间
        last_switch = time.time()
        
        if switch == 'Pen':
            switch = 'Eraser'
        else:
            switch = 'Pen'

    # 将BGR转换为HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    
    # 如果你从内存中读取,那么就从那里加载上下范围
    if load_from_disk:
            lower_range = penval[0]
            upper_range = penval[1]
            
    # 否则自定义
    else:             
       lower_range  = np.array([26,80,147])
       upper_range = np.array([81,255,255])
    
    mask = cv2.inRange(hsv, lower_range, upper_range)
    
    # 执行形态操作以去除噪声
    mask = cv2.erode(mask,kernel,iterations = 1)
    mask = cv2.dilate(mask,kernel,iterations = 2)
    
    # 发现轮廓
    contours, hierarchy = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    
    # 确保有一个轮廓存在,而且它的尺寸大于噪声阈值。
    if contours and cv2.contourArea(max(contours, key = cv2.contourArea)) > noiseth:
                
        c = max(contours, key = cv2.contourArea)    
        x2,y2,w,h = cv2.boundingRect(c)
        
        # 得到轮廓的面积
        area = cv2.contourArea(c)
        
        # 如果之前没有点,那么将检测到的x2,y2坐标保存为x1,y1。
        if x1 == 0 and y1 == 0:
            x1,y1= x2,y2
            
        else:
            
            if switch == 'Pen':
                # 在画布上画一条线
                canvas = cv2.line(canvas, (x1,y1), (x2,y2), [255,0,0], 5)
                
            else:
                cv2.circle(canvas, (x2, y2), 20, (0,0,0), -1)
            
            
        
        # 画完直线后,新的点就变成了以前的点。
        x1,y1= x2,y2
        
        # 现在,如果面积大于雨刷阈值,那么将clear变量设置为True
        if area > wiper_thresh:
           cv2.putText(canvas,'Clearing Canvas',(0,200), cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 1, cv2.LINE_AA)
           clear = True 

    else:
        # 如果没有检测到轮廓,则令x1,y1 = 0
        x1,y1 =0,0
    
   
    # 现在这段代码只是为了平滑绘图。(可选)
    _,mask = cv2.threshold(cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY), 20, 255, cv2.THRESH_BINARY)
    foreground = cv2.bitwise_and(canvas, canvas, mask = mask)
    background = cv2.bitwise_and(frame, frame, mask = cv2.bitwise_not(mask))
    frame = cv2.add(foreground,background)

    
    # 切换图像取决于我们使用什么,钢笔或橡皮擦。
    if switch != 'Pen':
        cv2.circle(frame, (x1, y1), 20, (255,255,255), -1)
        frame[0: 50, 0: 50] = eraser_img
    else:
        frame[0: 50, 0: 50] = pen_img

    
    cv2.imshow('image',frame)

    k = cv2.waitKey(5) & 0xFF
    if k == 27:
        break
    
    # 如果Clear变量为true,则在1秒后清除画布
    if clear == True:
        
        time.sleep(1)
        canvas = None
        
        # 然后设clear为false
        clear = False
        
cv2.destroyAllWindows()
cap.release()

代码解析

  • Step 1:找到目标对象的颜色范围并保存它。
    首先,我们必须为我们的目标颜色对象找到一个合适的颜色范围,这个范围将在cv2.inrange()函数中使用,以过滤出我们的对象。我们还将数组保存为.npy文件,以便以后访问它。
    由于我们试图进行颜色检测,我们将把RGB(或OpenCV中的BGR)格式的图像转换为HSV(色相,饱和度,值)颜色格式,因为它更容易在HSV模型中操作颜色。
    使用滚动条来调整图像的色调、饱和度和值。调整滚动条,直到只有目标对象可见,其余部分为黑色。
  • Step 2:最大限度的检测掩模和去除噪声
    你不一定在前面的步骤中得到一个完美的掩膜,它可以有一些噪声,比如图像中的白点,我们可以在这一步中用形态学操作去除这些噪声。
    使用一个名为load_from_disk的变量来决定我是否想要从磁盘加载颜色范围或者我想要使用一些自定义值。
    执行了1次腐蚀迭代和2次膨胀迭代,所有这些都使用了5×5内核。现在需要注意的是,这些迭代次数和核大小是特定于我的目标对象和我所实验的地方的光照条件的。对于您来说,这些值可能适合您,也可能不适合您,最好调整这些值,以便获得最佳结果。
    现在,我首先进行侵蚀,以消除那些小白点,然后扩大,以扩大我的目标对象。
    即使你仍然有一些白噪声,这没关系,在接下来的部分,我们可以避免这些小噪音,但要确保你的目标对象的掩膜是清晰可见的。
  • Step 3:跟踪目标笔
    现在我们已经有了一个像样的掩膜,可以使用轮廓检测来检测钢笔,将在笔对象周围绘制一个边界框,以确保它被检测到。
  • Step 4:用钢笔画画
    现在,一切都设置好了,我们可以很容易地跟踪我们的目标对象,是时候使用这个对象在屏幕上虚拟地绘制了。
    现在我们只需要做的是使用cv2.boundingRect()函数从上一帧(F-1)返回的x,y位置,并将它与新帧(F)中的对象的x,y坐标连接。通过这两个点,我们画一条线,我们在摄像头的每一帧都这样做,这样我们就可以看到用钢笔实时绘图。
    注意:我们将在黑色画布上绘图,然后将画布与帧图像合并。这是因为我们每次迭代都会得到一个新的帧,所以我们不能在实际的帧上绘制。
  • Step 5:增加雨刷功能
    我们有一个工作的虚拟笔,当用户按下c按钮时,我们也会清除或擦除屏幕,现在让我们也自动擦除这部分。一个简单的方法是检测目标物体是否离摄像机太近,如果太近我们就清除屏幕。
    轮廓的尺寸随着它靠近相机而增加,所以我们可以监控轮廓的尺寸来实现这一点。
    我们将做的另一件事是,我们还将警告用户,我们将在几秒钟内清除屏幕,以便他/她可以将对象从帧图像中取出。
    我用4行代码替换了frame=cv2.add(frame,canvas),这可以让你绘制更平滑的线,这些线只是用彩色线部分替换了帧图像部分,而不是直接添加以避免白色效果。这不是必需的,但看起来不错。
  • Step 6:添加橡皮擦功能,以擦除部分图纸。
    现在我们已经完成了钢笔和雨刷,是时候添加橡皮擦功能了。我想要的很简单,当用户切换到橡皮擦时,它会擦除画笔绘制的部分而不是绘图。这很容易做到,你只需要在画布上用黑色来画橡皮擦就可以了。通过绘制黑色部分,在合并期间恢复到原来的部分,所以它就像一个橡皮擦。橡皮擦功能的真正编码部分是如何在笔和橡皮擦之间进行切换,当然最简单的方法是使用键盘按钮,但我们想要一些更酷的东西。
    所以我们要做的是,当有人把手放在屏幕左上角时,执行这个切换。我们将使用背景消除法来监控那个区域,这样我们就能知道什么时候会有一些干扰。这就像按一个虚拟的按钮。

注意:我所选择的所有这些不同阈值都取决于您的环境,所以请首先调整它们,而不是用我这里的阈值。

你可以将步骤1-3用于任何需要通过颜色检测对象的应用程序。我希望你们中的一些人尝试扩展这个应用程序,也许可以构建一些更酷的东西。