update change the motion detection to optical flow calculations

This commit is contained in:
Maurice ONeal 2022-04-22 09:43:07 -04:00
parent 91b950df74
commit 8cfa1fccde
2 changed files with 94 additions and 66 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 2.8) cmake_minimum_required(VERSION 2.8)
project( MotionWatch ) project( MotionWatch )
find_package( OpenCV REQUIRED ) find_package( OpenCV REQUIRED )
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pthread")
include_directories( ${OpenCV_INCLUDE_DIRS} ) include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( mow src/main.cpp ) add_executable( mow src/main.cpp )
target_link_libraries( mow ${OpenCV_LIBS} ) target_link_libraries( mow ${OpenCV_LIBS} )

View File

@ -17,17 +17,17 @@ using namespace std;
struct shared_t struct shared_t
{ {
vector<Mat> buff;
vector<thread> writers;
string detectUrl; string detectUrl;
string recordUrl; string recordUrl;
string outDir; string outDir;
string postMoCmd; string postMoCmd;
string postNoMoCmd; string postNoMoCmd;
string secsStr; string secsStr;
string camId; bool wrRunning;
bool ffRunning; bool ffRunning;
bool motion;
int secs; int secs;
int ignore;
} sharedRes; } sharedRes;
@ -73,6 +73,10 @@ bool createDirTree(const string &full_path)
void vidCap(shared_t *share) void vidCap(shared_t *share)
{ {
if (share->buff.size() >= 30)
{
share->wrRunning = true;
time_t rawtime; time_t rawtime;
time(&rawtime); time(&rawtime);
@ -83,41 +87,35 @@ void vidCap(shared_t *share)
char fileName[20]; char fileName[20];
strftime(dirName, 20, "%Y%m%d", timeinfo); strftime(dirName, 20, "%Y%m%d", timeinfo);
strftime(fileName, 20, "%H%M%S.ts", timeinfo); strftime(fileName, 20, "%H%M%S.avi", timeinfo);
auto tmpFile = string("/tmp/mow/") + share->camId + ".ts";
auto ffmpegCmd = string("ffmpeg -hide_banner -loglevel error -i ") + share->recordUrl + string(" -t ") + share->secsStr + string(" -y -vcodec copy ") + tmpFile;
createDirTree(string("/tmp/mow"));
system(ffmpegCmd.c_str());
share->ffRunning = false;
if (share->motion)
{
if (!share->postMoCmd.empty())
{
system(share->postMoCmd.c_str());
}
createDirTree(cleanDir(share->outDir) + string("/") + string(dirName)); createDirTree(cleanDir(share->outDir) + string("/") + string(dirName));
auto dstPath = cleanDir(share->outDir) + string("/") + string(dirName) + string("/") + string(fileName); auto dstPath = cleanDir(share->outDir) + string("/") + string(dirName) + string("/") + string(fileName);
auto codec = VideoWriter::fourcc('M', 'J', 'P', 'G');
auto fps = 25.0;
system(string("mv " + tmpFile + " " + dstPath).c_str()); 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 else
{ {
if (!share->postNoMoCmd.empty()) for (; !share->buff.empty(); share->buff.erase(share->buff.begin()))
{ {
system(share->postNoMoCmd.c_str()); writer.write(share->buff[0]);
}
system(string("rm " + tmpFile).c_str());
} }
} }
void detectDiff(const Mat &prev, const Mat &next, shared_t *share) share->wrRunning = false;
}
}
bool detectDiff(const Mat &prev, const Mat &next, shared_t *share)
{ {
// optical flow calculations are used to detect motion. // optical flow calculations are used to detect motion.
// reference: https://docs.opencv.org/3.4/d4/dee/tutorial_optical_flow.html // reference: https://docs.opencv.org/3.4/d4/dee/tutorial_optical_flow.html
@ -135,14 +133,14 @@ void detectDiff(const Mat &prev, const Mat &next, shared_t *share)
goodFeaturesToTrack(prev, p0, 100, 0.3, 7, Mat(), 7, false, 0.04); goodFeaturesToTrack(prev, p0, 100, 0.3, 7, Mat(), 7, false, 0.04);
calcOpticalFlowPyrLK(prev, next, p0, p1, status, err, Size(10, 10), 2, criteria); calcOpticalFlowPyrLK(prev, next, p0, p1, status, err, Size(10, 10), 2, criteria);
for(uint i = 0; (i < p0.size()) && !share->motion; i++) for(uint i = 0; i < p0.size(); i++)
{ {
// select good points // select good points
if(status[i] == 1) if(status[i] == 1)
{ {
if (count == 5) if (count == 5)
{ {
share->motion = true; return true;
} }
else if (norm(p0[i] - p1[i]) > distance) else if (norm(p0[i] - p1[i]) > distance)
{ {
@ -156,38 +154,74 @@ void detectDiff(const Mat &prev, const Mat &next, shared_t *share)
} }
} }
} }
return false;
}
void timer(shared_t *share)
{
sleep(share->secs);
share->ffRunning = false;
if (!share->wrRunning)
{
new thread(vidCap, share);
}
} }
void moDetect(shared_t *share) void moDetect(shared_t *share)
{ {
auto cap = VideoCapture(share->detectUrl, CAP_FFMPEG); auto dCap = VideoCapture(share->detectUrl, CAP_FFMPEG);
auto rCap = VideoCapture(share->recordUrl, CAP_FFMPEG);
auto mod = false;
Mat firstFrame, currentFrame, frame; Mat dPrevFrame, dNextFrame, dFrame, rFrame;
while (share->ffRunning && !share->motion) while (share->ffRunning)
{ {
cap >> frame; dCap >> dFrame;
rCap >> rFrame;
if (frame.empty()) if (dFrame.empty())
{ {
// broken frames returned from the cameras i've tested this with would cause // 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 // 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 // to detect empty frames (signs of a dropped connection) and attempt
// re-connect to the cammera. // re-connect to the cammera.
cap.open(share->detectUrl, CAP_FFMPEG); dCap.open(share->detectUrl, CAP_FFMPEG);
} }
else if (firstFrame.empty()) else if (rFrame.empty())
{ {
cvtColor(frame, firstFrame, COLOR_BGR2GRAY); rCap.open(share->recordUrl, CAP_FFMPEG);
}
else if (dPrevFrame.empty())
{
cvtColor(dFrame, dPrevFrame, COLOR_BGR2GRAY);
} }
else else
{ {
cvtColor(frame, currentFrame, COLOR_BGR2GRAY); cvtColor(dFrame, dNextFrame, COLOR_BGR2GRAY);
detectDiff(firstFrame, currentFrame, share);
if (detectDiff(dPrevFrame, dNextFrame, share))
{
mod = true;
share->buff.push_back(rFrame.clone());
} }
} }
} }
if (mod)
{
system(share->postMoCmd.c_str());
}
else
{
system(share->postNoMoCmd.c_str());
}
}
string parseForParam(const string &arg, int argc, char** argv) string parseForParam(const string &arg, int argc, char** argv)
{ {
for (int i = 0; i < argc; ++i) for (int i = 0; i < argc; ++i)
@ -217,7 +251,6 @@ int main(int argc, char** argv)
auto outDir = parseForParam("-dir", argc, argv); auto outDir = parseForParam("-dir", argc, argv);
auto moCmd = parseForParam("-mc", argc, argv); auto moCmd = parseForParam("-mc", argc, argv);
auto noMocmd = parseForParam("-nmc", argc, argv); auto noMocmd = parseForParam("-nmc", argc, argv);
auto camId = parseForParam("-id", argc, argv);
auto secs = strtol(secsStr.c_str(), NULL, 10); auto secs = strtol(secsStr.c_str(), NULL, 10);
if (lowUrl.empty()) if (lowUrl.empty())
@ -232,16 +265,14 @@ int main(int argc, char** argv)
{ {
cerr << "the output directory is empty." << endl; cerr << "the output directory is empty." << endl;
} }
else if (camId.empty())
{
cerr << "the camera id is empty." << endl;
}
else if (secs == 0) else if (secs == 0)
{ {
cerr << "the amount of seconds in -sec cannot be 0 or an invalid number was given." << endl; cerr << "the amount of seconds in -sec cannot be 0 or an invalid number was given." << endl;
} }
else else
{ {
sharedRes.wrRunning = false;
while (true) while (true)
{ {
sharedRes.recordUrl = highUrl; sharedRes.recordUrl = highUrl;
@ -251,12 +282,9 @@ int main(int argc, char** argv)
sharedRes.secsStr = secsStr; sharedRes.secsStr = secsStr;
sharedRes.secs = secs; sharedRes.secs = secs;
sharedRes.outDir = outDir; sharedRes.outDir = outDir;
sharedRes.camId = camId;
sharedRes.ffRunning = true; sharedRes.ffRunning = true;
sharedRes.motion = false;
sharedRes.ignore = 0;
thread th1(vidCap, &sharedRes); thread th1(timer, &sharedRes);
thread th2(moDetect, &sharedRes); thread th2(moDetect, &sharedRes);
// Wait for the threads to finish // Wait for the threads to finish