#include #include #include #include #include #include #include #include #include #include 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; } 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) { time_t rawtime; time(&rawtime); auto timeinfo = localtime(&rawtime); char dirName[20]; char fileName[20]; strftime(dirName, 20, "%Y%m%d", timeinfo); strftime(fileName, 20, "%H%M%S", timeinfo); createDirTree(cleanDir(dirOut) + string("/") + string(dirName)); return cleanDir(dirOut) + string("/") + string(dirName) + string("/") + string(fileName) + ext; } void wrOut(shared_t *share) { share->baseImg.release(); 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()); } Mat toGray(const Mat &src) { Mat ret; cvtColor(src, ret, COLOR_BGR2GRAY); return ret; } bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share) { auto diff = 0; if (pixA > pixB) diff = pixA - pixB; if (pixB > pixA) diff = pixB - pixA; if (diff < share->colorThresh) { diff = 0; } 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++) { for (auto x = colOffs; x < cols; x++) { auto pixA = imgA.at(Point(x, y)); auto pixB = imgB.at(Point(x, y)); if (pixDiff(pixA, pixB, share)) { pnts += 1; } } } return pnts; } bool imgDiff(Mat curImg, shared_t *share) { if (share->baseImg.empty()) { share->baseImg = toGray(curImg); return false; } else { curImg = toGray(curImg); auto pnts = secDiff(share->baseImg, curImg, curImg.rows, curImg.cols, 0, 0, share); if (share->diffVerb == "Y") { 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; } } } string parseForParam(const string &arg, int argc, char** argv, bool argOnly) { for (int i = 0; i < argc; ++i) { auto argInParams = string(argv[i]); if (arg.compare(argInParams) == 0) { if (!argOnly) { // 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"); } } } return string(); } 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; } } 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; } } bool rdConf(shared_t *share) { auto ret = false; share->retCode = ENOENT; ifstream varFile(share->conf.c_str()); if (!varFile.is_open()) { cerr << "err: failed to open the config file: " << share->conf << " for reading. please check file permissions or if it exists." << endl; } else { 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; } 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 " << 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") { showHelp(); } else if (sharedRes.conf.empty()) { cerr << "err: a config file was not given in -c" << endl; } else { moDetect(&sharedRes); return sharedRes.retCode; } return EINVAL; }