基于OpenCV隔空画笔实战(代码可直接运行)

2023-11-21 22:30

本文主要是介绍基于OpenCV隔空画笔实战(代码可直接运行),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基于OpenCV的隔空画笔

演示效果链接:基于OpenCV的隔空画笔

一、项目背景

伴随着人工智能时代与5G技术的来临,许多技术得到了空前的发展。在之前疫情的背景下,让人们更加认识到了线上虚拟技术的强大。
对于喜欢美术的用户来说,在使用各种颜色的实体画笔绘画时,经常会需要用到大量的画笔和各种不同的颜料,在一套画笔中,画笔的数量众多,而且由于使用次数的增加会导致颜色出现偏差,而且在纸上进行绘画时,对于大量的颜料选择中,使用画笔绘画起来需要使用颜料版进行调色,并且需要用水来进行去除画笔上的颜色,在调色和去掉颜色的时候是非常麻烦和不方便的。比如,在纸上进行绘画,由于颜料在纸上需要一段时间才能固定好颜色,而且用户需要手中拿着大量的画笔进行绘制,这是对于创作一幅画时候最为麻烦的外界影响,因此,当前需要使用一种可以画出固定颜色并且不需要使用颜料进行调色和水来进行去除颜色的画笔。
对于线上网课教学和在教室中使用PPT进行教学的老师来说,由于长期使用粉笔在黑板上进行板书的习惯,在使用屏幕中的PPT教学时候使用鼠标进行写板书时候有着极大的不方便性和操作起来很麻烦,不能将PPT投影教学与传统黑板板书的优点结合,当前就需要一种画笔可以像粉笔一样实现在PPT投影中使用。

二、实现目标

对于喜欢美术的用户,可以通过画笔在电脑画面上进行作画并且不需要其他特定的电子画笔,只需要生活中的带颜色的画笔便可以直接在电脑上绘画。
对于线上教学和使用PPT投影教学的老师,可以通过画笔直接在电脑面前进行板书,不需要使用像鼠标和触屏版坐在电脑旁进行操作。

三、功能简介

