opencv c++虚拟画家

c++ opencv 虚拟画家如何实现橡皮擦功能

要求:
(1)用opencv调用电脑摄像头识别到笔以及笔尖
(2)笔在屏幕上移动可以在屏幕上留下相应的画痕
(3)可以实现橡皮擦功能

代码

我写到了第二部但第三步一直想不出来

#include <opencv2/opencv.hpp>
#include <iostream>
#include<opencv2/highgui.hpp>
#include<opencv2/objdetect.hpp>
using namespace std;
using namespace cv;

Mat img;

vector<vector<int>> newPoints;
vector<vector<int>> myColors{ {61,19,0,83,252,204},//green
                              {124,48,117,143,170,255}};//purple

vector<Scalar> myColorValues{ {0,255,0},{255,0,255} };


Point getContours(Mat imgDil)
{
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    
    //找轮廓
    findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    /*drawContours(img, contours, -1, Scalar(0, 0, 255), 2);*/

    Point myPoint(0, 0);
    for (int i = 0;i < contours.size();i++)
    {
        //面积
        int area = contourArea(contours[i]);
        cout << area << endl;

        vector<vector<Point>>conPoly(contours.size());
        vector<Rect>boundRect(contours.size());
        string objectType;
        if (area > 1000)
        {
            //找出边界框顶点

            float peri = arcLength(contours[i], true);
            approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);



            
            cout << conPoly[i].size() << endl;

            //边界矩形
            boundRect[i] = boundingRect(conPoly[i]);
            myPoint.x = boundRect[i].x + boundRect[i].width / 2;
            myPoint.y = boundRect[i].y;
            //做出轮廓
            drawContours(img, conPoly, i, Scalar(0, 0, 255), 2);
            rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 3);

        }

    }
    return myPoint;
}


vector<vector<int>> findColor(Mat img)
{
    Mat imgHSV;
    cvtColor(img, imgHSV, COLOR_BGR2HSV);

    for (int i = 0;i < myColors.size();i++)
    {
        //bsv
        Scalar lower(myColors[i][0], myColors[i][1], myColors[i][2]);
        Scalar upper(myColors[i][3], myColors[i][4], myColors[i][5]);
        
        //实现二值化
        Mat mask;
        inRange(imgHSV, lower, upper, mask);
        //imshow(to_string(i), mask);

        Point myPoint = getContours(mask);
        
        //使newPoints具有mypoint性质
        if (myPoint.x != 0 || myPoint.y != 0) {
            newPoints.push_back({ myPoint.x,myPoint.y,i });
        }
    }
    return newPoints;
}

void drawOnCanvas(vector<vector<int>> newPoints,vector<Scalar> myColorValues)
{
    for (int i = 0;i < newPoints.size();i++)
    {
        //调用圆函数绘制
        circle(img, Point(newPoints[i][0], newPoints[i][1]), 10, myColorValues[newPoints[i][2]], FILLED);
    
    }
}

void main()
{
    VideoCapture cap(0);

    while (true)
    {
        cap.read(img);

        newPoints = findColor(img);
        drawOnCanvas(newPoints,myColorValues);
            imshow("image", img);
        waitKey(1);
    }

    
}

运行结果及报错内容
我的解答思路和尝试过的方法
我想要达到的结果

提供参考实例【OpenCV进阶(4)使用OpenCV创建虚拟钢笔和橡皮擦】,链接:https://blog.csdn.net/weixin_43229348/article/details/120504748

https://blog.csdn.net/weixin_43229348/article/details/120504748

# 链接: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()


详细代码,望采纳:


//#include <opencv2/imgcodecs.hpp>
//#include <opencv2/highgui.hpp>
//#include <opencv2/imgproc.hpp>
#include <iostream>
#include <opencv2/opencv.hpp>
 
//项目一:虚拟画笔
using namespace cv;
using namespace std;
 
        Virtual Painter        //
 
Mat img;
vector<vector<int>> newPoints;
 
    //    hmin, smin, vmin, hmax, smax, vmax
