OpenCV (背景单一小球视频跟踪)
Eva.Q Lv9

小作业—视频跟踪

我们录制了一段视频,视频内容如下:

一个红色(可能是橙色……)的小球,从地上滚过去

视频特征:1. 背景单一 2. 要捕捉的对象与背景具有明显的颜色差别

学长的初步代码终于能跑起来了!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "opencv2/opencv.hpp"
#include "opencv2/video/background_segm.hpp"
#include<iostream>

using namespace std;
using namespace cv;


void main() {
VideoCapture video("D:/Summer Holiday Practice/opencv learning/Resources/tast.mp4");
int frameNum = 1;
Mat frame, mask, thresholdImage, output;
if (!video.isOpened())
cout << "fail to open!" << endl;
//cout<<video.isOpened();
double totalFrameNumber = video.get(CAP_PROP_FRAME_COUNT);
video >> frame;
Ptr<BackgroundSubtractorMOG2> bgsubtractor = createBackgroundSubtractorMOG2();
bgsubtractor->setVarThreshold(20);

while (true) {
if (totalFrameNumber == frameNum)
break;
video >> frame;
++frameNum;
//bgSubtractor(frame, mask, 0.001);
bgsubtractor->apply(frame, mask, 0.01);
imshow("mask", mask);
waitKey(10);
}
}

学长的最终代码

学长不愧是学长,太巨了!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
#include<opencv2/core.hpp>

using namespace std;
using namespace cv;


/// <summary>
/// Project 1
/// 使用HSV空间检测颜色、找到轮廓所在位置、取轮廓的位置然后创建一个圆
/// </summary>

Mat img;
vector<vector<int>> newPoints;

vector<vector<int>> myColors{ {0,87,54,15,205,245},//橙色(hmin smin vmin hmax smax vmax)
{0,0,0,0,0,0} };//绿色(hmin smin vmin hmax smax vmax)

vector<Scalar> myColorValues{ {255,0,255},//蓝色
{0,255,0} };//绿色

//! 获取轮廓
Point getContours(Mat imgDil) {//imgDil是传入的扩张边缘的图像用来查找轮廓,img是要在其上绘制轮廓的图像
vector<vector<Point>> contours;//轮廓检测到的轮廓。每个轮廓线存储为一个点的向量

vector<Vec4i> hierarchy;//包含关于映像拓扑的信息 typedef Vec<int, 4> Vec4i;具有4个整数值

//在二值图像中查找轮廓。该函数利用该算法从二值图像中提取轮廓
findContours(imgDil, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE);
//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);//img:要绘制轮廓在什么图片上,contours:要绘制的轮廓,-1定义要绘制的轮廓号(-1表示所有轮廓),Saclar表示轮廓颜色,2表示厚度

vector<vector<Point>> conPoly(contours.size());//conploy的数量应小于contours
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) {//轮廓面积>1000才绘制
//计算轮廓周长或凹坑长度。该函数计算了曲线长度和封闭的周长。
float peri = arcLength(contours[i], true);//计算封闭轮廓周长
approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);//以指定的精度近似多边形曲线。第二个参数conPloy[i]存储近似的结果,是输出。

boundRect[i] = boundingRect(conPoly[i]);//计算边界矩形

myPoint.x = boundRect[i].x;
myPoint.y = boundRect[i].y + boundRect[i].height / 2;

if (conPoly[i].size() > 4)
putText(img, "Small Ball", Point(boundRect[i].x, boundRect[i].y - 10), FONT_HERSHEY_PLAIN ,3 , Scalar(0, 255, 0), 6);

/*绘制边界矩形*/
rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);//tl():topleft矩形左上角坐标 br():bottom right矩形右下角坐标
drawContours(img, conPoly, i, Scalar(255, 0, 0), 2);
}
}
return myPoint;
}


vector<vector<int>> findColor(Mat img) {
Mat imgHSV;
cvtColor(img, imgHSV, COLOR_BGR2HSV);//转换图像到HSV空间,在其中查找颜色更加容易

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 });//i为颜色索引
}
}
return newPoints;

}

void drawOnCanvas(vector<vector<int>> newPoints, vector<Scalar> myColorValues) {
for (int i = 1; i < newPoints.size(); i++) {
line(img, Point(newPoints[i - 1][0], newPoints[i - 1][1]), Point(newPoints[i][0], newPoints[i][1]), Scalar(0, 255, 255), 10);
}
}

void main() {
VideoCapture cap("Resources/task.mp4");//相机id=0
char c;
bool stop = false;

namedWindow("Image", WINDOW_FREERATIO);
while (c = waitKey(1))
{
if (c == 27)
break;
if (c == 'p')
{
stop = !stop;
}
if (!stop)
{
cap >> img;

newPoints = findColor(img);
drawOnCanvas(newPoints, myColorValues);
imshow("Image", img);
}
}

}

我首先更改了视频读取的路径

D:/Summer Holiday Practice/opencv learning/Resources/task2.mp4

编译运行后,视频闪退了!??!

随后,调整了调试属性。平台从 x64活动 调整为 x64

在我的电脑上最终代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include<opencv2/imgcodecs.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
#include<opencv2/core.hpp>

using namespace std;
using namespace cv;


/// <summary>
/// Project 1
/// 使用HSV空间检测颜色、找到轮廓所在位置、取轮廓的位置然后创建一个圆
/// </summary>

Mat img;
vector<vector<int>> newPoints;

vector<vector<int>> myColors{ {0,87,54,15,205,245},//橙色(hmin smin vmin hmax smax vmax)
{0,0,0,0,0,0} };//绿色(hmin smin vmin hmax smax vmax)

