物体停留检测

一个视频检测小程序

功能很简单

检测目标区域内停留时间过长的物体

写的比较粗糙,本意是检测工厂内过道上是否存在障碍物。

思路是:每个进入ROI的物体都判断其中心点位置,由于多个运动物体存在多个中心点。这里做了一个假设——相邻两帧,最近的两个中心点是同一个物体。可以修改的参数有:中心点抖动偏移量 (作为判断物体运动的条件)、物体的允许停留时间 (超过这个时间报警)。

ROI区域通过鼠标选取四个点来划分 (四边形),可以符合道路“近大远小”的梯形。

测试效果:

黑色框为选定的ROI区域,红色框为ROI区域内停留时间过长的物体,蓝色框为ROI区域内正在运动的物体。图片中只有两个物体,如果更多的物体出现在ROI区域内,也可以被检测。

功能很简单,对于发生重叠的物体就没办法了,后续可以加上目标跟踪的方法。

上代码:

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

enum
{
CHOOSE_OK = 1, //区域选择完成状态
CHOOSE_NOT_OK, //区域未选择状态
CHOOSE_ING, //正在选择区域状态
BACKGROUND_SAVE,
BACKGROUND_NOT_SAVE,
MOVE_FIND,
MOVE_NOT_FIND
};

struct SelectObjectData
{
Point objectRectCenter;
Rect objectBoundRect;
int stayTimeCount;
};

Mat showImg, ROIbackground, ROImask, ROIimg;
Point startPoint = (-1, -1);
Point endPoint = (-1, -1);

vector<Point> preRectCenters; //代表前一帧障碍物的矩形轮廓中心
vector<Point> lastRectCenters; //代表后一帧障碍物的矩形轮廓中心

vector<SelectObjectData> selectObjects;

int chooseArea = CHOOSE_NOT_OK;
int backgroundState = BACKGROUND_NOT_SAVE;
int findMove = MOVE_NOT_FIND; //预留,暂时没用到
int clickCount = 0;

int stayTime = 300; //物体的允许的静止时间,与selectRectCenter数组的顺序对应
float objectScale = 0.2; //设置障碍物大小(降噪),表示占ROI区域的比例
float stationaryScale = 0.05; //设置判断静止物体时允许的震荡,表示占ROI区域的比例

Point peakPoints[4];

void DrawLine(Mat img, Point start, Point end)
{
int thickness = 2;
int lineType = 8;
line(img,
start,
end,
Scalar(0, 0, 0),
thickness,
lineType);
}

// 定义了4个点,画四边形
void DrawPolygon(Mat img)
{
int lineType = 8;

const Point* ppt[1] = { peakPoints };
int npt[] = { 4 };

fillPoly(img,
ppt,
npt,
1,
Scalar(255, 255, 255),
//Scalar(255),
lineType);
}