颜色选择器
在这里插入图片描述
图1是基于图像中画笔的颜色进行选择的步骤示意图。在步骤S1中,获取当前图像,将图像中的颜色由BGR转化为HSV。在步骤S2中,使用inRange()函数获取图像中的进行颜色选择,分别选择出不同画笔的不同颜色。在步骤S3中,通过S2中选择出来的颜色,进行与之匹配的颜色保存在虚拟画笔中。
在这里插入图片描述
图2是基于图像中画笔位置而画出不同颜色的步骤示意图。在步骤S1中,获取当前图像,通过BGR转HSV只保留画笔的颜色,并且通过画笔的颜色找到画笔的位置。在步骤S2中,获取当前画笔的位置,产生画笔的边框,通过边框的中点来当做画笔的笔尖并且通过扫描出来的颜色面积大小来进行过滤掉噪音。在步骤S3中,通过获取的边框中点来产生一系列的颜色点。在步骤S4中,通过将摄像头和不同的图片进行画面大小调节使之每一处像素坐标一一对应,并且进行相应的Y轴对称,可以让内置摄像头方向一致。
对于RGB值的解释,格式为颜色(R ,G ,B),分别为红、绿、蓝三种基色,三基色在一起时产生白色,其深浅度都减半就会产生灰色,例如白色(255 ,255 ,255)、灰色(127 ,127 ,127)、黑色(0 ,0 ,0)、红色(255 ,0 ,0)、绿色(0 ,255 ,0)、蓝色(0 ,0 ,255)、青色(0 ,255 ,255)、洋红色(255 ,0 ,255)、黄色(255 ,255 ,0)、橙色(255 ,127 ,0)、紫色(127 ,0 ,255)、粉绿(0 ,225 ,128)湖蓝(0 ,128 ,255)、草绿(128 ,255 ,0)、玫瑰红(255 ,0 ,128),某种颜色的RGB值越接近,这种颜色就越接近灰色或黑白,数值越大就越白,反之越黑。例如RGB(150 ,152 ,183),RGB值比较接近,但是蓝色的成份较多一些,因此我们可以判断出这是一种蓝灰色,某种颜色的RGB值如果其中一值与其它两值相差较大,而其它两值比较接近,那么根据RGB中较大的值可以知道这种颜色是比较接近红、绿、蓝、洋红、青、黄中的一种,例如RGB(150 ,20 ,156),R和B值比较接近,G的值较小,因此这是种深紫红色;而RGB(150 ,200 ,156),R和B值比较接近,G的值较大,因此这是种浅绿色。其中,预设阈值可以根据实际教学环境进行设定,例如给定预设阈值为32,则界限上的RGB值如下:RGB(32,0,0)、RGB(0,32,0)、RGB(0,0,32)、RGB(32,32,0)、RGB(32,0,32)、RGB(0,32,32)、RGB(32,32,32)。
基于RGB的概念解释,在图像处理中使用较多的是 HSV 颜色空间,它比 RGB 更接近人们对彩色的感知经验。非常直观地表达颜色的色调、鲜艳程度和明暗程度,方便进行颜色的对比。
在 HSV 颜色空间下,比 BGR 更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。

  • HSV 表达彩色图像的方式由三个部分组成:
  • Hue(色调、色相)
  • Saturation(饱和度、色彩纯净度)
  • Value(明度)
    用下面这个圆柱体来表示 HSV 颜色空间,圆柱体的横截面可以看做是一个极坐标系 ,H 用极坐标的极角表示,S 用极坐标的极轴长度表示,V 用圆柱中轴的高度表示。
    在这里插入图片描述
    在Hue一定的情况下,饱和度减小,就是往光谱色中添加白色,光谱色所占的比例也在减小,饱和度减为0,表示光谱色所占的比例为零,导致整个颜色呈现白色。
    明度减小,就是往光谱色中添加黑色,光谱色所占的比例也在减小,明度减为0,表示光谱色所占的比例为零,导致整个颜色呈现黑色。
    HSV 对用户来说是一种比较直观的颜色模型。我们可以很轻松地得到单一颜色,即指定颜色角H,并让V=S=1,然后通过向其中加入黑色和白色来得到我们需要的颜色。增加黑色可以减小V而S不变,同样增加白色可以减小S而V不变。例如,要得到深蓝色,V=0.4 S=1 H=240度。要得到浅蓝色,V=1 S=0.4 H=240度。
    HSV 的拉伸对比度增强就是对 S 和 V 两个分量进行归一化(min-max normalize)即可,H 保持不变。
    在这里插入图片描述
    RGB颜色空间更加面向于工业,而HSV更加面向于用户,所以在对画笔进行颜色选择的时候我们使用HSV来进行选择,而对于画笔在画面中画出的颜色我们使用RGB。

四、功能实现与代码讲解

颜色选择器:
在这里插入图片描述
上图从左到右分别是原图、HSV图和选择出来的橙色图,通过颜色选择器可以在众多颜色和不同的图形中选择出想要的颜色,并且只保留出想要的颜色,如上图中选择了正方形代表的橙色,那么在最右图中只会保留出正方形。
在这里插入图片描述
上图为通过调整参数数值选择出来的浅绿色长方形。

#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int hmin = 0, smin = 0, vmin = 0;
int hmax = 255, smax = 255, vmax = 255;
int main()
{string path = "D:\\opencvC++\\Resources\\shapes.png";Mat img = imread(path);Mat imgHSV, mask;cvtColor(img, imgHSV, COLOR_BGR2HSV);//RGB转换成HSVnamedWindow("Trackbars", (640, 200));//命令框 640*200createTrackbar("Hue Min", "Trackbars", &hmin, 255);//调节HUE Min 0到255createTrackbar("Sat Min", "Trackbars", &smin, 255);createTrackbar("Val Min", "Trackbars", &vmin, 255);createTrackbar("Hue Max", "Trackbars", &hmax, 179);createTrackbar("Sat Max", "Trackbars", &smax, 255);createTrackbar("Val Max", "Trackbars", &vmax, 255);while (true){Scalar lower(hmin, smin, vmin);Scalar upper(hmax, smax, vmax);inRange(imgHSV, lower, upper, mask);imshow("Image", img);imshow("Image imgHSV", imgHSV);imshow("Image mask", mask);waitKey(1);}return 0;
}

