#include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace cv; using namespace std; struct shared_t { vector buff; string detectUrl; string recordUrl; string outDir; string postCmd; string conf; bool wrRunning; bool ffRunning; int motion; int secs; int thrWithMotion; int thresh; int pixSize; int postMoIncr; int minRecFrames; int sectionSize; 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; } void vidCap(shared_t *share) { if (share->buff.size() >= share->minRecFrames) { share->wrRunning = true; 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.avi", timeinfo); 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'); VideoWriter writer; writer.open(dstPath, codec, 30.0, share->buff[0].size(), true); if (!writer.isOpened()) { cerr << "could not open the output video file for writing: " << dstPath; } else { for (; !share->buff.empty(); share->buff.erase(share->buff.begin())) { writer.write(share->buff[0]); } } share->wrRunning = false; } } uchar valDiff(uchar valA, uchar valB) { auto diff = 0; if (valA > valB) diff = valA - valB; if (valA < valB) diff = valB - valA; return diff; } void secDiff(Mat imgA, Mat imgB, int rows, int cols, int rowOffs, int colOffs, shared_t *share) { auto xCnt = 0; auto yCnt = 0; for (auto y = rowOffs; (y < rows) && share->thrWithMotion == 0; y++) { for (auto x = colOffs; (x < cols) && share->thrWithMotion == 0; x++) { auto pixA = imgA.at(Point(x, y)); auto pixB = imgB.at(Point(x, y)); if (valDiff(pixA, pixB) > share->thresh) { xCnt += 1; if (xCnt >= share->pixSize) break; } else { xCnt = 0; } } if (xCnt >= share->pixSize) { yCnt += 1; if (yCnt >= share->pixSize) { share->thrWithMotion += 1; } } else { xCnt = 0; yCnt = 0; } } } bool grayDiff(Mat imgA, Mat imgB, shared_t *share) { share->thrWithMotion = 0; auto colBuff = share->sectionSize; auto allRows = imgA.rows; auto allCols = imgA.cols; auto colsOffs = 0; vector thrs; while (allCols != 0) { if (colBuff > allCols) { colBuff -= (colBuff - allCols); } thrs.push_back(thread(secDiff, imgA, imgB, allRows, colBuff, 0, colsOffs, share)); colsOffs += colBuff; allCols -= colBuff; } for (auto &&thr : thrs) { thr.join(); } return share->thrWithMotion != 0; } void timer(shared_t *share) { sleep(share->secs); if (share->motion == 0) { share->ffRunning = false; } if (!share->wrRunning) { new thread(vidCap, share); } } Mat toGray(const Mat &src) { Mat ret; cvtColor(src, ret, COLOR_BGR2GRAY); return ret; } void moDetect(shared_t *share) { auto dCap = VideoCapture(share->detectUrl, CAP_FFMPEG); auto rCap = VideoCapture(share->recordUrl, CAP_FFMPEG); Mat dFrame, rFrame, dPrev, rPrev; while (share->ffRunning) { if (share->motion == 0) dCap >> dFrame; rCap >> rFrame; if (dFrame.empty() && (share->motion == 0)) { // 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); } else if (rFrame.empty()) { rCap.open(share->recordUrl, CAP_FFMPEG); } else if (share->motion > 0) { share->buff.push_back(rFrame.clone()); share->motion -= 1; } else if (dPrev.empty() || rPrev.empty()) { dPrev = toGray(dFrame); rPrev = rFrame.clone(); } else if (grayDiff(dPrev, toGray(dFrame), share)) { share->buff.push_back(rPrev); share->buff.push_back(rFrame.clone()); share->motion += share->postMoIncr; rPrev.release(); dPrev.release(); } else { rPrev.release(); dPrev.release(); } } system(share->postCmd.c_str()); } 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(); } bool rdConf(shared_t *share) { // recording_stream // detection_stream // output_dir // diff_threshold // post_cmd // duration // pixel_size // frames_post_motion // minimum_recording_frames // section_size 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; do { getline(varFile, line); if (line.rfind("#", 0) != 0) { if (line.rfind("recording_stream = ", 0) == 0) { share->recordUrl = line.substr(19); cout << "recording_stream = " << share->recordUrl << endl; } else if (line.rfind("detection_stream = ", 0) == 0) { share->detectUrl = line.substr(19); cout << "detection_stream = " << share->detectUrl << endl; } else if (line.rfind("output_dir = ", 0) == 0) { share->outDir = line.substr(13); cout << "output_dir = " << share->outDir << endl; } else if (line.rfind("post_cmd = ", 0) == 0) { share->postCmd = line.substr(11); cout << "post_cmd = " << share->postCmd << endl; } else if (line.rfind("diff_threshold = ", 0) == 0) { share->thresh = strtol(line.substr(17).c_str(), NULL, 10); cout << "diff_threshold = " << share->thresh << endl; } else if (line.rfind("duration = ", 0) == 0) { share->secs = strtol(line.substr(11).c_str(), NULL, 10); cout << "duration = " << share->secs << endl; } else if (line.rfind("pixel_size = ", 0) == 0) { share->pixSize = strtol(line.substr(13).c_str(), NULL, 10); cout << "pixel_size = " << share->pixSize << endl; } else if (line.rfind("frames_post_motion = ", 0) == 0) { share->postMoIncr = strtol(line.substr(21).c_str(), NULL, 10); cout << "frames_post_motion = " << share->postMoIncr << endl; } else if (line.rfind("minimum_recording_frames = ", 0) == 0) { share->minRecFrames = strtol(line.substr(27).c_str(), NULL, 10); cout << "minimum_recording_frames = " << share->minRecFrames << endl; } else if (line.rfind("section_size = ", 0) == 0) { share->sectionSize = strtol(line.substr(15).c_str(), NULL, 10); cout << "section_size = " << share->sectionSize << endl; } } } while(!line.empty()); ret = true; share->retCode = 0; } varFile.close(); return ret; } 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 { sharedRes.retCode = 0; sharedRes.motion = 0; sharedRes.wrRunning = false; while (rdConf(&sharedRes)) { sharedRes.ffRunning = true; thread th1(timer, &sharedRes); 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 sharedRes.retCode; } return EINVAL; }