vector<Scalar> myColorValues{ {255,0,255},//蓝色
{0,255,0} };//绿色

//! 获取轮廓
Point getContours(Mat imgDil) {//imgDil是传入的扩张边缘的图像用来查找轮廓,img是要在其上绘制轮廓的图像
vector<vector<Point>> contours;//轮廓检测到的轮廓。每个轮廓线存储为一个点的向量

vector<Vec4i> hierarchy;//包含关于映像拓扑的信息 typedef Vec<int, 4> Vec4i;具有4个整数值

//在二值图像中查找轮廓。该函数利用该算法从二值图像中提取轮廓
findContours(imgDil, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE);
//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);//img:要绘制轮廓在什么图片上,contours:要绘制的轮廓,-1定义要绘制的轮廓号(-1表示所有轮廓),Saclar表示轮廓颜色,2表示厚度

vector<vector<Point>> conPoly(contours.size());//conploy的数量应小于contours
vector<Rect> boundRect(contours.size());

Point myPoint(0, 0);

//过滤器:通过轮廓面积来过滤噪声
for (int i = 0; i < contours.size(); i++) {//遍历检测到的轮廓
double area = contourArea(contours[i]);

//cout << area << endl;

string objectType;
if (area > 1000) {//轮廓面积>1000才绘制
//计算轮廓周长或凹坑长度。该函数计算了曲线长度和封闭的周长。
double peri = arcLength(contours[i], true);//计算封闭轮廓周长
approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);//以指定的精度近似多边形曲线。第二个参数conPloy[i]存储近似的结果,是输出。

boundRect[i] = boundingRect(conPoly[i]);//计算边界矩形

myPoint.x = boundRect[i].x;
myPoint.y = boundRect[i].y + boundRect[i].height / 2;

if (conPoly[i].size() > 4)
putText(img, "Small Ball", Point(boundRect[i].x, boundRect[i].y - 10), FONT_HERSHEY_PLAIN, 3, Scalar(0, 255, 0), 6);

/*绘制边界矩形*/
rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);//tl():topleft矩形左上角坐标 br():bottom right矩形右下角坐标
drawContours(img, conPoly, i, Scalar(255, 0, 0), 2);
}
}
return myPoint;
}


vector<vector<int>> findColor(Mat img) {
Mat imgHSV;
cvtColor(img, imgHSV, COLOR_BGR2HSV);//转换图像到HSV空间,在其中查找颜色更加容易

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 });//i为颜色索引
}
}
return newPoints;

}

void drawOnCanvas(vector<vector<int>> newPoints, vector<Scalar> myColorValues) {
for (int i = 1; i < newPoints.size(); i++) {
line(img, Point(newPoints[i - 1][0], newPoints[i - 1][1]), Point(newPoints[i][0], newPoints[i][1]), Scalar(0, 255, 255), 10);
}
}

int main() {
VideoCapture cap("D:/Summer Holiday Practice/opencv learning/Resources/tast.mp4");//相机id=0
if (!cap.isOpened()) {
cout << "fail to open!" << endl;
return -1;
}
char c;
bool stop = false;

namedWindow("Image", WINDOW_FREERATIO);
while (c = waitKey(1))
{
if (c == 27)
break;
if (c == 'p')
{
stop = !stop;
}
if (!stop)
{
cap >> img;

newPoints = findColor(img);
drawOnCanvas(newPoints, myColorValues);
imshow("Image", img);
}
}
return 0;
}

代码跑起来了,就来认真学习一下学长的 “高级” 代码呐~

解剖代码ing~

全局变量
1
2
3
4
5
6
7
8
Mat img;
vector<vector<int>> newPoints;
//HSV颜色空间
vector<vector<int>> myColors{ {0,87,54,15,205,245},//橙色(hmin smin vmin hmax smax vmax)
{0,0,0,0,0,0} };//绿色(hmin smin vmin hmax smax vmax)
//RGB颜色空间
vector<Scalar> myColorValues{ {255,0,255},//蓝色
{0,255,0} };//绿色
绘制
1
2
3
4
5
6
void drawOnCanvas(vector<vector<int>> newPoints, vector<Scalar> myColorValues) {
for (int i = 1; i < newPoints.size(); i++) {
line(img, Point(newPoints[i - 1][0], newPoints[i - 1][1]), Point(newPoints[i][0], newPoints[i][1]), Scalar(0, 255, 255), 10);
}
}
//把 newPoints[i- 1] 和 newPoints[i] 连起来

line

1
2
3
4
5
6
7
8
void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
//img 要绘制线段的图像
//pt1 线段的起点
//pt2 线段的终点
//color 线段的颜色 Scalar对象定义
//thickness 线条的宽度
//lineType 线段的类型 8(默认)/4/CV_AA(高斯滤波)
//shift 坐标点小数点位数
颜色过滤 & 寻找对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<vector<int>> findColor(Mat img) {
Mat imgHSV;
cvtColor(img, imgHSV, COLOR_BGR2HSV);//转换图像到HSV空间,在其中查找颜色更加容易

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 });//i为颜色索引
}
}
return newPoints;//返回检测到物体轮廓的点集
}

Scalar

1
2
3
typedef struct Scalar{
double val[4];
}Scalar;

将各个通道的值构成一个整体


越看代码越觉得眼熟,欢迎回到前面的博客(bilibili教程代码——project1)

(我说怎么写代码写这么多注释……

  • Post title:OpenCV (背景单一小球视频跟踪)
  • Post author:Eva.Q
  • Create time:2021-07-29 12:33:41
  • Post link:https://qyy/2021/07/29/OPENCV/OpenCVProject1/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.