基于上述代码改为使用摄像头可以动态识别不同颜色并且进行调整参数来保留指定颜色。保存好指定颜色画笔通过RGB值来保存虚拟画笔的颜色。

美术生绘画功能:

在这里插入图片描述
上图为通过虚拟黄色画笔在空白画布中画出的小太阳简笔画,可以看出基本效果还是不错的(动态效果展示见文件中的视频)。下面进行代码分析。
首先通过颜色选择器选择好的颜色,再通过画笔便可以选择在白色画面上作画或者直接真实画面中进行绘画。
(1)引入OpenCV相关库,定义画笔的颜色和虚拟画笔画出的颜色并打开内置摄像头。

#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
vector<vector<int> > myColors{ {138,92,199,179,255,255},//收集的粉色
{18,122,175,111,255,255} };//黄色
vector<Scalar> myColorValues{ {255,0,255} ,{0,225,255} };
Mat img, img2;
VideoCapture cap(0);//0默认是内置设备头
vector<vector<int> >newPoints;
Mat img31(512, 512, CV_8UC3, Scalar(255, 255, 255));
Mat img3, img33, img333;

(2)获取边框函数。通过边框的中点来当做画笔的笔尖。通过使用边框膨胀之后的图像进行处理,其中通过对面积大小的计算,过滤噪音,扫描出图像中指定画笔的颜色的边框,并且计算出上边框的中点坐标来作为返回值。

Point getContours(Mat imgDil) {vector<vector<Point> > contours;//保存边框vector<Vec4i> hierarchy;findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);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]);//找出每个边框的面积if (area > 1000)//过滤面积小于1000{float peri = arcLength(contours[i], true);//计算轮廓周长approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);//指定的点集进行多边形逼近的函数,其逼近的精度可通过参数设置。//approxPolyDP(contourMat, approxCurve, 10, true);//找出轮廓的多边形拟合曲线//第一个参数 InputArray curve:输入的点集//第二个参数OutputArray approxCurve:输出的点集,当前点集是能最小包容指定点集的。画出来即是一个多边形。//第三个参数double epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离。//第四个参数bool closed:若为true,则说明近似曲线是闭合的;反之,若为false,则断开。boundRect[i] = boundingRect(conPoly[i]);//计算轮廓的垂直边界最小矩形,矩形是与图像上下边界平行的myPoint.x = boundRect[i].x + boundRect[i].width / 2;//边框中间myPoint.y = boundRect[i].y;}}return myPoint;
}

(3)寻找画笔颜色函数。通过颜色选择器选择出来的颜色保存在myColors中,在寻找画笔颜色函数中用同样的代码来分离出画笔的颜色,并且将当前画笔所在的位置保存到newPoints中。

vector<vector<int> > findColor(Mat img)//通过画笔的颜色找到画笔的坐标
{Mat imgHSV;cvtColor(img, imgHSV, COLOR_BGR2HSV);//BGR转换成HSVfor (int i = 0; i < myColors.size(); ++i)//通过HSV模式下来只保留画笔颜色{Mat mask;Scalar lower(myColors[i][0], myColors[i][1], myColors[i][2]);Scalar upper(myColors[i][3], myColors[i][4], myColors[i][5]);inRange(imgHSV, lower, upper, mask);//imshow(to_string(i), mask);Point myPoint = getContours(mask);if (myPoint.x != 0){newPoints.push_back({ myPoint.x,myPoint.y,i });}}return newPoints;
}

(4)绘画函数。通过寻找画笔颜色函数中获取的newPoints和获取边框函数中的myColorValues,将newPoints中的坐标和myColorValues颜色值传入circle函数中,画出点,通过不断连续调用circle函数进而使绘画中成为线条。

