-
Notifications
You must be signed in to change notification settings - Fork 9
/
camodet.cpp
289 lines (255 loc) · 7.41 KB
/
camodet.cpp
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
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <math.h>
#include <ctime>
#include <chrono>
#include <signal.h>
#include <thread>
#include "settings.h"
//#define HEADLESS
using namespace cv;
using namespace std;
/** @brief Helper logging function
* This function logs events to stdout
* @param event The string describing the event to log
* @param quiet The quiet flag status to disable logging
*/
static void log_event(string event)
{
auto now = chrono::system_clock::now();
time_t time_now = chrono::system_clock::to_time_t(now);
cout << ctime(&time_now) << " - " << event << endl;
}
/** @brief Exec external command function
* This function will execute the command passed on the
* setting k parameter when motion is detected
* @param command The command to be executed
*/
static void execCommand(string command) {
system(command.c_str());
}
/** @brief Sabe ROI mask template to image file
* This function generates an image template for creating
* The ROI mask.
* @param cap VideoCapture where extract a frame for the image
*/
static void generate_mask_template(Mat frame)
{
vector<int> compression_params;
compression_params.push_back(IMWRITE_PNG_COMPRESSION);
compression_params.push_back(9);
try {
imwrite("mask.png", frame, compression_params);
}
catch (runtime_error& ex) {
cerr << "Exception converting image to PNG format: " << ex.what() << endl;
return;
}
log_event("Saved template for ROI Mask image.");
}
int main(int argc, char** argv) {
Settings settings;
if (settings.load_from_args(argc, argv) != 0) {
exit(-1);
}
signal( SIGPIPE, SIG_IGN ) ;
bool record = false;
double t2 = 0;
double t3 = 0;
int motion = 0;
int frames_motion = 0;
VideoWriter writer;
int counter = 0; /* Video name counter */
VideoCapture cap;
bool started = false;
//If no provided input source, try to open the
//first device's camera
if (settings.input_source == "") {
cap.set(CAP_PROP_FPS, settings.fps);
cap.open(0);
} else {
cap.open(settings.input_source);
}
counter = settings.counter_start;
#ifndef HEADLESS
//Prepare windows
if (settings.debug > 0 && settings.debug < 5) {
namedWindow("Debug", WINDOW_AUTOSIZE);
}
if (settings.show_input) {
namedWindow("Input", WINDOW_AUTOSIZE);
}
#endif
Size size(
(int)cap.get(CAP_PROP_FRAME_WIDTH),
(int)cap.get(CAP_PROP_FRAME_HEIGHT)
);
Mat frame, pyr1, gray, gaus, delta, old, binary, dilated, mask, bin_mask, dst;
Mat element = getStructuringElement(MORPH_RECT,Size(7,7),Point(-1,-1));
//Generate ROI mask template if needed
if (settings.mask_template) {
cap >> frame;
generate_mask_template(frame);
}
//Load the ROI mask image and binaryze
bool do_mask = false;
if (settings.mask_file != "") {
do_mask = true;
mask = imread(settings.mask_file,IMREAD_GRAYSCALE);
while (mask.size().width > settings.max_width) {
pyrDown(mask,mask);
}
threshold(mask,bin_mask,15,255,THRESH_BINARY);
}
unsigned int n = settings.noise;
int numfps = 0;
int fps = cap.get(CAP_PROP_FPS);
if (settings.fps > fps) settings.fps = fps;
long now = 0;
long nextframe = 0;
long startframe = 0;
int numframe = 0;
for (;;) {
now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
cap >> frame;
if (fps > settings.fps) {
if (startframe == 0) startframe = now;
if (now < nextframe) {
//do not process this frame
continue;
}
numframe++;
nextframe = startframe + ((1000/settings.fps)*numframe);
if (numframe>settings.fps) {
numframe=0;
startframe=startframe + ((1000/settings.fps)*(1+settings.fps));
}
}
if (frame.empty()) break; //ran out of film
pyr1 = frame.clone();
while (pyr1.size().width > settings.max_width) {
pyrDown(pyr1,pyr1);
}
cvtColor(pyr1, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gaus,Size(n,n), 5,5);
if (!started) {
gaus.copyTo(old);
started = true;
}
absdiff(old,gaus,delta);
gaus.copyTo(old);
threshold(delta,binary,15,255,THRESH_BINARY);
if (do_mask) {
//Input, Input, output
bitwise_and(bin_mask, binary, binary);
}
dilate(binary,dilated, element, Point(-1,-1), 2);
vector<vector < Point > > contours0;
vector<Vec4i> hierarchy;
findContours(dilated, contours0, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
ostringstream nameStream;
ostringstream timeStream;
auto now = chrono::system_clock::now();
time_t time_now = chrono::system_clock::to_time_t(now);
timeStream << ctime(&time_now);
string time = timeStream.str();
time.erase(std::remove(time.begin(), time.end(), '\n'), time.end());
//Camera Name
if (settings.cam_name != "") {
putText(frame, settings.cam_name, Point2f(15,25), FONT_HERSHEY_PLAIN, 2, Scalar(0,0,255,255), 2);
}
//Add timestamp to video
if (settings.timestamp) {
putText(frame, time, Point2f(15,size.height - 15), FONT_HERSHEY_PLAIN, 1, Scalar(0,0,255,255), 2);
}
if (settings.draw_contours) {
/* Draw contours */
Mat drawing = Mat::zeros( dilated.size(), CV_8UC3 );
RNG rng(12345);
for( int i = 0; i< contours0.size(); i++ )
{
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
drawContours( drawing, contours0, i, color, 2, 8, hierarchy, 0, Point() );
}
dst = drawing.clone();
while (dst.size().width < frame.size().width) {
pyrUp(dst,dst);
}
add( frame, dst, frame);
}
for (size_t k = 0; k < contours0.size(); k++) {
if (contourArea(contours0[k]) < settings.area) {
frames_motion = 0;
continue;
}
frames_motion++;
if (frames_motion < settings.frames_trigger) {
/* Not enough frames to trigger motion yet */
continue;
}
motion++; /* Some contour big enough to be movement */
t2 = 0;
if (!record) {
log_event("Motion Start");
record = true;
counter++;
nameStream << settings.output_name << counter << ".avi";
writer.open(nameStream.str().c_str(), VideoWriter::fourcc('X','V','I','D'),settings.fps,size);
log_event("Recording file: " + nameStream.str());
if (settings.command != "") {
log_event("Executing command: " + settings.command);
thread exec(execCommand, settings.command);
exec.detach();
}
}
}
if (motion == 0 && record && t2 == 0) {
/* If no motion in this frame, stop recording */
t2 = getTickCount();
frames_motion = 0;
}
motion = 0; /* Reset motion detected for the new frame */
#ifndef HEADLESS
//Show input source if desired
if (settings.show_input) {
namedWindow("Input", WINDOW_AUTOSIZE);
imshow("Input", frame);
}
#endif
/* Now compute time to stop recording */
if (record && t2 != 0) {
t3 = getTickCount();
double tf = (t3 - t2)/(double)getTickFrequency();
if ( tf >= settings.seconds_after) {
record = false;
t2 = 0;
writer.release();
log_event("Motion Stop");
}
}
if (record) {
writer << frame;
}
#ifndef HEADLESS
//Debug window
switch (settings.debug) {
case 1:
imshow("Debug", gaus); //Noise reduction
break;
case 2:
imshow("Debug", delta); //Difference image
break;
case 3:
imshow("Debug", binary); //Binary threshold image
break;
case 4:
imshow("Debug", dilated ); //Dilated
break;
default: //Should never happen
break;
}
if(waitKey(33) == 0) return 0;
#endif
}
return 0;
}