基于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

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

CSS3中使用flex和grid实现等高元素布局的示例代码

《CSS3中使用flex和grid实现等高元素布局的示例代码》:本文主要介绍了使用CSS3中的Flexbox和Grid布局实现等高元素布局的方法,通过简单的两列实现、每行放置3列以及全部代码的展示,展示了这两种布局方式的实现细节和效果,详细内容请阅读本文,希望能对你有所帮助... 过往的实现方法是使用浮动加

在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程

《在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程》本文介绍了在Java中使用ModelMapper库简化Shapefile属性转JavaBean的过程,对比... 目录前言一、原始的处理办法1、使用Set方法来转换2、使用构造方法转换二、基于ModelMapper

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前