void drawOnCanvas(vector<vector<int> >newPoints, vector<Scalar> myColorValues)//画点的函数
{for (int i = 0; i < newPoints.size(); ++i){circle(img33, Point(newPoints[i][0], newPoints[i][1]), 4, myColorValues[newPoints[i][2]], FILLED);//绘制出点}
}

(5)主函数。生成一个640×480大小的空白画纸来进行绘画,调用上述三个函数实现功能,由于前置摄像头生成的图像并不是镜像画面,而人们更加习惯镜像画面(现实中的左和图像中的右对应),于是将画面改为人们更加习惯的镜像画面。

int main()
{resize(img31, img3, Size(640, 480));flip(img3, img33, 1);while (true){cap.read(img);newPoints = findColor(img);//找到要画的点的坐标drawOnCanvas(newPoints, myColorValues);//画出一个点flip(img, img2, 1);//Y轴翻转,使画面同步flip(img33, img333, 1);//imshow("Image", img2);imshow("ti", img333);waitKey(1);}return 0;
}

线上老师教学画笔功能:

在这里插入图片描述
上图为用虚拟画笔在PPT画面上进行勾画的效果图,老师可以通过非电子设备在PPT上进行修改。
对美术生绘画功能进行改进,将空白画面转换为PPT中的画面,这样可以实现老师直接用画笔在PPT中进行相关板书,完整代码如下。

#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
vector<vector<int> > myColors{ {138,92,199,179,255,255},//收集的粉色
{18,122,175,111,255,255} };//黄色
vector<Scalar> myColorValues{ {255,0,255} ,{0,225,255} };
Mat img, img2;
VideoCapture cap(0);//0默认是内置设备头
vector<vector<int> >newPoints;
string path = "D:\\ID\\ti.png";
Mat img31 = imread(path);
Mat img3, img33, img333;
Point getContours(Mat imgDil)//获取边框,通过边框的中点来当做画笔的笔尖
{vector<vector<Point> > contours;//保存边框vector<Vec4i> hierarchy;findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);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]);//找出每个边框的面积if (area > 1000)//过滤面积小于1000{float peri = arcLength(contours[i], true);//计算轮廓周长approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);boundRect[i] = boundingRect(conPoly[i]);//计算轮廓的垂直边界最小矩形,矩形是与图像上下边界平行的myPoint.x = boundRect[i].x + boundRect[i].width / 2;//边框中间myPoint.y = boundRect[i].y;}}return myPoint;
}
vector<vector<int> > findColor(Mat img)//通过画笔的颜色找到画笔的坐标
{Mat imgHSV;cvtColor(img, imgHSV, COLOR_BGR2HSV);//BGR转换成HSVfor (int i = 0; i < myColors.size(); ++i)//通过HSV模式下来只保留画笔颜色{Mat mask;Scalar lower(myColors[i][0], myColors[i][1], myColors[i][2]);Scalar upper(myColors[i][3], myColors[i][4], myColors[i][5]);inRange(imgHSV, lower, upper, mask);Point myPoint = getContours(mask);if (myPoint.x != 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(img33, Point(newPoints[i][0], newPoints[i][1]), 4, myColorValues[newPoints[i][2]], FILLED);//绘制出点}
}
int main()
{resize(img31, img3, Size(640, 480));flip(img3, img33, 1);while (true){cap.read(img);newPoints = findColor(img);//找到要画的点的坐标drawOnCanvas(newPoints, myColorValues);//画出一个点flip(img, img2, 1);//Y轴翻转,使画面同步flip(img33, img333, 1);imshow("ti", img333);waitKey(1);}return 0;
}

这篇关于基于OpenCV隔空画笔实战(代码可直接运行)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/405517

相关文章

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

python多进程实现数据共享的示例代码

《python多进程实现数据共享的示例代码》本文介绍了Python中多进程实现数据共享的方法,包括使用multiprocessing模块和manager模块这两种方法,具有一定的参考价值,感兴趣的可以... 目录背景进程、进程创建进程间通信 进程间共享数据共享list实践背景 安卓ui自动化框架,使用的是

SpringBoot生成和操作PDF的代码详解

《SpringBoot生成和操作PDF的代码详解》本文主要介绍了在SpringBoot项目下,通过代码和操作步骤,详细的介绍了如何操作PDF,希望可以帮助到准备通过JAVA操作PDF的你,项目框架用的... 目录本文简介PDF文件简介代码实现PDF操作基于PDF模板生成,并下载完全基于代码生成,并保存合并P

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

SpringCloud集成AlloyDB的示例代码

《SpringCloud集成AlloyDB的示例代码》AlloyDB是GoogleCloud提供的一种高度可扩展、强性能的关系型数据库服务,它兼容PostgreSQL,并提供了更快的查询性能... 目录1.AlloyDBjavascript是什么?AlloyDB 的工作原理2.搭建测试环境3.代码工程1.