JustMotion/src/main.cpp

302 lines
7.7 KiB
C++
Raw Normal View History

2022-04-14 09:45:54 -04:00
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h>
#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
{
vector<Mat> buff;
vector<thread> writers;
string detectUrl;
string recordUrl;
string outDir;
string postMoCmd;
string postNoMoCmd;
string secsStr;
bool wrRunning;
bool ffRunning;
int secs;
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)
{
if (share->buff.size() >= 30)
{
share->wrRunning = true;
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.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);
auto codec = VideoWriter::fourcc('M', 'J', 'P', 'G');
auto fps = 25.0;
2022-04-14 09:45:54 -04:00
VideoWriter writer;
writer.open(dstPath, codec, fps, share->buff[0].size(), true);
if (!writer.isOpened())
{
cerr << "could not open the output video file for writing: " << dstPath;
}
else
2022-04-14 09:45:54 -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
}
share->wrRunning = false;
2022-04-14 09:45:54 -04:00
}
}
bool detectDiff(const Mat &prev, const Mat &next, shared_t *share)
2022-04-14 09:45:54 -04:00
{
// optical flow calculations are used to detect motion.
// reference: https://docs.opencv.org/3.4/d4/dee/tutorial_optical_flow.html
vector<Point2f> p0, p1;
vector<uchar> status;
vector<float> err;
auto criteria = TermCriteria((TermCriteria::COUNT) + (TermCriteria::EPS), 10, 0.03);
// distance is basically 0.0578% of the total pixel area of the
// frames. this value is used later below.
auto distance = ((double) 0.0578 / (double) 100) * (prev.size().height * prev.size().width);
auto count = 0;
goodFeaturesToTrack(prev, p0, 100, 0.3, 7, Mat(), 7, false, 0.04);
calcOpticalFlowPyrLK(prev, next, p0, p1, status, err, Size(10, 10), 2, criteria);
for(uint i = 0; i < p0.size(); i++)
2022-04-14 09:45:54 -04:00
{
// select good points
if(status[i] == 1)
{
if (count == 5)
{
return true;
2022-04-14 09:45:54 -04:00
}
else if (norm(p0[i] - p1[i]) > distance)
{
// any points that moved 0.0578% or more of the total pixel
// area can be considered motion.
// the count variable is there to make sure mutiple points
// are calling out motion. this prevents false positives
// due to insects or other small objects like grass, bush
// etc...
count += 1;
}
}
}
return false;
}
void timer(shared_t *share)
{
sleep(share->secs);
share->ffRunning = false;
if (!share->wrRunning)
{
new thread(vidCap, share);
}
2022-04-14 09:45:54 -04:00
}
void moDetect(shared_t *share)
{
auto dCap = VideoCapture(share->detectUrl, CAP_FFMPEG);
auto rCap = VideoCapture(share->recordUrl, CAP_FFMPEG);
auto mod = false;
2022-04-14 09:45:54 -04:00
Mat dPrevFrame, dNextFrame, dFrame, rFrame;
2022-04-14 09:45:54 -04:00
while (share->ffRunning)
2022-04-14 09:45:54 -04:00
{
dCap >> dFrame;
rCap >> rFrame;
2022-04-14 09:45:54 -04:00
if (dFrame.empty())
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.
dCap.open(share->detectUrl, CAP_FFMPEG);
2022-04-14 09:45:54 -04:00
}
else if (rFrame.empty())
2022-04-14 09:45:54 -04:00
{
rCap.open(share->recordUrl, CAP_FFMPEG);
}
else if (dPrevFrame.empty())
{
cvtColor(dFrame, dPrevFrame, COLOR_BGR2GRAY);
2022-04-14 09:45:54 -04:00
}
else
{
cvtColor(dFrame, dNextFrame, COLOR_BGR2GRAY);
if (detectDiff(dPrevFrame, dNextFrame, share))
{
mod = true;
share->buff.push_back(rFrame.clone());
}
2022-04-14 09:45:54 -04:00
}
}
if (mod)
{
system(share->postMoCmd.c_str());
}
else
{
system(share->postNoMoCmd.c_str());
}
2022-04-14 09:45:54 -04:00
}
string parseForParam(const string &arg, int argc, char** argv)
{
for (int i = 0; i < argc; ++i)
{
auto argInParams = string(argv[i]);
if (arg.compare(argInParams) == 0)
{
// check ahead, make sure i + 1 won't cause out-of-range exception
if ((i + 1) <= (argc - 1))
{
return string(argv[i + 1]);
}
}
}
return string();
}
int main(int argc, char** argv)
{
auto vidRet = 0;
auto moRet = 0;
auto secsStr = parseForParam("-sec", argc, argv);
auto highUrl = parseForParam("-rs", argc, argv);
auto lowUrl = parseForParam("-ds", argc, argv);
auto outDir = parseForParam("-dir", argc, argv);
auto moCmd = parseForParam("-mc", argc, argv);
auto noMocmd = parseForParam("-nmc", argc, argv);
auto secs = strtol(secsStr.c_str(), NULL, 10);
if (lowUrl.empty())
{
cerr << "the detection-stream camera url is empty." << endl;
}
else if (highUrl.empty())
{
cerr << "the recording-stream camera url is empty." << endl;
}
else if (outDir.empty())
{
cerr << "the output directory is empty." << endl;
}
else if (secs == 0)
{
cerr << "the amount of seconds in -sec cannot be 0 or an invalid number was given." << endl;
}
else
{
sharedRes.wrRunning = false;
2022-04-14 09:45:54 -04:00
while (true)
{
sharedRes.recordUrl = highUrl;
sharedRes.detectUrl = lowUrl;
sharedRes.postMoCmd = moCmd;
sharedRes.postNoMoCmd = noMocmd;
sharedRes.secsStr = secsStr;
sharedRes.secs = secs;
sharedRes.outDir = outDir;
sharedRes.ffRunning = true;
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();
}
return 0;
}
return 1;
}