# 画矩形版本的OnMouse函数
//void OnMouse(int event, int x, int y, int flags, void *ustc)//event鼠标事件代号,x,y鼠标坐标,flags拖拽和键盘操作的代号
//{
// static Point lastPoint = (-1, -1);//初始坐标
// static Point prePoint = (-1, -1);//实时坐标
//
// if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标
// {
//
// chooseArea = CHOOSE_NOT_OK;
// backgroundState = BACKGROUND_NOT_SAVE;
// findMove = MOVE_NOT_FIND;
//
// //此处要避免vector一直追加的问题,用swap与新vector置换
// vector<Point>().swap(preRectCenters);
// vector<Point>().swap(lastRectCenters);
//
// vector<SelectObjectData>().swap(selectObjects);
// cout << "左键按下, selectObjects大小为:" << selectObjects.size() << endl;
//
//
// lastPoint = Point(x, y); //若是不加上面的内容,下次选择区域时会出现BUG
//
// }
// else if (event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON))//左键按下时,鼠标移动,跟随鼠标划矩形
// {
// prePoint = Point(x, y);
// chooseArea = CHOOSE_ING;
//
// startPoint = lastPoint;
// endPoint = prePoint;
// }
// else if (event == CV_EVENT_LBUTTONUP)//左键松开,将在图像上划矩形
// {
// prePoint = Point(x, y);
//
// int width = abs(lastPoint.x - prePoint.x);
// int height = abs(lastPoint.y - prePoint.y);
//
// if (width == 0 || height == 0)
// {
// startPoint = Point(-1, -1);
// endPoint = Point(-1, -1);
// return;
// }
//
// startPoint = Point(min(prePoint.x, lastPoint.x), min(prePoint.y, lastPoint.y));
// endPoint = Point(max(prePoint.x, lastPoint.x), max(prePoint.y, lastPoint.y));
//
// chooseArea = CHOOSE_OK;
// }
// else if (event == CV_EVENT_RBUTTONDOWN)//右键按下,还原,不做处理
// {
// chooseArea = CHOOSE_NOT_OK;
// backgroundState = BACKGROUND_NOT_SAVE;
// findMove = MOVE_NOT_FIND;
//
// //此处要避免vector一直追加的问题,用swap与新vector置换
// vector<Point>().swap(preRectCenters);
// vector<Point>().swap(lastRectCenters);
//
// vector<SelectObjectData>().swap(selectObjects);
// cout << "右键按下, selectObjects大小为:" << selectObjects.size() << endl;
// }
//}

# 画四边形版本的OnMouse函数
void OnMouse(int event, int x, int y, int flags, void *ustc)//event鼠标事件代号,x,y鼠标坐标,flags拖拽和键盘操作的代号
{
static Point pre_pt = (-1, -1);//初始坐标
static Point cur_pt = (-1, -1);//实时坐标
char temp[16];
if (event == CV_EVENT_LBUTTONDOWN)//左键按下,读取初始坐标,并在图像上该点处划圆
{
//org.copyTo(img);//将原始图片复制到img中
//sprintf(temp, "(%d,%d)", x, y);
//pre_pt = Point(x, y);
//circle(img, pre_pt, 2, Scalar(255, 0, 0, 0), CV_FILLED, CV_AA, 0);//划圆
//imshow("img", img);
}

else if (event == CV_EVENT_LBUTTONUP)//左键松开
{
//clickCount=3表示有4个点
if (clickCount <= 3)
{
peakPoints[clickCount] = Point(x, y);
cout << peakPoints[clickCount] << endl;
clickCount += 1;

if (clickCount == 4)
{
cout << "已有" << clickCount << "个点,开始绘图" << endl;

ROImask = Mat::zeros(showImg.size(), CV_8UC1);

DrawPolygon(ROImask);
showImg.copyTo(ROIimg, ROImask);
DrawLine(showImg, peakPoints[0], peakPoints[1]);
DrawLine(showImg, peakPoints[1], peakPoints[2]);
DrawLine(showImg, peakPoints[2], peakPoints[3]);
DrawLine(showImg, peakPoints[3], peakPoints[0]);

imshow("ROImask", ROImask);

chooseArea = CHOOSE_OK;
}
}
}
else if (event == CV_EVENT_RBUTTONDOWN) //点击右键,重新选择区域
{
cout << "清空点" << clickCount << endl;
clickCount = 0;
}
}
//筛选中心点
void MoveCentersRecord(vector<Rect> selectBoundRect) //1.记录当前帧所有中心点。2.记录上一帧所有中心点。3.进行停留计数
{
findMove = MOVE_FIND;

Point centerPoint;
SelectObjectData selectObject;
vector<Point> centerPoints;

//取对应中心点
for (int i = 0; i < selectBoundRect.size(); i++)
{
centerPoint.x = selectBoundRect[i].x + 1 / 2.0 * selectBoundRect[i].width;
centerPoint.y = selectBoundRect[i].y + 1 / 2.0 * selectBoundRect[i].height;
centerPoints.push_back(centerPoint);
cout << "第" << i + 1 << "个中心点坐标" << centerPoint << endl;
}

if (lastRectCenters.empty())
{
lastRectCenters.swap(centerPoints);
}
else //发现障碍物
{
preRectCenters.swap(lastRectCenters); //不等长的vector,是否可以交换,需要验证
lastRectCenters.swap(centerPoints);
cout << "preRectCenter大小" << preRectCenters.size() << endl;
cout << "lastRectCenter大小" << lastRectCenters.size() << endl;

for (int m = 0; m < preRectCenters.size(); m++) //比较两帧障碍物的矩形轮廓中心,并记录连续帧的中心点情况
{
for (int n = 0; n < lastRectCenters.size(); n++)
{
//if (abs(preRectCenters[m].x - lastRectCenters[n].x) < stationaryScale * (endPoint.x - startPoint.x) && //注意,这里的大小应该是可更改的!可以根据ROI区域大小更改
// abs(preRectCenters[m].y - lastRectCenters[n].y) < stationaryScale * (endPoint.y - startPoint.y)) //选前后两帧相差较小的中心点
if (abs(preRectCenters[m].x - lastRectCenters[n].x) < 5 && //注意,这里的大小应该是可更改的!可以根据ROI区域大小更改
abs(preRectCenters[m].y - lastRectCenters[n].y) < 5) //选前后两帧相差较小的中心点
{
if (selectObjects.empty())
{
//追加
selectObject.objectRectCenter = preRectCenters[m];
selectObject.objectBoundRect = selectBoundRect[n];
selectObject.stayTimeCount = 0;

selectObjects.push_back(selectObject);
}
else
{
for (int k = 0; k < selectObjects.size(); k++)
{
//跟上面一样,大小应该可更改,先设置为5
if (abs(preRectCenters[m].x - selectObjects[k].objectRectCenter.x) < 5 &&
abs(preRectCenters[m].y - selectObjects[k].objectRectCenter.y) < 5) //此中心点已经被记录
{
if (selectObjects[k].stayTimeCount <= 28)
{
//被记录的中心点加一,其他中心点计数在stationaryDetect()函数中减一,+2-1 = +1
selectObjects[k].stayTimeCount += 2;
cout << selectObjects[k].stayTimeCount << endl;
}

break;
}

if (k == selectObjects.size() - 1)
{
//追加
selectObject.objectRectCenter = preRectCenters[m];
selectObject.objectBoundRect = selectBoundRect[n];
selectObject.stayTimeCount = 0;

selectObjects.push_back(selectObject);

cout << "新增点" << endl;
break;
}
}
}
}
}
}
}
}


