v1.5.t1
Decided to switch using opencv's builtin pixel diff motion detection via absdiff and thresh. Doing this should increase efficiency instead of using the home brewed pixel loops and threads. Added a web interface of sorts by having html files output along with the video clips. These files are designed to link together with the assumption that the output directory is a web root like /var/www/html that apache2 uses. The interface is crude at best but at least allow playback of recorded footage. Added max_clips config variable that can limit the amount of motion events that can recorded to storage on a single day.
This commit is contained in:
parent
01a11741c7
commit
9816ba339f
|
@ -1,7 +1,7 @@
|
||||||
cmake_minimum_required(VERSION 2.8.12)
|
cmake_minimum_required(VERSION 2.8.12)
|
||||||
project( MotionWatch )
|
project( MotionWatch )
|
||||||
find_package( OpenCV REQUIRED )
|
find_package( OpenCV REQUIRED )
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -pthread")
|
||||||
include_directories( ${OpenCV_INCLUDE_DIRS} )
|
include_directories( ${OpenCV_INCLUDE_DIRS} )
|
||||||
add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp )
|
add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/web.cpp )
|
||||||
target_link_libraries( mow ${OpenCV_LIBS} )
|
target_link_libraries( mow ${OpenCV_LIBS} )
|
||||||
|
|
6
setup.sh
6
setup.sh
|
@ -1,7 +1,11 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
apt update
|
apt update -y
|
||||||
apt install -y cmake g++ wget unzip git ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
|
apt install -y cmake g++ wget unzip git ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
|
||||||
|
add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||||
|
apt update -y
|
||||||
|
apt install -y gcc-10 gcc-10-base gcc-10-doc g++-10
|
||||||
|
apt install -y libstdc++-10-dev libstdc++-10-doc
|
||||||
cd ./src
|
cd ./src
|
||||||
if [ -d "./opencv" ]
|
if [ -d "./opencv" ]
|
||||||
then
|
then
|
||||||
|
|
153
src/common.cpp
153
src/common.cpp
|
@ -57,10 +57,9 @@ bool fileExists(const string& name)
|
||||||
return access(name.c_str(), F_OK) != -1;
|
return access(name.c_str(), F_OK) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceAll(string &str, const string &from, const string &to)
|
string replaceAll(string str, const string &from, const string &to)
|
||||||
{
|
{
|
||||||
if(from.empty())
|
if (from.empty()) return str;
|
||||||
return;
|
|
||||||
|
|
||||||
size_t startPos = 0;
|
size_t startPos = 0;
|
||||||
|
|
||||||
|
@ -70,6 +69,8 @@ void replaceAll(string &str, const string &from, const string &to)
|
||||||
|
|
||||||
startPos += to.length();
|
startPos += to.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> lsFilesInDir(const string &path, const string &ext)
|
vector<string> lsFilesInDir(const string &path, const string &ext)
|
||||||
|
@ -81,12 +82,12 @@ vector<string> lsFilesInDir(const string &path, const string &ext)
|
||||||
if ((dir = opendir(path.c_str())) != NULL)
|
if ((dir = opendir(path.c_str())) != NULL)
|
||||||
{
|
{
|
||||||
while ((ent = readdir(dir)) != NULL)
|
while ((ent = readdir(dir)) != NULL)
|
||||||
|
{
|
||||||
|
if (ent->d_type & DT_REG)
|
||||||
{
|
{
|
||||||
auto name = string(ent->d_name);
|
auto name = string(ent->d_name);
|
||||||
|
|
||||||
if ((name.size() >= 4) && (ent->d_type & DT_REG))
|
if (name.ends_with(ext.c_str()) || ext.empty())
|
||||||
{
|
|
||||||
if (name.substr(name.size() - 4) == ext)
|
|
||||||
{
|
{
|
||||||
names.push_back(name);
|
names.push_back(name);
|
||||||
}
|
}
|
||||||
|
@ -101,19 +102,54 @@ vector<string> lsFilesInDir(const string &path, const string &ext)
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
void enforceMaxDays(shared_t *share)
|
vector<string> lsDirsInDir(const string &path)
|
||||||
{
|
{
|
||||||
auto names = lsFilesInDir(share->outDir, ".m3u");
|
DIR *dir;
|
||||||
|
struct dirent *ent;
|
||||||
|
vector<string> names;
|
||||||
|
|
||||||
while (names.size() > share->maxDays)
|
if ((dir = opendir(path.c_str())) != NULL)
|
||||||
{
|
{
|
||||||
auto name = names[0];
|
while ((ent = readdir(dir)) != NULL)
|
||||||
auto plsFile = cleanDir(share->outDir) + "/" + name;
|
{
|
||||||
auto vidFold = cleanDir(share->outDir) + "/." + name.substr(0, name.size() - 4);
|
if (ent->d_type & DT_DIR)
|
||||||
|
{
|
||||||
|
names.push_back(string(ent->d_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
remove(plsFile.c_str());
|
closedir(dir);
|
||||||
remove_all(vidFold.c_str());
|
}
|
||||||
|
|
||||||
|
sort(names.begin(), names.end());
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enforceMaxDays(const string &dirPath, shared_t *share)
|
||||||
|
{
|
||||||
|
auto names = lsDirsInDir(dirPath);
|
||||||
|
|
||||||
|
while (names.size() > (share->maxDays - 1))
|
||||||
|
{
|
||||||
|
remove_all(string(cleanDir(dirPath) + "/" + names[0]).c_str());
|
||||||
|
|
||||||
|
names.erase(names.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void enforceMaxClips(const string &dirPath, shared_t *share)
|
||||||
|
{
|
||||||
|
auto names = lsDirsInDir(dirPath);
|
||||||
|
|
||||||
|
while ((names.size() * 3) > ((share->maxClips - 1) * 3))
|
||||||
|
{
|
||||||
|
remove(string(cleanDir(dirPath) + "/" + names[0]).c_str());
|
||||||
|
remove(string(cleanDir(dirPath) + "/" + names[1]).c_str());
|
||||||
|
remove(string(cleanDir(dirPath) + "/" + names[2]).c_str());
|
||||||
|
|
||||||
|
names.erase(names.begin());
|
||||||
|
names.erase(names.begin());
|
||||||
names.erase(names.begin());
|
names.erase(names.begin());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,22 +176,11 @@ string genDstFile(const string &dirOut, const char *fmt, const string &ext)
|
||||||
return cleanDir(dirOut) + string("/") + genTimeStr(fmt) + ext;
|
return cleanDir(dirOut) + string("/") + genTimeStr(fmt) + ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mat toGray(const Mat &src)
|
|
||||||
{
|
|
||||||
Mat ret;
|
|
||||||
|
|
||||||
cvtColor(src, ret, COLOR_BGR2GRAY);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rdLine(const string ¶m, const string &line, string *value)
|
void rdLine(const string ¶m, const string &line, string *value)
|
||||||
{
|
{
|
||||||
if (line.rfind(param.c_str(), 0) == 0)
|
if (line.rfind(param.c_str(), 0) == 0)
|
||||||
{
|
{
|
||||||
*value = line.substr(param.size());
|
*value = line.substr(param.size());
|
||||||
|
|
||||||
//cout << param << *value << endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,8 +189,6 @@ void rdLine(const string ¶m, const string &line, int *value)
|
||||||
if (line.rfind(param.c_str(), 0) == 0)
|
if (line.rfind(param.c_str(), 0) == 0)
|
||||||
{
|
{
|
||||||
*value = strtol(line.substr(param.size()).c_str(), NULL, 10);
|
*value = strtol(line.substr(param.size()).c_str(), NULL, 10);
|
||||||
|
|
||||||
//cout << param << *value << endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,16 +207,17 @@ bool rdConf(shared_t *share)
|
||||||
string line;
|
string line;
|
||||||
|
|
||||||
share->recordUrl.clear();
|
share->recordUrl.clear();
|
||||||
share->outDir.clear();
|
|
||||||
share->postCmd.clear();
|
share->postCmd.clear();
|
||||||
share->buffDir.clear();
|
share->buffDir.clear();
|
||||||
|
|
||||||
share->consecThresh = 512;
|
share->frameGap = 10;
|
||||||
|
share->pixThresh = 30;
|
||||||
|
share->imgThresh = 512;
|
||||||
share->secs = 60;
|
share->secs = 60;
|
||||||
share->blockX = 32;
|
share->maxDays = 15;
|
||||||
share->blockY = 32;
|
share->maxClips = 30;
|
||||||
share->blockThresh = 1024;
|
share->camName = path(share->conf.c_str()).filename();
|
||||||
share->maxDays = 5;
|
share->outDir = "/var/www/hmtl";
|
||||||
share->vidExt = "mp4";
|
share->vidExt = "mp4";
|
||||||
share->recLoopWait = false;
|
share->recLoopWait = false;
|
||||||
share->skipCmd = false;
|
share->skipCmd = false;
|
||||||
|
@ -204,26 +228,23 @@ bool rdConf(shared_t *share)
|
||||||
|
|
||||||
if (line.rfind("#", 0) != 0)
|
if (line.rfind("#", 0) != 0)
|
||||||
{
|
{
|
||||||
|
rdLine("cam_name = ", line, &share->camName);
|
||||||
rdLine("recording_stream = ", line, &share->recordUrl);
|
rdLine("recording_stream = ", line, &share->recordUrl);
|
||||||
rdLine("output_dir = ", line, &share->outDir);
|
rdLine("output_dir = ", line, &share->outDir);
|
||||||
rdLine("post_cmd = ", line, &share->postCmd);
|
rdLine("post_cmd = ", line, &share->postCmd);
|
||||||
rdLine("consec_threshold = ", line, &share->consecThresh);
|
|
||||||
rdLine("duration = ", line, &share->secs);
|
rdLine("duration = ", line, &share->secs);
|
||||||
rdLine("buff_dir = ", line, &share->buffDir);
|
rdLine("buff_dir = ", line, &share->buffDir);
|
||||||
rdLine("block_x = ", line, &share->blockX);
|
rdLine("frame_gap = ", line, &share->frameGap);
|
||||||
rdLine("block_y = ", line, &share->blockY);
|
rdLine("pix_thresh = ", line, &share->pixThresh);
|
||||||
rdLine("block_threshold = ", line, &share->blockThresh);
|
rdLine("img_thresh = ", line, &share->imgThresh);
|
||||||
rdLine("max_days = ", line, &share->maxDays);
|
rdLine("max_days = ", line, &share->maxDays);
|
||||||
|
rdLine("max_clips = ", line, &share->maxClips);
|
||||||
rdLine("vid_container = ", line, &share->vidExt);
|
rdLine("vid_container = ", line, &share->vidExt);
|
||||||
}
|
}
|
||||||
|
|
||||||
} while(!line.empty());
|
} while(!line.empty());
|
||||||
|
|
||||||
// it's imperative that blockX/Y are not zero or it will cause
|
share->outDir = cleanDir(share->outDir) + "/" + share->camName;
|
||||||
// an infinte loop. if bad data is read from the conf, default
|
|
||||||
// values will be used.
|
|
||||||
if (share->blockX == 0) share->blockX = 32;
|
|
||||||
if (share->blockY == 0) share->blockY = 32;
|
|
||||||
|
|
||||||
if (share->init)
|
if (share->init)
|
||||||
{
|
{
|
||||||
|
@ -232,10 +253,7 @@ bool rdConf(shared_t *share)
|
||||||
share->init = false;
|
share->init = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
new thread(enforceMaxDays, share);
|
|
||||||
|
|
||||||
createDirTree(cleanDir(share->buffDir));
|
createDirTree(cleanDir(share->buffDir));
|
||||||
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
|
|
||||||
|
|
||||||
share->retCode = 0;
|
share->retCode = 0;
|
||||||
}
|
}
|
||||||
|
@ -245,40 +263,6 @@ bool rdConf(shared_t *share)
|
||||||
return share->retCode == 0;
|
return share->retCode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool capPair(Mat &prev, Mat &next, VideoCapture &capture, shared_t *share)
|
|
||||||
{
|
|
||||||
capture >> prev;
|
|
||||||
capture >> next;
|
|
||||||
|
|
||||||
return !prev.empty() && !next.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void wrOut(const string &buffFile, shared_t *share)
|
|
||||||
{
|
|
||||||
auto clnDir = cleanDir(share->outDir);
|
|
||||||
auto vidOut = genDstFile(clnDir + "/." + genTimeStr("%Y-%m-%d"), "%H%M%S", "." + share->vidExt);
|
|
||||||
auto m3uOut = vidOut.substr(clnDir.size() + 1);
|
|
||||||
auto lisOut = genDstFile(share->outDir, "%Y-%m-%d", ".m3u");
|
|
||||||
|
|
||||||
copy_file(buffFile.c_str(), vidOut.c_str());
|
|
||||||
remove(buffFile.c_str());
|
|
||||||
|
|
||||||
ofstream file;
|
|
||||||
|
|
||||||
if (fileExists(lisOut))
|
|
||||||
{
|
|
||||||
file.open(lisOut.c_str(), ios_base::app);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
file.open(lisOut.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
file << m3uOut << endl;
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
|
string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < argc; ++i)
|
for (int i = 0; i < argc; ++i)
|
||||||
|
@ -304,14 +288,3 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
|
||||||
|
|
||||||
return string();
|
return string();
|
||||||
}
|
}
|
||||||
|
|
||||||
void statOut(shared_t *share)
|
|
||||||
{
|
|
||||||
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
|
|
||||||
|
|
||||||
auto path = string(cleanDir(share->buffDir) + "/stat");
|
|
||||||
auto fd = open(path.c_str(), fstream::out | fstream::trunc);
|
|
||||||
|
|
||||||
write(fd, share->stat.c_str(), share->stat.size() + 1);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
25
src/common.h
25
src/common.h
|
@ -36,27 +36,27 @@ using namespace cv;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace std::filesystem;
|
using namespace std::filesystem;
|
||||||
|
|
||||||
#define BUF_SZ 10
|
#define APP_VER "1.5.t1"
|
||||||
#define APP_VER "1.5"
|
#define APP_NAME "Motion Watch"
|
||||||
|
|
||||||
struct shared_t
|
struct shared_t
|
||||||
{
|
{
|
||||||
string stat;
|
|
||||||
string recordUrl;
|
string recordUrl;
|
||||||
string outDir;
|
string outDir;
|
||||||
string postCmd;
|
string postCmd;
|
||||||
string conf;
|
string conf;
|
||||||
string buffDir;
|
string buffDir;
|
||||||
string vidExt;
|
string vidExt;
|
||||||
|
string camName;
|
||||||
bool init;
|
bool init;
|
||||||
bool recLoopWait;
|
bool recLoopWait;
|
||||||
bool skipCmd;
|
bool skipCmd;
|
||||||
int consecThresh;
|
int frameGap;
|
||||||
|
int pixThresh;
|
||||||
|
int imgThresh;
|
||||||
int secs;
|
int secs;
|
||||||
int blockThresh;
|
|
||||||
int blockX;
|
|
||||||
int blockY;
|
|
||||||
int maxDays;
|
int maxDays;
|
||||||
|
int maxClips;
|
||||||
int retCode;
|
int retCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,18 +64,17 @@ string genDstFile(const string &dirOut, const char *fmt, const string &e
|
||||||
string genTimeStr(const char *fmt);
|
string genTimeStr(const char *fmt);
|
||||||
string cleanDir(const string &path);
|
string cleanDir(const string &path);
|
||||||
string parseForParam(const string &arg, int argc, char** argv, bool argOnly);
|
string parseForParam(const string &arg, int argc, char** argv, bool argOnly);
|
||||||
|
string replaceAll(string str, const string &from, const string &to);
|
||||||
bool createDir(const string &dir);
|
bool createDir(const string &dir);
|
||||||
bool createDirTree(const string &full_path);
|
bool createDirTree(const string &full_path);
|
||||||
bool fileExists(const string& name);
|
bool fileExists(const string& name);
|
||||||
void wrOut(const string &buffFile, shared_t *share);
|
void enforceMaxDays(const string &dirPath, shared_t *share);
|
||||||
void replaceAll(string &str, const string &from, const string &to);
|
void enforceMaxClips(const string &dirPath, shared_t *share);
|
||||||
void enforceMaxDays(shared_t *share);
|
|
||||||
void rdLine(const string ¶m, const string &line, string *value);
|
void rdLine(const string ¶m, const string &line, string *value);
|
||||||
void rdLine(const string ¶m, const string &line, int *value);
|
void rdLine(const string ¶m, const string &line, int *value);
|
||||||
void statOut(shared_t *share);
|
void statOut(shared_t *share);
|
||||||
bool rdConf(shared_t *share);
|
bool rdConf(shared_t *share);
|
||||||
bool capPair(Mat &prev, Mat &next, VideoCapture &capture, shared_t *share);
|
vector<string> lsFilesInDir(const string &path, const string &ext = string());
|
||||||
Mat toGray(const Mat &src);
|
vector<string> lsDirsInDir(const string &path);
|
||||||
vector<string> lsFilesInDir(const string &path, const string &ext);
|
|
||||||
|
|
||||||
#endif // COMMON_H
|
#endif // COMMON_H
|
||||||
|
|
44
src/main.cpp
44
src/main.cpp
|
@ -16,40 +16,54 @@ void detectLoop(shared_t *share)
|
||||||
{
|
{
|
||||||
vector<string> bufFiles;
|
vector<string> bufFiles;
|
||||||
|
|
||||||
while (!bufFiles.empty() || !share->recLoopWait)
|
do
|
||||||
{
|
{
|
||||||
bufFiles = lsFilesInDir(share->buffDir, "." + share->vidExt);
|
bufFiles = lsFilesInDir(share->buffDir, "." + share->vidExt);
|
||||||
|
|
||||||
|
// this loop will not process the last buffile while recLoop is still actively
|
||||||
|
// pulling footage from ffmpeg. it is assumed the last file is not finished.
|
||||||
|
// share->recLoopWait is used to detect if the recloop is still pulling. if not
|
||||||
|
// then the last file is finally processed.
|
||||||
|
|
||||||
if ((bufFiles.size() >= 2) || (share->recLoopWait && !bufFiles.empty()))
|
if ((bufFiles.size() >= 2) || (share->recLoopWait && !bufFiles.empty()))
|
||||||
{
|
{
|
||||||
auto fullPath = cleanDir(share->buffDir) + "/" + bufFiles[0];
|
auto fullPath = cleanDir(share->buffDir) + "/" + bufFiles[0];
|
||||||
|
|
||||||
share->stat.clear();
|
Mat thumbNail;
|
||||||
|
|
||||||
if (moDetect(fullPath, share))
|
if (moDetect(fullPath, thumbNail, share))
|
||||||
{
|
{
|
||||||
share->skipCmd = true;
|
share->skipCmd = true;
|
||||||
|
|
||||||
wrOut(fullPath, share);
|
wrOut(fullPath, thumbNail, share);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
remove(fullPath.c_str());
|
remove(fullPath.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
statOut(share);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (!bufFiles.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
void recLoop(shared_t *share)
|
void recLoop(shared_t *share)
|
||||||
{
|
{
|
||||||
while (rdConf(share))
|
while (rdConf(share))
|
||||||
{
|
{
|
||||||
|
if (!fileExists("/tmp/mow-lock"))
|
||||||
|
{
|
||||||
|
system("touch /tmp/mow-lock");
|
||||||
|
|
||||||
|
auto webRoot = path(share->outDir).parent_path().string();
|
||||||
|
|
||||||
|
genHTMLul(webRoot, string(APP_NAME) + " " + string(APP_VER));
|
||||||
|
system("rm /tmp/mow-lock");
|
||||||
|
}
|
||||||
|
|
||||||
createDirTree(share->buffDir);
|
createDirTree(share->buffDir);
|
||||||
|
|
||||||
auto bufPath = cleanDir(share->buffDir) + "/%03d." + share->vidExt;
|
auto bufPath = cleanDir(share->buffDir) + "/%03d." + share->vidExt;
|
||||||
|
@ -59,11 +73,19 @@ void recLoop(shared_t *share)
|
||||||
|
|
||||||
thread th2(detectLoop, share);
|
thread th2(detectLoop, share);
|
||||||
|
|
||||||
system(cmd.c_str());
|
if (system(cmd.c_str()) != 0)
|
||||||
|
{
|
||||||
share->recLoopWait = true;
|
share->recLoopWait = true;
|
||||||
|
|
||||||
th2.join();
|
th2.join();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// simulate that ffmpeg is running even after it has failed.
|
||||||
|
sleep(share->secs);
|
||||||
|
|
||||||
|
share->recLoopWait = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!share->skipCmd)
|
if (!share->skipCmd)
|
||||||
{
|
{
|
||||||
|
@ -86,7 +108,7 @@ int main(int argc, char** argv)
|
||||||
cout << "-c : path to the config file." << endl;
|
cout << "-c : path to the config file." << endl;
|
||||||
cout << "-v : display the current version." << endl;
|
cout << "-v : display the current version." << endl;
|
||||||
}
|
}
|
||||||
if (parseForParam("-v", argc, argv, true) == "true")
|
else if (parseForParam("-v", argc, argv, true) == "true")
|
||||||
{
|
{
|
||||||
cout << APP_VER << endl;
|
cout << APP_VER << endl;
|
||||||
}
|
}
|
||||||
|
@ -101,9 +123,7 @@ int main(int argc, char** argv)
|
||||||
sharedRes.skipCmd = false;
|
sharedRes.skipCmd = false;
|
||||||
sharedRes.init = true;
|
sharedRes.init = true;
|
||||||
|
|
||||||
thread th1(recLoop, &sharedRes);
|
recLoop(&sharedRes);
|
||||||
|
|
||||||
th1.join();
|
|
||||||
|
|
||||||
return sharedRes.retCode;
|
return sharedRes.retCode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,101 +12,64 @@
|
||||||
|
|
||||||
#include "mo_detect.h"
|
#include "mo_detect.h"
|
||||||
|
|
||||||
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share)
|
|
||||||
{
|
|
||||||
if (pixA > pixB) return true;
|
|
||||||
if (pixB > pixA) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void secDiff(const Mat &imgA, const Mat &imgB, int id, int rows, int cols, int rowOffs, int colOffs, vector<sec_t> *results, mutex *secMutex, shared_t *share)
|
|
||||||
{
|
|
||||||
auto diff = 0;
|
|
||||||
auto pnts = 0;
|
|
||||||
|
|
||||||
for (auto y = rowOffs; y < (rowOffs + rows); y++)
|
|
||||||
{
|
|
||||||
for (auto x = colOffs; x < (colOffs + cols); x++)
|
|
||||||
{
|
|
||||||
auto pixA = imgA.at<uchar>(Point(x, y));
|
|
||||||
auto pixB = imgB.at<uchar>(Point(x, y));
|
|
||||||
|
|
||||||
if (pixDiff(pixA, pixB, share)) pnts += 1;
|
|
||||||
else pnts = 0;
|
|
||||||
|
|
||||||
if (pnts >= share->consecThresh)
|
|
||||||
{
|
|
||||||
diff += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sec_t res;
|
|
||||||
|
|
||||||
res.x = colOffs;
|
|
||||||
res.y = rowOffs;
|
|
||||||
res.xSize = cols;
|
|
||||||
res.ySize = rows;
|
|
||||||
res.pixDiff = diff;
|
|
||||||
res.id = id;
|
|
||||||
|
|
||||||
lock_guard<mutex> guard(*secMutex);
|
|
||||||
|
|
||||||
results->push_back(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
|
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
|
||||||
{
|
{
|
||||||
vector<thread> threads;
|
Mat diff;
|
||||||
vector<sec_t> results;
|
|
||||||
mutex secMutex;
|
|
||||||
|
|
||||||
auto id = 0;
|
absdiff(prev, next, diff);
|
||||||
|
threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY);
|
||||||
|
|
||||||
for (auto x = 0; x < prev.cols; x += share->blockX)
|
return countNonZero(diff) >= share->imgThresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat frameFF(VideoCapture *cap, int gap)
|
||||||
{
|
{
|
||||||
// spawn all of the block motion detection threads.
|
Mat ret;
|
||||||
for (auto y = 0; y < prev.rows; y += share->blockY, id += 1)
|
|
||||||
|
if (gap == 0) gap = 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < gap; ++i)
|
||||||
{
|
{
|
||||||
threads.push_back(thread(secDiff, prev, next, id, share->blockY, share->blockX, y, x, &results, &secMutex, share));
|
cap->grab();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &&thr : threads)
|
cap->retrieve(ret);
|
||||||
|
|
||||||
|
if (!ret.empty())
|
||||||
{
|
{
|
||||||
// wait for all of the threads to finish.
|
cvtColor(ret, ret, COLOR_BGR2GRAY);
|
||||||
thr.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto maxPixDiff = 0;
|
return ret;
|
||||||
auto blockPick = 0;
|
}
|
||||||
|
|
||||||
for (auto i = 0; i < results.size(); ++i)
|
void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share)
|
||||||
{
|
{
|
||||||
// out of all of the results returned form the threads, pick
|
auto dayStr = genTimeStr("%Y-%m-%d");
|
||||||
// the block with the highest amount of pixDiff.
|
auto timStr = genTimeStr("%H%M%S");
|
||||||
auto x = results[i].x;
|
auto outDir = cleanDir(share->outDir) + "/" + dayStr;
|
||||||
auto y = results[i].y;
|
|
||||||
auto diff = results[i].pixDiff;
|
|
||||||
auto id = results[i].id;
|
|
||||||
|
|
||||||
if (diff > 0)
|
if (!exists(outDir))
|
||||||
{
|
{
|
||||||
share->stat += string("block_thread:") + " id=" + to_string(id) + " diff=" + to_string(diff) + "\n";
|
enforceMaxDays(share->outDir, share);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((results[i].pixDiff >= share->blockThresh) && (results[i].pixDiff > maxPixDiff))
|
auto vidOut = genDstFile(outDir, timStr.c_str(), "." + share->vidExt);
|
||||||
{
|
auto imgOut = genDstFile(outDir, timStr.c_str(), ".jpg");
|
||||||
maxPixDiff = results[i].pixDiff;
|
|
||||||
blockPick = i;
|
enforceMaxClips(outDir, share);
|
||||||
}
|
|
||||||
|
copy_file(buffFile.c_str(), vidOut.c_str());
|
||||||
|
remove(buffFile.c_str());
|
||||||
|
|
||||||
|
imwrite(imgOut.c_str(), vidThumb);
|
||||||
|
|
||||||
|
genHTMLvid(vidOut, share);
|
||||||
|
genHTMLul(outDir, share->camName + ": " + dayStr);
|
||||||
|
genHTMLul(share->outDir, share->camName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxPixDiff >= share->blockThresh;
|
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
|
||||||
}
|
|
||||||
|
|
||||||
bool moDetect(const string &buffFile, shared_t *share)
|
|
||||||
{
|
{
|
||||||
auto mod = false;
|
auto mod = false;
|
||||||
|
|
||||||
|
@ -117,13 +80,21 @@ bool moDetect(const string &buffFile, shared_t *share)
|
||||||
Mat prev;
|
Mat prev;
|
||||||
Mat next;
|
Mat next;
|
||||||
|
|
||||||
while (capPair(prev, next, capture, share))
|
do
|
||||||
{
|
{
|
||||||
if (imgDiff(toGray(prev), toGray(next), share))
|
if (prev.empty()) prev = frameFF(&capture, 1);
|
||||||
|
else prev = next.clone();
|
||||||
|
|
||||||
|
next = frameFF(&capture, share->frameGap);
|
||||||
|
|
||||||
|
if (imgDiff(prev, next, share))
|
||||||
{
|
{
|
||||||
|
resize(next, vidThumb, Size(360, 202), INTER_LINEAR);
|
||||||
|
|
||||||
mod = true; break;
|
mod = true; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (!prev.empty() && !next.empty());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,20 +14,11 @@
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "web.h"
|
||||||
|
|
||||||
struct sec_t
|
|
||||||
{
|
|
||||||
int id;
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
int xSize;
|
|
||||||
int ySize;
|
|
||||||
int pixDiff;
|
|
||||||
};
|
|
||||||
|
|
||||||
void secDiff(const Mat &imgA, const Mat &imgB, int id, int rows, int cols, int rowOffs, int colOffs, vector<sec_t> *results, mutex *secMutex, shared_t *share);
|
|
||||||
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share);
|
|
||||||
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share);
|
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share);
|
||||||
bool moDetect(const string &buffFile, shared_t *share);
|
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share);
|
||||||
|
void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share);
|
||||||
|
Mat frameFF(VideoCapture *cap, int gap);
|
||||||
|
|
||||||
#endif // MO_DETECT_H
|
#endif // MO_DETECT_H
|
||||||
|
|
94
src/web.cpp
Normal file
94
src/web.cpp
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
|
// Motion Watch is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
#include "web.h"
|
||||||
|
|
||||||
|
void genHTMLul(const string &outputDir, const string &title)
|
||||||
|
{
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *ent;
|
||||||
|
vector<string> regNames;
|
||||||
|
vector<string> dirNames;
|
||||||
|
|
||||||
|
string htmlText = "<!DOCTYPE html>\n";
|
||||||
|
|
||||||
|
htmlText += "<html>\n";
|
||||||
|
htmlText += "<body>\n";
|
||||||
|
htmlText += "<h2>" + title + "</h2>\n";
|
||||||
|
htmlText += "<ul>\n";
|
||||||
|
|
||||||
|
if ((dir = opendir(outputDir.c_str())) != NULL)
|
||||||
|
{
|
||||||
|
while ((ent = readdir(dir)) != NULL)
|
||||||
|
{
|
||||||
|
if (ent->d_type & DT_REG)
|
||||||
|
{
|
||||||
|
regNames.push_back(string(ent->d_name));
|
||||||
|
}
|
||||||
|
else if (ent->d_type & DT_DIR)
|
||||||
|
{
|
||||||
|
dirNames.push_back(string(ent->d_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(regNames.begin(), regNames.end());
|
||||||
|
sort(dirNames.begin(), dirNames.end());
|
||||||
|
|
||||||
|
for (auto dirName : dirNames)
|
||||||
|
{
|
||||||
|
htmlText += " <li><a href='" + dirName + "'>" + dirName + "</a></li>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto regName : regNames)
|
||||||
|
{
|
||||||
|
if (regName.ends_with(".html") && !regName.ends_with("index.html"))
|
||||||
|
{
|
||||||
|
htmlText += " <li><a href='" + regName + "'> <img src='" + replaceAll(regName, ".html", ".jpg") + "' </a></li>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlText += "</ul>\n";
|
||||||
|
htmlText += "</body>\n";
|
||||||
|
htmlText += "</html>";
|
||||||
|
|
||||||
|
ofstream file(string(cleanDir(outputDir) + "/index.html").c_str());
|
||||||
|
|
||||||
|
file << htmlText << endl;
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void genHTMLvid(const string &outputVid, shared_t *share)
|
||||||
|
{
|
||||||
|
auto filename = path(outputVid).filename().string();
|
||||||
|
auto filePath = path(outputVid).parent_path().string();
|
||||||
|
string htmlText = "<!DOCTYPE html>\n";
|
||||||
|
|
||||||
|
filename = replaceAll(filename, share->vidExt, "html");
|
||||||
|
|
||||||
|
htmlText += "<html>\n";
|
||||||
|
htmlText += "<body>\n";
|
||||||
|
htmlText += "<video width='420' height='320' controls autoplay>\n";
|
||||||
|
htmlText += " <source src='" + filename + "' type='video/" + share->vidExt + "'>\n";
|
||||||
|
htmlText += "</video>\n";
|
||||||
|
htmlText += "</body>\n";
|
||||||
|
htmlText += "</html>";
|
||||||
|
|
||||||
|
ofstream file(string(cleanDir(filePath) + "/" + filename + ".html").c_str());
|
||||||
|
|
||||||
|
file << htmlText << endl;
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
21
src/web.h
Normal file
21
src/web.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef WEB_H
|
||||||
|
#define WEB_H
|
||||||
|
|
||||||
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
|
// Motion Watch is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
void genHTMLul(const string &outputDir, const string &title);
|
||||||
|
void genHTMLvid(const string &outputVid, shared_t *share);
|
||||||
|
|
||||||
|
#endif // WEB_H
|
Loading…
Reference in New Issue
Block a user