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)
|
||||
project( MotionWatch )
|
||||
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} )
|
||||
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} )
|
||||
|
|
6
setup.sh
6
setup.sh
|
@ -1,7 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
apt update
|
||||
apt update -y
|
||||
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
|
||||
if [ -d "./opencv" ]
|
||||
then
|
||||
|
|
165
src/common.cpp
165
src/common.cpp
|
@ -57,19 +57,20 @@ bool fileExists(const string& name)
|
|||
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())
|
||||
return;
|
||||
if (from.empty()) return str;
|
||||
|
||||
size_t startPos = 0;
|
||||
|
||||
while((startPos = str.find(from, startPos)) != string::npos)
|
||||
while ((startPos = str.find(from, startPos)) != string::npos)
|
||||
{
|
||||
str.replace(startPos, from.length(), to);
|
||||
|
||||
startPos += to.length();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
vector<string> lsFilesInDir(const string &path, const string &ext)
|
||||
|
@ -82,11 +83,11 @@ vector<string> lsFilesInDir(const string &path, const string &ext)
|
|||
{
|
||||
while ((ent = readdir(dir)) != NULL)
|
||||
{
|
||||
auto name = string(ent->d_name);
|
||||
|
||||
if ((name.size() >= 4) && (ent->d_type & DT_REG))
|
||||
if (ent->d_type & DT_REG)
|
||||
{
|
||||
if (name.substr(name.size() - 4) == ext)
|
||||
auto name = string(ent->d_name);
|
||||
|
||||
if (name.ends_with(ext.c_str()) || ext.empty())
|
||||
{
|
||||
names.push_back(name);
|
||||
}
|
||||
|
@ -101,19 +102,54 @@ vector<string> lsFilesInDir(const string &path, const string &ext)
|
|||
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];
|
||||
auto plsFile = cleanDir(share->outDir) + "/" + name;
|
||||
auto vidFold = cleanDir(share->outDir) + "/." + name.substr(0, name.size() - 4);
|
||||
while ((ent = readdir(dir)) != NULL)
|
||||
{
|
||||
if (ent->d_type & DT_DIR)
|
||||
{
|
||||
names.push_back(string(ent->d_name));
|
||||
}
|
||||
}
|
||||
|
||||
remove(plsFile.c_str());
|
||||
remove_all(vidFold.c_str());
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -140,22 +176,11 @@ string genDstFile(const string &dirOut, const char *fmt, const string &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)
|
||||
{
|
||||
if (line.rfind(param.c_str(), 0) == 0)
|
||||
{
|
||||
*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)
|
||||
{
|
||||
*value = strtol(line.substr(param.size()).c_str(), NULL, 10);
|
||||
|
||||
//cout << param << *value << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,19 +207,20 @@ bool rdConf(shared_t *share)
|
|||
string line;
|
||||
|
||||
share->recordUrl.clear();
|
||||
share->outDir.clear();
|
||||
share->postCmd.clear();
|
||||
share->buffDir.clear();
|
||||
|
||||
share->consecThresh = 512;
|
||||
share->secs = 60;
|
||||
share->blockX = 32;
|
||||
share->blockY = 32;
|
||||
share->blockThresh = 1024;
|
||||
share->maxDays = 5;
|
||||
share->vidExt = "mp4";
|
||||
share->recLoopWait = false;
|
||||
share->skipCmd = false;
|
||||
share->frameGap = 10;
|
||||
share->pixThresh = 30;
|
||||
share->imgThresh = 512;
|
||||
share->secs = 60;
|
||||
share->maxDays = 15;
|
||||
share->maxClips = 30;
|
||||
share->camName = path(share->conf.c_str()).filename();
|
||||
share->outDir = "/var/www/hmtl";
|
||||
share->vidExt = "mp4";
|
||||
share->recLoopWait = false;
|
||||
share->skipCmd = false;
|
||||
|
||||
do
|
||||
{
|
||||
|
@ -204,26 +228,23 @@ bool rdConf(shared_t *share)
|
|||
|
||||
if (line.rfind("#", 0) != 0)
|
||||
{
|
||||
rdLine("cam_name = ", line, &share->camName);
|
||||
rdLine("recording_stream = ", line, &share->recordUrl);
|
||||
rdLine("output_dir = ", line, &share->outDir);
|
||||
rdLine("post_cmd = ", line, &share->postCmd);
|
||||
rdLine("consec_threshold = ", line, &share->consecThresh);
|
||||
rdLine("duration = ", line, &share->secs);
|
||||
rdLine("buff_dir = ", line, &share->buffDir);
|
||||
rdLine("block_x = ", line, &share->blockX);
|
||||
rdLine("block_y = ", line, &share->blockY);
|
||||
rdLine("block_threshold = ", line, &share->blockThresh);
|
||||
rdLine("frame_gap = ", line, &share->frameGap);
|
||||
rdLine("pix_thresh = ", line, &share->pixThresh);
|
||||
rdLine("img_thresh = ", line, &share->imgThresh);
|
||||
rdLine("max_days = ", line, &share->maxDays);
|
||||
rdLine("max_clips = ", line, &share->maxClips);
|
||||
rdLine("vid_container = ", line, &share->vidExt);
|
||||
}
|
||||
|
||||
} while(!line.empty());
|
||||
|
||||
// it's imperative that blockX/Y are not zero or it will cause
|
||||
// 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;
|
||||
share->outDir = cleanDir(share->outDir) + "/" + share->camName;
|
||||
|
||||
if (share->init)
|
||||
{
|
||||
|
@ -232,10 +253,7 @@ bool rdConf(shared_t *share)
|
|||
share->init = false;
|
||||
}
|
||||
|
||||
new thread(enforceMaxDays, share);
|
||||
|
||||
createDirTree(cleanDir(share->buffDir));
|
||||
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
|
||||
|
||||
share->retCode = 0;
|
||||
}
|
||||
|
@ -245,40 +263,6 @@ bool rdConf(shared_t *share)
|
|||
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)
|
||||
{
|
||||
for (int i = 0; i < argc; ++i)
|
||||
|
@ -304,14 +288,3 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
|
|||
|
||||
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::filesystem;
|
||||
|
||||
#define BUF_SZ 10
|
||||
#define APP_VER "1.5"
|
||||
#define APP_VER "1.5.t1"
|
||||
#define APP_NAME "Motion Watch"
|
||||
|
||||
struct shared_t
|
||||
{
|
||||
string stat;
|
||||
string recordUrl;
|
||||
string outDir;
|
||||
string postCmd;
|
||||
string conf;
|
||||
string buffDir;
|
||||
string vidExt;
|
||||
string camName;
|
||||
bool init;
|
||||
bool recLoopWait;
|
||||
bool skipCmd;
|
||||
int consecThresh;
|
||||
int frameGap;
|
||||
int pixThresh;
|
||||
int imgThresh;
|
||||
int secs;
|
||||
int blockThresh;
|
||||
int blockX;
|
||||
int blockY;
|
||||
int maxDays;
|
||||
int maxClips;
|
||||
int retCode;
|
||||
};
|
||||
|
||||
|
@ -64,18 +64,17 @@ string genDstFile(const string &dirOut, const char *fmt, const string &e
|
|||
string genTimeStr(const char *fmt);
|
||||
string cleanDir(const string &path);
|
||||
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 createDirTree(const string &full_path);
|
||||
bool fileExists(const string& name);
|
||||
void wrOut(const string &buffFile, shared_t *share);
|
||||
void replaceAll(string &str, const string &from, const string &to);
|
||||
void enforceMaxDays(shared_t *share);
|
||||
void enforceMaxDays(const string &dirPath, shared_t *share);
|
||||
void enforceMaxClips(const string &dirPath, shared_t *share);
|
||||
void rdLine(const string ¶m, const string &line, string *value);
|
||||
void rdLine(const string ¶m, const string &line, int *value);
|
||||
void statOut(shared_t *share);
|
||||
bool rdConf(shared_t *share);
|
||||
bool capPair(Mat &prev, Mat &next, VideoCapture &capture, shared_t *share);
|
||||
Mat toGray(const Mat &src);
|
||||
vector<string> lsFilesInDir(const string &path, const string &ext);
|
||||
vector<string> lsFilesInDir(const string &path, const string &ext = string());
|
||||
vector<string> lsDirsInDir(const string &path);
|
||||
|
||||
#endif // COMMON_H
|
||||
|
|
46
src/main.cpp
46
src/main.cpp
|
@ -16,40 +16,54 @@ void detectLoop(shared_t *share)
|
|||
{
|
||||
vector<string> bufFiles;
|
||||
|
||||
while (!bufFiles.empty() || !share->recLoopWait)
|
||||
do
|
||||
{
|
||||
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()))
|
||||
{
|
||||
auto fullPath = cleanDir(share->buffDir) + "/" + bufFiles[0];
|
||||
|
||||
share->stat.clear();
|
||||
Mat thumbNail;
|
||||
|
||||
if (moDetect(fullPath, share))
|
||||
if (moDetect(fullPath, thumbNail, share))
|
||||
{
|
||||
share->skipCmd = true;
|
||||
|
||||
wrOut(fullPath, share);
|
||||
wrOut(fullPath, thumbNail, share);
|
||||
}
|
||||
else
|
||||
{
|
||||
remove(fullPath.c_str());
|
||||
}
|
||||
|
||||
statOut(share);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
while (!bufFiles.empty());
|
||||
}
|
||||
|
||||
void recLoop(shared_t *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);
|
||||
|
||||
auto bufPath = cleanDir(share->buffDir) + "/%03d." + share->vidExt;
|
||||
|
@ -59,11 +73,19 @@ void recLoop(shared_t *share)
|
|||
|
||||
thread th2(detectLoop, share);
|
||||
|
||||
system(cmd.c_str());
|
||||
if (system(cmd.c_str()) != 0)
|
||||
{
|
||||
share->recLoopWait = true;
|
||||
|
||||
share->recLoopWait = true;
|
||||
th2.join();
|
||||
}
|
||||
else
|
||||
{
|
||||
// simulate that ffmpeg is running even after it has failed.
|
||||
sleep(share->secs);
|
||||
|
||||
th2.join();
|
||||
share->recLoopWait = true;
|
||||
}
|
||||
|
||||
if (!share->skipCmd)
|
||||
{
|
||||
|
@ -86,7 +108,7 @@ int main(int argc, char** argv)
|
|||
cout << "-c : path to the config file." << 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;
|
||||
}
|
||||
|
@ -101,9 +123,7 @@ int main(int argc, char** argv)
|
|||
sharedRes.skipCmd = false;
|
||||
sharedRes.init = true;
|
||||
|
||||
thread th1(recLoop, &sharedRes);
|
||||
|
||||
th1.join();
|
||||
recLoop(&sharedRes);
|
||||
|
||||
return sharedRes.retCode;
|
||||
}
|
||||
|
|
|
@ -12,101 +12,64 @@
|
|||
|
||||
#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)
|
||||
{
|
||||
vector<thread> threads;
|
||||
vector<sec_t> results;
|
||||
mutex secMutex;
|
||||
Mat diff;
|
||||
|
||||
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)
|
||||
{
|
||||
// spawn all of the block motion detection threads.
|
||||
for (auto y = 0; y < prev.rows; y += share->blockY, id += 1)
|
||||
{
|
||||
threads.push_back(thread(secDiff, prev, next, id, share->blockY, share->blockX, y, x, &results, &secMutex, share));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &&thr : threads)
|
||||
{
|
||||
// wait for all of the threads to finish.
|
||||
thr.join();
|
||||
}
|
||||
|
||||
auto maxPixDiff = 0;
|
||||
auto blockPick = 0;
|
||||
|
||||
for (auto i = 0; i < results.size(); ++i)
|
||||
{
|
||||
// out of all of the results returned form the threads, pick
|
||||
// the block with the highest amount of pixDiff.
|
||||
auto x = results[i].x;
|
||||
auto y = results[i].y;
|
||||
auto diff = results[i].pixDiff;
|
||||
auto id = results[i].id;
|
||||
|
||||
if (diff > 0)
|
||||
{
|
||||
share->stat += string("block_thread:") + " id=" + to_string(id) + " diff=" + to_string(diff) + "\n";
|
||||
}
|
||||
|
||||
if ((results[i].pixDiff >= share->blockThresh) && (results[i].pixDiff > maxPixDiff))
|
||||
{
|
||||
maxPixDiff = results[i].pixDiff;
|
||||
blockPick = i;
|
||||
}
|
||||
}
|
||||
|
||||
return maxPixDiff >= share->blockThresh;
|
||||
return countNonZero(diff) >= share->imgThresh;
|
||||
}
|
||||
|
||||
bool moDetect(const string &buffFile, shared_t *share)
|
||||
Mat frameFF(VideoCapture *cap, int gap)
|
||||
{
|
||||
Mat ret;
|
||||
|
||||
if (gap == 0) gap = 1;
|
||||
|
||||
for (int i = 0; i < gap; ++i)
|
||||
{
|
||||
cap->grab();
|
||||
}
|
||||
|
||||
cap->retrieve(ret);
|
||||
|
||||
if (!ret.empty())
|
||||
{
|
||||
cvtColor(ret, ret, COLOR_BGR2GRAY);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share)
|
||||
{
|
||||
auto dayStr = genTimeStr("%Y-%m-%d");
|
||||
auto timStr = genTimeStr("%H%M%S");
|
||||
auto outDir = cleanDir(share->outDir) + "/" + dayStr;
|
||||
|
||||
if (!exists(outDir))
|
||||
{
|
||||
enforceMaxDays(share->outDir, share);
|
||||
}
|
||||
|
||||
auto vidOut = genDstFile(outDir, timStr.c_str(), "." + share->vidExt);
|
||||
auto imgOut = genDstFile(outDir, timStr.c_str(), ".jpg");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
|
||||
{
|
||||
auto mod = false;
|
||||
|
||||
|
@ -117,13 +80,21 @@ bool moDetect(const string &buffFile, shared_t *share)
|
|||
Mat prev;
|
||||
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))
|
||||
{
|
||||
mod = true; break;
|
||||
resize(next, vidThumb, Size(360, 202), INTER_LINEAR);
|
||||
|
||||
mod = true; break;
|
||||
}
|
||||
}
|
||||
while (!prev.empty() && !next.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -14,20 +14,11 @@
|
|||
// GNU General Public License for more details.
|
||||
|
||||
#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 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
|
||||
|
|
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