2022-04-14 09:45:54 -04:00
|
|
|
#include <iostream>
|
2022-07-08 15:24:45 -04:00
|
|
|
#include <fstream>
|
2022-04-14 09:45:54 -04:00
|
|
|
#include <thread>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/stat.h>
|
2022-07-08 15:24:45 -04:00
|
|
|
#include <errno.h>
|
2022-04-14 09:45:54 -04:00
|
|
|
|
|
|
|
#include <opencv4/opencv2/opencv.hpp>
|
|
|
|
#include <opencv4/opencv2/video/tracking.hpp>
|
|
|
|
#include <opencv4/opencv2/core/ocl.hpp>
|
|
|
|
#include <opencv4/opencv2/videoio.hpp>
|
|
|
|
|
|
|
|
using namespace cv;
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
struct shared_t
|
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
vector<Mat> buff;
|
|
|
|
string detectUrl;
|
|
|
|
string recordUrl;
|
|
|
|
string outDir;
|
|
|
|
string postCmd;
|
|
|
|
string conf;
|
|
|
|
bool wrRunning;
|
|
|
|
bool ffRunning;
|
|
|
|
int motion;
|
|
|
|
int secs;
|
|
|
|
int thrWithMotion;
|
|
|
|
int thresh;
|
|
|
|
int pixSize;
|
|
|
|
int postMoIncr;
|
|
|
|
int minRecFrames;
|
|
|
|
int sectionSize;
|
|
|
|
int retCode;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
|
|
|
} sharedRes;
|
|
|
|
|
|
|
|
string cleanDir(const string &path)
|
|
|
|
{
|
|
|
|
if (path[path.size() - 1] == '/')
|
|
|
|
{
|
|
|
|
return path.substr(0, path.size() - 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool createDir(const string &dir)
|
|
|
|
{
|
|
|
|
auto ret = mkdir(dir.c_str(), 0777);
|
|
|
|
|
|
|
|
if (ret == -1)
|
|
|
|
{
|
|
|
|
return errno == EEXIST;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool createDirTree(const string &full_path)
|
|
|
|
{
|
|
|
|
size_t pos = 0;
|
|
|
|
auto ret = true;
|
|
|
|
|
|
|
|
while (ret == true && pos != string::npos)
|
|
|
|
{
|
|
|
|
pos = full_path.find('/', pos + 1);
|
|
|
|
ret = createDir(full_path.substr(0, pos));
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vidCap(shared_t *share)
|
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
if (share->buff.size() >= share->minRecFrames)
|
2022-04-22 09:43:07 -04:00
|
|
|
{
|
|
|
|
share->wrRunning = true;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
time_t rawtime;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
time(&rawtime);
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
auto timeinfo = localtime(&rawtime);
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
char dirName[20];
|
|
|
|
char fileName[20];
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
strftime(dirName, 20, "%Y%m%d", timeinfo);
|
|
|
|
strftime(fileName, 20, "%H%M%S.avi", timeinfo);
|
2022-04-14 09:45:54 -04:00
|
|
|
|
|
|
|
createDirTree(cleanDir(share->outDir) + string("/") + string(dirName));
|
|
|
|
|
|
|
|
auto dstPath = cleanDir(share->outDir) + string("/") + string(dirName) + string("/") + string(fileName);
|
2022-04-22 09:43:07 -04:00
|
|
|
auto codec = VideoWriter::fourcc('M', 'J', 'P', 'G');
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
VideoWriter writer;
|
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
writer.open(dstPath, codec, 30.0, share->buff[0].size(), true);
|
2022-04-22 09:43:07 -04:00
|
|
|
|
|
|
|
if (!writer.isOpened())
|
|
|
|
{
|
|
|
|
cerr << "could not open the output video file for writing: " << dstPath;
|
|
|
|
}
|
|
|
|
else
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-04-22 09:43:07 -04:00
|
|
|
for (; !share->buff.empty(); share->buff.erase(share->buff.begin()))
|
|
|
|
{
|
|
|
|
writer.write(share->buff[0]);
|
|
|
|
}
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
share->wrRunning = false;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
uchar valDiff(uchar valA, uchar valB)
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
auto diff = 0;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
if (valA > valB) diff = valA - valB;
|
|
|
|
if (valA < valB) diff = valB - valA;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
return diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
void secDiff(Mat imgA, Mat imgB, int rows, int cols, int rowOffs, int colOffs, shared_t *share)
|
|
|
|
{
|
|
|
|
auto xCnt = 0;
|
|
|
|
auto yCnt = 0;
|
|
|
|
|
|
|
|
for (auto y = rowOffs; (y < rows) && share->thrWithMotion == 0; y++)
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
for (auto x = colOffs; (x < cols) && share->thrWithMotion == 0; x++)
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
auto pixA = imgA.at<uchar>(Point(x, y));
|
|
|
|
auto pixB = imgB.at<uchar>(Point(x, y));
|
|
|
|
|
|
|
|
if (valDiff(pixA, pixB) > share->thresh)
|
|
|
|
{
|
|
|
|
xCnt += 1;
|
2022-06-11 08:43:19 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
if (xCnt >= share->pixSize) break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
xCnt = 0;
|
|
|
|
}
|
2022-06-11 08:43:19 -04:00
|
|
|
}
|
2022-07-08 15:24:45 -04:00
|
|
|
|
|
|
|
if (xCnt >= share->pixSize)
|
2022-06-11 08:43:19 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
yCnt += 1;
|
|
|
|
|
|
|
|
if (yCnt >= share->pixSize)
|
|
|
|
{
|
|
|
|
share->thrWithMotion += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
xCnt = 0;
|
|
|
|
yCnt = 0;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
}
|
2022-04-22 09:43:07 -04:00
|
|
|
}
|
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
bool grayDiff(Mat imgA, Mat imgB, shared_t *share)
|
|
|
|
{
|
|
|
|
share->thrWithMotion = 0;
|
|
|
|
|
|
|
|
auto colBuff = share->sectionSize;
|
|
|
|
auto allRows = imgA.rows;
|
|
|
|
auto allCols = imgA.cols;
|
|
|
|
auto colsOffs = 0;
|
|
|
|
|
|
|
|
vector<thread> thrs;
|
|
|
|
|
|
|
|
while (allCols != 0)
|
|
|
|
{
|
|
|
|
if (colBuff > allCols)
|
|
|
|
{
|
|
|
|
colBuff -= (colBuff - allCols);
|
|
|
|
}
|
|
|
|
|
|
|
|
thrs.push_back(thread(secDiff, imgA, imgB, allRows, colBuff, 0, colsOffs, share));
|
|
|
|
|
|
|
|
colsOffs += colBuff;
|
|
|
|
allCols -= colBuff;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto &&thr : thrs)
|
|
|
|
{
|
|
|
|
thr.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
return share->thrWithMotion != 0;
|
|
|
|
}
|
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
void timer(shared_t *share)
|
|
|
|
{
|
|
|
|
sleep(share->secs);
|
|
|
|
|
2022-06-11 08:43:19 -04:00
|
|
|
if (share->motion == 0)
|
|
|
|
{
|
|
|
|
share->ffRunning = false;
|
|
|
|
}
|
2022-04-22 09:43:07 -04:00
|
|
|
|
|
|
|
if (!share->wrRunning)
|
|
|
|
{
|
|
|
|
new thread(vidCap, share);
|
|
|
|
}
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
|
2022-06-11 08:43:19 -04:00
|
|
|
Mat toGray(const Mat &src)
|
|
|
|
{
|
|
|
|
Mat ret;
|
|
|
|
|
|
|
|
cvtColor(src, ret, COLOR_BGR2GRAY);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-04-14 09:45:54 -04:00
|
|
|
void moDetect(shared_t *share)
|
|
|
|
{
|
2022-04-22 09:43:07 -04:00
|
|
|
auto dCap = VideoCapture(share->detectUrl, CAP_FFMPEG);
|
|
|
|
auto rCap = VideoCapture(share->recordUrl, CAP_FFMPEG);
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
Mat dFrame, rFrame, dPrev, rPrev;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
while (share->ffRunning)
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
if (share->motion == 0) dCap >> dFrame;
|
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
rCap >> rFrame;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
if (dFrame.empty() && (share->motion == 0))
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
|
|
|
// broken frames returned from the cameras i've tested this with would cause
|
|
|
|
// the entire capture connection to drop, hence why this bit of code is here
|
|
|
|
// to detect empty frames (signs of a dropped connection) and attempt
|
|
|
|
// re-connect to the cammera.
|
2022-04-22 09:43:07 -04:00
|
|
|
dCap.open(share->detectUrl, CAP_FFMPEG);
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
2022-04-22 09:43:07 -04:00
|
|
|
else if (rFrame.empty())
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-04-22 09:43:07 -04:00
|
|
|
rCap.open(share->recordUrl, CAP_FFMPEG);
|
|
|
|
}
|
2022-07-08 15:24:45 -04:00
|
|
|
else if (share->motion > 0)
|
2022-04-22 09:43:07 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
share->buff.push_back(rFrame.clone());
|
|
|
|
|
|
|
|
share->motion -= 1;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
2022-07-08 15:24:45 -04:00
|
|
|
else if (dPrev.empty() || rPrev.empty())
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
dPrev = toGray(dFrame);
|
|
|
|
rPrev = rFrame.clone();
|
|
|
|
}
|
|
|
|
else if (grayDiff(dPrev, toGray(dFrame), share))
|
|
|
|
{
|
|
|
|
share->buff.push_back(rPrev);
|
|
|
|
share->buff.push_back(rFrame.clone());
|
2022-06-11 08:43:19 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
share->motion += share->postMoIncr;
|
2022-06-11 08:43:19 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
rPrev.release();
|
|
|
|
dPrev.release();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rPrev.release();
|
|
|
|
dPrev.release();
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
}
|
2022-04-22 09:43:07 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
system(share->postCmd.c_str());
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
|
|
|
for (int i = 0; i < argc; ++i)
|
|
|
|
{
|
|
|
|
auto argInParams = string(argv[i]);
|
|
|
|
|
|
|
|
if (arg.compare(argInParams) == 0)
|
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
if (!argOnly)
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
// check ahead, make sure i + 1 won't cause out-of-range exception
|
|
|
|
if ((i + 1) <= (argc - 1))
|
|
|
|
{
|
|
|
|
return string(argv[i + 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return string("true")
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return string();
|
|
|
|
}
|
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
bool rdConf(shared_t *share)
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
// recording_stream
|
|
|
|
// detection_stream
|
|
|
|
// output_dir
|
|
|
|
// diff_threshold
|
|
|
|
// post_cmd
|
|
|
|
// duration
|
|
|
|
// pixel_size
|
|
|
|
// frames_post_motion
|
|
|
|
// minimum_recording_frames
|
|
|
|
// section_size
|
|
|
|
|
|
|
|
auto ret = false;
|
|
|
|
|
|
|
|
share->retCode = ENOENT;
|
|
|
|
|
|
|
|
ifstream varFile(share->conf.c_str());
|
|
|
|
|
|
|
|
if (!varFile.is_open())
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
cerr << "err: failed to open the config file: " << share->conf << " for reading. please check file permissions or if it exists." << endl;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
2022-07-08 15:24:45 -04:00
|
|
|
else
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
string line;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
getline(varFile, line);
|
|
|
|
|
|
|
|
if (line.rfind("#", 0) != 0)
|
|
|
|
{
|
|
|
|
if (line.rfind("recording_stream = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->recordUrl = line.substr(19);
|
|
|
|
|
|
|
|
cout << "recording_stream = " << share->recordUrl << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("detection_stream = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->detectUrl = line.substr(19);
|
|
|
|
|
|
|
|
cout << "detection_stream = " << share->detectUrl << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("output_dir = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->outDir = line.substr(13);
|
|
|
|
|
|
|
|
cout << "output_dir = " << share->outDir << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("post_cmd = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->postCmd = line.substr(11);
|
|
|
|
|
|
|
|
cout << "post_cmd = " << share->postCmd << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("diff_threshold = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->thresh = strtol(line.substr(17).c_str(), NULL, 10);
|
|
|
|
|
|
|
|
cout << "diff_threshold = " << share->thresh << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("duration = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->secs = strtol(line.substr(11).c_str(), NULL, 10);
|
|
|
|
|
|
|
|
cout << "duration = " << share->secs << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("pixel_size = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->pixSize = strtol(line.substr(13).c_str(), NULL, 10);
|
|
|
|
|
|
|
|
cout << "pixel_size = " << share->pixSize << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("frames_post_motion = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->postMoIncr = strtol(line.substr(21).c_str(), NULL, 10);
|
|
|
|
|
|
|
|
cout << "frames_post_motion = " << share->postMoIncr << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("minimum_recording_frames = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->minRecFrames = strtol(line.substr(27).c_str(), NULL, 10);
|
|
|
|
|
|
|
|
cout << "minimum_recording_frames = " << share->minRecFrames << endl;
|
|
|
|
}
|
|
|
|
else if (line.rfind("section_size = ", 0) == 0)
|
|
|
|
{
|
|
|
|
share->sectionSize = strtol(line.substr(15).c_str(), NULL, 10);
|
|
|
|
|
|
|
|
cout << "section_size = " << share->sectionSize << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} while(!line.empty());
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
|
|
|
|
share->retCode = 0;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
2022-07-08 15:24:45 -04:00
|
|
|
|
|
|
|
varFile.close();
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void showHelp()
|
|
|
|
{
|
|
|
|
cout << "Motion Watch v1.0" << endl << endl;
|
|
|
|
cout << "Usage: mow <argument>" << endl << endl;
|
|
|
|
cout << "-h : display usage information about this application." << endl;
|
|
|
|
cout << "-c : path to the config file." << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
|
|
{
|
|
|
|
sharedRes.conf = parseForParam("-c", argc, argv, false);
|
|
|
|
|
|
|
|
if (parseForParam("-h", argc, argv, true) == "true")
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
showHelp();
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
2022-07-08 15:24:45 -04:00
|
|
|
else if (sharedRes.conf.empty())
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
cerr << "err: a config file was not given in -c" << endl;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
sharedRes.retCode = 0;
|
2022-06-11 08:43:19 -04:00
|
|
|
sharedRes.motion = 0;
|
2022-07-08 15:24:45 -04:00
|
|
|
sharedRes.wrRunning = false;
|
2022-04-22 09:43:07 -04:00
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
while (rdConf(&sharedRes))
|
2022-04-14 09:45:54 -04:00
|
|
|
{
|
2022-07-08 15:24:45 -04:00
|
|
|
sharedRes.ffRunning = true;
|
2022-04-14 09:45:54 -04:00
|
|
|
|
2022-04-22 09:43:07 -04:00
|
|
|
thread th1(timer, &sharedRes);
|
2022-04-14 09:45:54 -04:00
|
|
|
thread th2(moDetect, &sharedRes);
|
|
|
|
|
|
|
|
// Wait for the threads to finish
|
|
|
|
// Wait for thread t1 to finish
|
|
|
|
th1.join();
|
|
|
|
// Wait for thread t2 to finish
|
|
|
|
th2.join();
|
|
|
|
}
|
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
return sharedRes.retCode;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|
|
|
|
|
2022-07-08 15:24:45 -04:00
|
|
|
return EINVAL;
|
2022-04-14 09:45:54 -04:00
|
|
|
}
|