环境:OpenCV4.5.1 + VS2019
目录
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. 总结
实现功能:
通过颜色检测器检测出笔头颜色后,调用电脑摄像头可实时检测出笔头顶点中心的运动轨迹并将该运动轨迹用设置好的相应颜色绘制出来。
未完善地方:
同一时间只能使用一种颜色绘制,若同时使用两种或以上颜色进行绘制会出现绘制错乱的情况。