JustMotion/src/main.cpp

363 lines
8.4 KiB
C++
Raw Normal View History

2022-04-14 09:45:54 -04:00
#include <iostream>
#include <fstream>
2022-04-14 09:45:54 -04:00
#include <string>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
2022-04-14 09:45:54 -04:00
#include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/videoio.hpp>
using namespace cv;
using namespace std;
struct shared_t
{
VideoCapture camera;
Mat baseImg;
string detectUrl;
string recordUrl;
string diffVerb;
string outDir;
string postCmd;
string conf;
int detectFps;
int colorThresh;
int secs;
int consec;
int consecThresh;
int pixThresh;
int postMoIncr;
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;
}
string genDstFile(const string &dirOut, const string &ext)
2022-04-14 09:45:54 -04:00
{
time_t rawtime;
2022-04-14 09:45:54 -04:00
time(&rawtime);
2022-04-14 09:45:54 -04:00
auto timeinfo = localtime(&rawtime);
2022-04-14 09:45:54 -04:00
char dirName[20];
char fileName[20];
2022-04-14 09:45:54 -04:00
strftime(dirName, 20, "%Y%m%d", timeinfo);
strftime(fileName, 20, "%H%M%S", timeinfo);
2022-04-14 09:45:54 -04:00
createDirTree(cleanDir(dirOut) + string("/") + string(dirName));
2022-04-14 09:45:54 -04:00
return cleanDir(dirOut) + string("/") + string(dirName) + string("/") + string(fileName) + ext;
}
2022-04-14 09:45:54 -04:00
void wrOut(shared_t *share)
{
share->baseImg.release();
2022-04-14 09:45:54 -04:00
share->consec = 0;
auto dstPath = genDstFile(share->outDir, ".mp4");
auto cmd = "ffmpeg -i " + share->recordUrl + " -y -vcodec copy -t " + to_string(share->postMoIncr) + " " + dstPath;
system(cmd.c_str());
}
2022-04-14 09:45:54 -04:00
Mat toGray(const Mat &src)
{
Mat ret;
cvtColor(src, ret, COLOR_BGR2GRAY);
return ret;
2022-04-14 09:45:54 -04:00
}
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share)
2022-04-14 09:45:54 -04:00
{
auto diff = 0;
2022-04-14 09:45:54 -04:00
if (pixA > pixB) diff = pixA - pixB;
if (pixB > pixA) diff = pixB - pixA;
if (diff < share->colorThresh)
{
diff = 0;
}
2022-04-14 09:45:54 -04:00
return diff != 0;
}
int secDiff(Mat imgA, Mat imgB, int rows, int cols, int rowOffs, int colOffs, shared_t *share)
{
auto pnts = 0;
for (auto y = rowOffs; y < rows; y++)
2022-04-14 09:45:54 -04:00
{
for (auto x = colOffs; x < cols; x++)
2022-04-14 09:45:54 -04:00
{
auto pixA = imgA.at<uchar>(Point(x, y));
auto pixB = imgB.at<uchar>(Point(x, y));
if (pixDiff(pixA, pixB, share))
{
pnts += 1;
}
}
2022-04-14 09:45:54 -04:00
}
return pnts;
}
bool imgDiff(Mat curImg, shared_t *share)
{
if (share->baseImg.empty())
{
share->baseImg = toGray(curImg);
return false;
}
else
2022-04-14 09:45:54 -04:00
{
curImg = toGray(curImg);
auto pnts = secDiff(share->baseImg, curImg, curImg.rows, curImg.cols, 0, 0, share);
2022-04-14 09:45:54 -04:00
if (share->diffVerb == "Y")
2022-04-14 09:45:54 -04:00
{
cout << "diff: " << pnts << endl;
}
share->baseImg = curImg.clone();
if (pnts >= share->pixThresh)
{
share->consec += 1;
return share->consec >= share->consecThresh;
}
else
{
share->consec = 0;
return false;
2022-04-14 09:45:54 -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)
{
if (!argOnly)
2022-04-14 09:45:54 -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();
}
void rdLine(const string &param, const string &line, string *value)
2022-04-14 09:45:54 -04:00
{
if (line.rfind(param.c_str(), 0) == 0)
{
*value = line.substr(param.size());
cout << param << *value << endl;
}
}
void rdLine(const string &param, const string &line, int *value)
{
if (line.rfind(param.c_str(), 0) == 0)
{
*value = strtol(line.substr(param.size()).c_str(), NULL, 10);
cout << param << *value << endl;
}
}
bool rdConf(shared_t *share)
{
auto ret = false;
share->retCode = ENOENT;
ifstream varFile(share->conf.c_str());
if (!varFile.is_open())
2022-04-14 09:45:54 -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
}
else
2022-04-14 09:45:54 -04:00
{
string line;
share->recordUrl.clear();
share->detectUrl.clear();
share->outDir.clear();
share->postCmd.clear();
share->diffVerb.clear();
share->baseImg.release();
share->pixThresh = 8;
share->consecThresh = 10;
share->colorThresh = 60;
share->secs = 60;
share->detectFps = 20;
share->postMoIncr = 5;
share->consec = 0;
do
{
getline(varFile, line);
if (line.rfind("#", 0) != 0)
{
rdLine("recording_stream = ", line, &share->recordUrl);
rdLine("detection_stream = ", line, &share->detectUrl);
rdLine("output_dir = ", line, &share->outDir);
rdLine("diff_verbose = ", line, &share->diffVerb);
rdLine("post_cmd = ", line, &share->postCmd);
rdLine("pix_threshold = ", line, &share->pixThresh);
rdLine("color_threshold = ", line, &share->colorThresh);
rdLine("consec_threshold = ", line, &share->consecThresh);
rdLine("duration = ", line, &share->secs);
rdLine("secs_post_motion = ", line, &share->postMoIncr);
rdLine("detect_fps = ", line, &share->detectFps);
}
} while(!line.empty());
ret = true;
share->retCode = 0;
2022-04-14 09:45:54 -04:00
}
varFile.close();
return ret;
}
void moDetect(shared_t *share)
{
while (rdConf(share))
{
for (auto i = 0; i < (share->secs * share->detectFps); ++i)
{
Mat frame;
if (!share->camera.isOpened())
{
share->camera.open(share->detectUrl, CAP_FFMPEG);
}
share->camera >> frame;
if (frame.empty())
{
// 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.
share->camera.open(share->detectUrl, CAP_FFMPEG);
}
else if (imgDiff(frame, share))
{
wrOut(share); i = 0;
}
else
{
usleep(1000000 / share->detectFps);
}
}
system(share->postCmd.c_str());
}
}
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
{
showHelp();
2022-04-14 09:45:54 -04:00
}
else if (sharedRes.conf.empty())
2022-04-14 09:45:54 -04:00
{
cerr << "err: a config file was not given in -c" << endl;
2022-04-14 09:45:54 -04:00
}
else
{
moDetect(&sharedRes);
2022-04-14 09:45:54 -04:00
return sharedRes.retCode;
2022-04-14 09:45:54 -04:00
}
return EINVAL;
2022-04-14 09:45:54 -04:00
}