vector<vector<int>> myColors{ {169,183,0,179,255,255},        // Red
                                {32,83,53,79,255,255},            //    Green
                                {106,174,0,121,255,255} };        //    Blue
 
vector<Scalar> myColorValues{ {0,0,255},        // Red
                                {0,255,0},            //    Green
                                {255,0,0} };        //    Blue
 
Point getContours(Mat imgDil) {
 
    vector<vector<Point>> contours;        //轮廓组
    vector<Vec4i> hierarchy;            // 向量内每个元素包含了4个int型的 向量
 
    findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);        //检测轮廓
    //    参数二:输入的轮廓组    参数三:画第几条轮廓(-1为负数表示全画)        参数五:线宽
    //    参数四:检测轮廓的方法(这里是只检测外轮廓))    参数五:表示一条轮廓的方法(这里是只存储水平,垂直,对角直线的起始点)
    //drawContours(img, contours, -1, Scalar(255, 0, 255), 2);                            //画出轮廓
 
    vector<vector<Point>> conPoly(contours.size());
    vector<Rect> boundRect(contours.size());
 
    Point myPoint(0, 0);
 
    for (int i = 0; i < contours.size(); i++)
    {
        int area = contourArea(contours[i]);        //轮廓面积
        //cout << area << endl;
        //string objectType;
 
        //    通过面积大小过滤(去噪)
        if (area > 1000)
        {
            float peri = arcLength(contours[i], true);        //弧长(轮廓周长) (参数一:图像轮廓;参数二:是否闭合)
            approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);                    //对图像轮廓点 进行 多边形拟合
            //参数一/二:输入/输出的点集    参数三:指定精度    参数四:是否闭合
 
            //cout << conPoly[i].size() << endl;
            boundRect[i] = boundingRect(contours[i]);        //计算轮廓的 垂直边界最小矩形
 
            myPoint.x = boundRect[i].x + boundRect[i].width / 2;
            myPoint.y = boundRect[i].y;
 
            //drawContours(img, conPoly, i, Scalar(255, 0, 255), 2);                        //画出轮廓
            //    参数二:输入的轮廓组    参数三:画第i条轮廓(负数表示全画)        参数五:线宽
            //rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);
        }
    }
    return myPoint;
}
 
vector<vector<int>> findColor(Mat img) {
 
    Mat imgHSV;
    cvtColor(img, imgHSV, COLOR_BGR2HSV);
 
    for (int i = 0; i < myColors.size(); i++) {
 
        Scalar lower(myColors[i][0], myColors[i][1], myColors[i][2]);
        Scalar upper(myColors[i][3], myColors[i][4], myColors[i][5]);
        Mat mask;
        inRange(imgHSV, lower, upper, mask);
        //imshow(to_string(i), mask);
        Point myPoint = getContours(mask);
 
        if (myPoint.x != 0 && myPoint.y != 0) {
            newPoints.push_back({ myPoint.x, myPoint.y, i });
        }
    }
    return newPoints;
}
 
void drawOnCanvas(vector<vector<int>> newPoints, vector<Scalar> myColorValues) {
    for (int i = 0; i < newPoints.size(); i++) {
        if (i == 0) {
            circle(img, Point(newPoints[i][0], newPoints[i][1]), 5, Scalar(myColorValues[newPoints[i][2]]), FILLED);
        }
        else if (i > 0) {
            line(img, Point(newPoints[i-1][0], newPoints[i-1][1]), Point(newPoints[i][0], newPoints[i][1]), myColorValues[newPoints[i][2]], 5);
        }
    }
}
 
void main() {
 
    VideoCapture cap(0);
 
    while (true) {
 
        cap.read(img);
        flip(img, img, 1);
 
        newPoints = findColor(img);
        drawOnCanvas(newPoints, myColorValues);
 
        imshow("Image", img);
        int c = waitKey(1);
        if (c == 27) {            //按 esc 退出应用程序
            break;
        }
        else if (c == 32) {        //按 空格 清屏
            newPoints = {};
        }
    }
 
}