void stationaryDetect()
{
for (int i = 0; i < selectObjects.size(); i++)
{
selectObjects[i].stayTimeCount -= 1;

if (selectObjects[i].stayTimeCount == 29) //问题,如何将这个29置0
{
for (int m = 0; m < lastRectCenters.size(); m++)
{
//大小可更改:stationaryScale * (endPoint.x - startPoint.x),先设置为5
if (abs(lastRectCenters[m].x - selectObjects[i].objectRectCenter.x) < 5 &&
abs(lastRectCenters[m].y - selectObjects[i].objectRectCenter.y) < 5)
{
rectangle(showImg, selectObjects[i].objectBoundRect, Scalar(0, 0, 255), 2);
cout << "发现障碍物!" << endl;
break;
}
else if (m == lastRectCenters.size() - 1)
{
selectObjects[i].stayTimeCount /= 2;
}
}
}
else if (selectObjects[i].stayTimeCount <= -3)
{
selectObjects.erase(selectObjects.begin() + i);
}
}
}


void MoveDetect(Mat& temp, Mat& frame)
{
Mat result = frame.clone();
//1.将background和frame转为灰度图
Mat gray1, gray2;
cvtColor(temp, gray1, CV_BGR2GRAY);
cvtColor(frame, gray2, CV_BGR2GRAY);
//2.将background和frame做差
Mat diff;
absdiff(gray1, gray2, diff);
// imshow("diff", diff);
//3.对差值图diff_thresh进行阈值化处理
Mat diff_thresh;
threshold(diff, diff_thresh, 30, 255, CV_THRESH_BINARY);//CV_THRESH_BINARY——flag表示大于50都变成255
// imshow("diff_thresh", diff_thresh);
//4.腐蚀
Mat kernel_erode = getStructuringElement(MORPH_RECT, Size(3, 3)); //MORPH_RECTb表示矩形内核
Mat kernel_dilate = getStructuringElement(MORPH_RECT, Size(18, 18));
erode(diff_thresh, diff_thresh, kernel_erode);
// imshow("erode", diff_thresh);
//5.膨胀
dilate(diff_thresh, diff_thresh, kernel_dilate);
// imshow("dilate", diff_thresh);
//6.查找轮廓
vector<vector<Point>> allContours;
findContours(diff_thresh, allContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

//7.筛选轮廓,查找正外接矩形
if (allContours.size() > 0) //若侦测到物体
{
Rect boundRect;
vector<Rect> selectBoundRect;

//筛选,设定障碍物的宽或高至少为ROI区域边界的objectScale倍
for (int i = 0; i < allContours.size(); i++)
{
boundRect = boundingRect(allContours[i]); //计算轮廓的垂直边界最小矩形

if (boundRect.width > objectScale*(endPoint.x - startPoint.x) || boundRect.height > objectScale*(endPoint.y - startPoint.y))
{
rectangle(showImg, boundRect, Scalar(255, 0, 0), 2);//在result上绘制正外接矩形

selectBoundRect.push_back(boundRect);
}
}

if (selectBoundRect.size() > 0)
{
MoveCentersRecord(selectBoundRect);
stationaryDetect();
}
else //区域内无足够大的障碍物,清空
{
vector<Point>().swap(preRectCenters);
vector<Point>().swap(lastRectCenters);
cout << "未检测到物体" << endl;
}
}
else //若未侦测到物体,清空
{
vector<Point>().swap(preRectCenters);
vector<Point>().swap(lastRectCenters);
cout << "未检测到物体" << endl;
}

return;
}

int main()
{
VideoCapture capture(0); //0是打开摄像头,若是"1.avi",则是打开1.avi这个视频文件

if (!capture.isOpened()) // 异常处理
{
cout << "Cannot open the video cam" << endl;
waitKey(60000);
return -1;
}

Mat lastImg, preImg, lastROI, preROI;

namedWindow("showWindow");//定义一个img窗口
setMouseCallback("showWindow", OnMouse, 0);//调用回调函数on_mouse

while (1)
{
capture >> preImg; //读取当前帧

preImg.copyTo(showImg);

if (chooseArea == CHOOSE_OK)
{
preImg.copyTo(lastImg);
// lastROI = lastImg(Range(startPoint.y, endPoint.y), Range(startPoint.x, endPoint.x));
lastImg.copyTo(lastROI, ROImask);
if (backgroundState == BACKGROUND_NOT_SAVE) //保存背景,可以对比,发现运动物体
{
lastROI.copyTo(ROIbackground);
backgroundState = BACKGROUND_SAVE;
}

capture >> preImg; //执行这句,之前的preImg相当于lastImg了

// preROI = preImg(Range(startPoint.y, endPoint.y), Range(startPoint.x, endPoint.x));

preImg.copyTo(showImg);
preImg.copyTo(preROI, ROImask);
MoveDetect(ROIbackground, preROI);

waitKey(10);

// preImg.copyTo(showImg);

// rectangle(showImg, startPoint, endPoint, Scalar(0, 255, 0, 0), 1, 8, 0);
DrawLine(showImg, peakPoints[0], peakPoints[1]);
DrawLine(showImg, peakPoints[1], peakPoints[2]);
DrawLine(showImg, peakPoints[2], peakPoints[3]);
DrawLine(showImg, peakPoints[3], peakPoints[0]);
}
else if (chooseArea == CHOOSE_NOT_OK)
{
waitKey(10);
}

imshow("showWindow", showImg);
}

return 0;
}

----------over----------


文章标题:物体停留检测

文章作者:Ge垚

发布时间:2018年05月12日 - 11:05

最后更新:2018年05月23日 - 13:05

原始链接:http://geyao1995.com/物体停留检测/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。