OpenCV4.0 C++ 实战:虚拟画家(自用 代码+注释)

9 篇文章 7 订阅

环境:OpenCV4.5.1 + VS2019

目录

1. 准备工作:

1.1 "资源"文件

1.2 颜色检测器

2. 主要代码

2.1 获取笔头轮廓并找出笔头顶点坐标

2.2 找出落笔位置坐标及颜色的集

2.3 在视频窗口中画出笔头运动轨迹

2.4 实现

3. 总结


1. 准备工作:

1.1 "资源"文件

"Resources"文件下载链接:

https://pan.baidu.com/s/1uzVDwl8lD2qVTY1bFlhF1A 
提取码:5n48

1.2 颜色检测器

拾色器.cpp

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

//	颜色检测器
using namespace cv;
using namespace std;

//		Color Picker		//

Mat imgHSV, mask, imgColor;
int hmin = 0, smin = 0, vmin = 0;
int hmax = 179, smax = 255, vmax = 255;

VideoCapture cap(0);
Mat img;

void main() {

	namedWindow("Trackbar", (640, 200));	//	Create Window
	createTrackbar("Hue Min", "Trackbar", &hmin, 179);	//参数三:初始值(当前值);
	createTrackbar("Hue Max", "Trackbar", &hmax, 179);	//参数四:最大范围值
	createTrackbar("Sat Min", "Trackbar", &smin, 255);
	createTrackbar("Sat Max", "Trackbar", &smax, 255);
	createTrackbar("Val Min", "Trackbar", &vmin, 255);
	createTrackbar("Val Max", "Trackbar", &vmax, 255);

	while (true) {

		cap.read(img);
		flip(img, img, 1);
		cvtColor(img, imgHSV, COLOR_BGR2HSV);

		Scalar lower(hmin, smin, vmin);
		Scalar upper(hmax, smax, vmax);

		inRange(imgHSV, lower, upper, mask);
		//bitwise_and(img, img, imgColor, mask = mask);

		cout << hmin << "," << smin << "," << vmin << ",";
		cout << hmax << "," << smax << "," << vmax << endl;

		imshow("Image", img);
		imshow("Image Mask", mask);
		//imshow("Image Color", imgColor);

		waitKey(1);
	}
}

2. 主要代码

2.1 获取笔头轮廓并找出笔头顶点坐标

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;
}

2.2 找出落笔位置坐标及颜色的集

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;
}

2.3 在视频窗口中画出笔头运动轨迹

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);
		}
	}
}

2.4 实现

Project 1.cpp

//#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 = {};
		}
	}

}

3. 总结

实现功能:

通过颜色检测器检测出笔头颜色后,调用电脑摄像头可实时检测出笔头顶点中心的运动轨迹并将该运动轨迹用设置好的相应颜色绘制出来。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAY2h4aW4xNDE2MA==,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAY2h4aW4xNDE2MA==,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAY2h4aW4xNDE2MA==,size_20,color_FFFFFF,t_70,g_se,x_16

未完善地方:

同一时间只能使用一种颜色绘制,若同时使用两种或以上颜色进行绘制会出现绘制错乱的情况。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAY2h4aW4xNDE2MA==,size_20,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAY2h4aW4xNDE2MA==,size_20,color_FFFFFF,t_70,g_se,x_16

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值