diff --git a/.gitignore b/.gitignore index 6620139..e161d72 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ compile_commands.json # Build folders /.build-mow +/.build-opencv +/src/opencv diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c31c93..a4aa5fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,18 +11,13 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -include_directories(${OpenCV_INCLUDE_DIRS}) - -find_package(QT NAMES Qt6 COMPONENTS Core REQUIRED) -find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Multimedia REQUIRED) -find_package(OpenCV REQUIRED) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) add_executable(mow src/main.cpp src/common.h src/common.cpp - src/mo_detect.h - src/mo_detect.cpp src/web.h src/web.cpp src/logger.h @@ -31,4 +26,4 @@ add_executable(mow src/camera.cpp ) -target_link_libraries(mow Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Multimedia ${OpenCV_LIBS}) +target_link_libraries(mow Qt${QT_VERSION_MAJOR}::Core ${OpenCV_LIBS}) diff --git a/bin/magick b/bin/magick new file mode 100644 index 0000000..4fe1666 Binary files /dev/null and b/bin/magick differ diff --git a/setup.sh b/setup.sh index 90cad0a..da27c01 100644 --- a/setup.sh +++ b/setup.sh @@ -1,4 +1,7 @@ #!/bin/sh +export DEBIAN_FRONTEND=noninteractive apt update -y -apt install -y pkg-config cmake make g++ wget unzip git -apt install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libgstreamer1.0-dev x264 libx264-dev libilmbase-dev libopencv-dev qt6-base-dev qtchooser qmake6 qt6-base-dev-tools qt6-multimedia-dev libxkbcommon-dev +apt install -y pkg-config cmake make g++ +apt install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev x264 libx264-dev libilmbase-dev qt6-base-dev qtchooser qmake6 qt6-base-dev-tools libxkbcommon-dev +cp ./bin/magick /usr/bin/magick +chmod +x /usr/bin/magick diff --git a/src/camera.cpp b/src/camera.cpp index ba2f4ee..6a59c8c 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -21,8 +21,8 @@ Camera::Camera(QObject *parent) : QObject(parent) shared.retCode = 0; shared.maxScore = 0; shared.pixThresh = 50; - shared.imgThresh = 800; - shared.maxEvents = 40; + shared.imgThresh = 8000; + shared.maxEvents = 100; shared.maxLogSize = 100000; shared.skipCmd = false; shared.postSecs = 60; @@ -47,10 +47,10 @@ int Camera::start(const QStringList &args) auto thr3 = new QThread(nullptr); auto thr4 = new QThread(nullptr); - new RecLoop(&shared, thr1, this); - new Upkeep(&shared, thr2, this); - new EventLoop(&shared, thr3, this); - new DetectLoop(&shared, thr4, this); + new RecLoop(&shared, thr1, nullptr); + new Upkeep(&shared, thr2, nullptr); + new EventLoop(&shared, thr3, nullptr); + new DetectLoop(&shared, thr4, nullptr); thr1->start(); thr2->start(); @@ -61,25 +61,25 @@ int Camera::start(const QStringList &args) return shared.retCode; } -Loop::Loop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(0) +Loop::Loop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent) { shared = sharedRes; heartBeat = 10; - loopTimer = new QTimer(nullptr); + loopTimer = 0; - loopTimer->setSingleShot(false); - - connect(thr, &QThread::started, this, &Loop::init); - connect(parent, &QObject::destroyed, this, &Loop::deleteLater); - connect(parent, &QObject::destroyed, thr, &QThread::terminate); - connect(parent, &QObject::destroyed, loopTimer, &QTimer::deleteLater); - connect(loopTimer, &QTimer::timeout, this, &Loop::loopSlot); + connect(thr, &QThread::started, this, &Loop::init); + connect(this, &Loop::loopSig, this, &Loop::loopSlot); moveToThread(thr); } void Loop::init() { + loopTimer = new QTimer(nullptr); + + connect(loopTimer, &QTimer::timeout, this, &Loop::loopSlot); + + loopTimer->setSingleShot(false); loopTimer->start(heartBeat * 1000); } @@ -93,82 +93,127 @@ void Loop::loopSlot() bool Loop::exec() { + if (loopTimer->interval() != heartBeat * 1000) + { + loopTimer->start(heartBeat * 1000); + } + return shared->retCode == 0; } RecLoop::RecLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) { - once = true; + once = true; + baseListRdy = false; + recProc = 0; + imgProc = 0; +} + +void RecLoop::init() +{ + recProc = new QProcess(this); + imgProc = new QProcess(this); + + Loop::init(); } void RecLoop::updateCmd() { - QStringList args; + QStringList recArgs; + QStringList imgArgs; - args << "-hide_banner"; - args << "-i" << shared->recordUrl; - args << "-strftime" << "1"; - args << "-strftime_mkdir" << "1"; - args << "-hls_segment_filename" << "live/%Y-%j-%H-%M-%S.ts"; - args << "-hls_flags" << "delete_segments"; - args << "-y"; - args << "-vcodec" << "copy"; - args << "-f" << "hls"; - args << "-hls_time" << "2"; - args << "-hls_list_size" << "1000"; - args << "stream.m3u8"; + recArgs << "-hide_banner"; + recArgs << "-i" << shared->recordUrl; + recArgs << "-strftime" << "1"; + recArgs << "-strftime_mkdir" << "1"; + recArgs << "-hls_segment_filename" << "live/" + QString(STRFTIME_FMT) + ".ts"; + recArgs << "-hls_flags" << "delete_segments"; + recArgs << "-y"; + recArgs << "-vcodec" << "copy"; + recArgs << "-f" << "hls"; + recArgs << "-hls_time" << "2"; + recArgs << "-hls_list_size" << "1000"; + recArgs << "stream.m3u8"; - proc.setProgram("ffmpeg"); - proc.setArguments(args); + imgArgs << "-hide_banner"; + imgArgs << "-i" << shared->recordUrl; + imgArgs << "-strftime" << "1"; + imgArgs << "-vf" << "fps=1,scale=320:240"; + imgArgs << "img/" + QString(STRFTIME_FMT) + ".bmp"; + + recProc->setProgram("ffmpeg"); + recProc->setArguments(recArgs); + + imgProc->setProgram("ffmpeg"); + imgProc->setArguments(imgArgs); curUrl = shared->recordUrl; - recLog("rec_args_updated: " + args.join(" "), shared); + recLog("rec_args_updated: " + recArgs.join(" "), shared); + recLog("img_args_updated: " + imgArgs.join(" "), shared); } void RecLoop::reset() { - recLog("--rec_cmd_resetting--", shared); + recLog("--rec_and_img_cmds_resetting--", shared); - proc.kill(); - proc.waitForFinished(); + baseListRdy = false; + + recProc->kill(); + recProc->waitForFinished(); + + imgProc->kill(); + imgProc->waitForFinished(); updateCmd(); } -bool RecLoop::exec() +void RecLoop::startProc(const QString &desc, QProcess *proc) { - auto md5 = genMD5(QString("stream.m3u8")); - - if (once) + if (proc->state() == QProcess::NotRunning) { - updateCmd(); once = false; streamMD5 = genMD5(QByteArray("FIRST")); - } - else if ((curUrl != shared->recordUrl) || (streamMD5 == md5)) - { - reset(); - } + proc->start(); - auto hashLogLine = "stream_hash--prev:" + QString(streamMD5.toHex()) + "--new:" + QString(md5.toHex()); - - streamMD5 = md5; - - recLog(hashLogLine, shared); - - if (proc.state() == QProcess::NotRunning) - { - proc.start(); - - if (proc.waitForStarted()) + if (proc->waitForStarted()) { - recLog("rec_cmd_start: ok", shared); + recLog(desc + "_cmd_start: ok", shared); } else { - recLog("rec_cmd_start: fail", shared); - recLog("rec_cmd_stderr: " + QString(proc.readAllStandardError()), shared); + recLog(desc + "_cmd_start: fail", shared); + recLog(desc + "_cmd_stderr: " + QString(proc->readAllStandardError()), shared); } } +} + +bool RecLoop::exec() +{ + if (once) + { + updateCmd(); once = false; + } + else if (!baseListRdy) + { + baseListRdy = true; + } + else if (backwardFacingFiles("live", ".ts", QDateTime::currentDateTime(), heartBeat).isEmpty()) + { + recLog("backward facing files in the live stream are empty. cmd stall is suspected.", shared); + reset(); + } + else if (backwardFacingFiles("img", ".bmp", QDateTime::currentDateTime(), heartBeat).isEmpty()) + { + recLog("backward facing files in the image stream are empty. cmd stall is suspected.", shared); + reset(); + } + else if (curUrl != shared->recordUrl) + { + recLog("a change in the recording URL was detected.", shared); + reset(); + } + + startProc("img", imgProc); + startProc("rec", recProc); return Loop::exec(); } @@ -180,6 +225,7 @@ bool Upkeep::exec() QDir().mkdir("live"); QDir().mkdir("events"); QDir().mkdir("logs"); + QDir().mkdir("img"); enforceMaxLogSize(QString("logs/") + REC_LOG_NAME, shared); enforceMaxLogSize(QString("logs/") + DET_LOG_NAME, shared); @@ -195,6 +241,7 @@ bool Upkeep::exec() initLogFrontPages(shared); enforceMaxEvents(shared); + enforceMaxImages(); genHTMLul(".", shared->camName, shared); upkLog("camera specific webroot page updated: " + shared->outDir + "/index.html", shared); @@ -224,22 +271,43 @@ bool Upkeep::exec() EventLoop::EventLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) { - heartBeat = 2; + heartBeat = 5; } bool EventLoop::exec() { if (!shared->recList.isEmpty()) { - auto event = shared->recList[0]; + auto event = shared->recList[0]; + auto name = event.timeStamp.toString(DATETIME_FMT); + auto vidList = backwardFacingFiles("live", ".ts", event.timeStamp, shared->evMaxSecs / 2); - recLog("attempting write out of event: " + event.evName, shared); - - if (wrOutVod(event)) + if (vidList.isEmpty()) { - genHTMLvod(event.evName); + recLog("err: no backward faces files were found for event: " + name, shared); + } + else + { + vidList.removeLast(); - event.thumbnail.save(QString("events/" + event.evName + ".jpg")); + vidList += forwardFacingFiles("live", ".ts", event.timeStamp, shared->evMaxSecs / 2); + + recLog("attempting write out of event: " + name, shared); + + if (wrOutVod(name, vidList)) + { + genHTMLvod(name); + + QProcess proc; + QStringList args; + + args << "convert"; + args << event.imgPath; + args << "events/" + name + ".jpg"; + + proc.start("magick", args); + proc.waitForFinished(); + } } shared->recList.removeFirst(); @@ -248,22 +316,22 @@ bool EventLoop::exec() return Loop::exec(); } -bool EventLoop::wrOutVod(const evt_t &event) +bool EventLoop::wrOutVod(const QString &name, const QStringList &vids) { auto cnt = 0; - auto concat = event.evName + ".tmp"; + auto concat = name + ".tmp"; QFile file(concat); file.open(QFile::WriteOnly); - for (auto i = 0; i < event.srcPaths.size(); ++i) + for (auto &&vid : vids) { - recLog("event_src: " + event.srcPaths[i], shared); + recLog("event_src: " + vid, shared); - if (QFile::exists(event.srcPaths[i])) + if (QFile::exists(vid)) { - file.write(QString("file '" + event.srcPaths[i] + "'\n").toUtf8()); cnt++; + file.write(QString("file '" + vid + "'\n").toUtf8()); cnt++; } } @@ -285,7 +353,7 @@ bool EventLoop::wrOutVod(const evt_t &event) args << "-safe" << "0"; args << "-i" << concat; args << "-c" << "copy"; - args << "events/" + event.evName + ".mp4"; + args << "events/" + name + ".mp4"; proc.setProgram("ffmpeg"); proc.setArguments(args); @@ -313,20 +381,16 @@ bool EventLoop::wrOutVod(const evt_t &event) DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) { - heartBeat = 1; - evId = 0; - pcId = 0; - player = new QMediaPlayer(this); - frameAnalyzer = new MoDetect(shared, this); - - player->setVideoSink(frameAnalyzer); - - connect(player, &QMediaPlayer::errorOccurred, this, &DetectLoop::playError); + pcTimer = 0; + heartBeat = 3; } void DetectLoop::init() { - thread()->sleep(1); + pcTimer = new QTimer(nullptr); + mod = false; + + connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak); resetTimers(); @@ -335,134 +399,68 @@ void DetectLoop::init() void DetectLoop::resetTimers() { - if (evId != 0) killTimer(evId); - if (pcId != 0) killTimer(pcId); - - evId = startTimer(shared->evMaxSecs * 1000); - pcId = startTimer(shared->postSecs * 1000); + pcTimer->start(shared->postSecs * 1000); } -void DetectLoop::timerEvent(QTimerEvent *event) +void DetectLoop::pcBreak() { - if (event->timerId() == evId) - { - detLog("---EVENT_BREAK---", shared); - - if (!shared->curEvent.srcPaths.isEmpty()) - { - shared->curEvent.evName = QDateTime::currentDateTime().toString("yyyyMMddmmss--") + QString::number(shared->maxScore); - - shared->recList.append(shared->curEvent); - - detLog("motion detected in " + QString::number(shared->curEvent.srcPaths.size()) + " file(s) in " + QString::number(shared->evMaxSecs) + " secs", shared); - detLog("all video clips queued for event generation under event name: " + shared->curEvent.evName, shared); - } - else - { - detLog("no motion detected in all files. none queued for event generation.", shared); - } - - shared->curEvent.srcPaths.clear(); - shared->curEvent.evName.clear(); - - shared->curEvent.thumbnail = QImage(); - - shared->maxScore = 0; - } - - if ((event->timerId() == pcId) && (!shared->postCmd.isEmpty())) + if (!shared->postCmd.isEmpty()) { detLog("---POST_BREAK---", shared); - if (!shared->skipCmd) + if (mod) + { + detLog("motion detected, skipping the post command.", shared); + } + else { detLog("no motion detected, running post command: " + shared->postCmd, shared); system(shared->postCmd.toUtf8().data()); } - else - { - shared->skipCmd = false; - - detLog("motion detected, skipping the post command.", shared); - } } -} -void DetectLoop::playError(QMediaPlayer::Error error, const QString &errorString) -{ - Q_UNUSED(error) - - detLog("err: media player error - " + errorString, shared); -} - -void DetectLoop::playStateChanged(QMediaPlayer::PlaybackState newState) -{ - if (newState == QMediaPlayer::PlayingState) - { - detLog("detection playback started.", shared); - } - else if (newState == QMediaPlayer::StoppedState) - { - detLog("detection playback stopped.", shared); - } + mod = false; } bool DetectLoop::exec() { - if (player->playbackState() != QMediaPlayer::PlayingState) + auto curDT = QDateTime::currentDateTime(); + auto images = backwardFacingFiles("img", ".bmp", curDT, 10); + + if (images.size() < 3) { - QFile fileIn("stream.m3u8"); - QString tsPath; + detLog("wrn: didn't pick up enough image files from the image stream. number of files: " + QString::number(images.size()), shared); + } + else + { + QProcess extComp; + QStringList args; - if (!fileIn.open(QFile::ReadOnly)) + args << "compare"; + args << "-metric" << "FUZZ"; + args << images[0]; + args << images[1]; + args << "/dev/null"; + + extComp.start("magick", args); + extComp.waitForFinished(); + + QString output = extComp.readAllStandardError(); + + output = output.left(output.indexOf(' ')); + + detLog(extComp.program() + " " + args.join(" ") + " --result: " + output, shared); + + if (output.toFloat() >= shared->imgThresh) { - detLog("err: failed to open the stream hls file for reading. reason: " + fileIn.errorString(), shared); - } - else if (fileIn.size() < 50) - { - detLog("the stream hls list is not big enough yet. waiting for more clips.", shared); - } - else if (!fileIn.seek(fileIn.size() - 50)) - { - detLog("err: failed to seek to 'near end' of stream file. reason: " + fileIn.errorString(), shared); - } - else - { - QString line; + detLog("--threshold_breached: " + QString::number(shared->imgThresh), shared); - do - { - line = QString::fromUtf8(fileIn.readLine()); + evt_t event; - if (line.startsWith("live/")) - { - tsPath = line; - } + event.timeStamp = curDT; + event.imgPath = images[2]; - } while(!line.isEmpty()); - } - - if (tsPath.isEmpty()) - { - detLog("wrn: didn't find the latest hls clip. previous failure? waiting 5secs.", shared); - - thread()->sleep(5); - } - else if (prevTs == tsPath) - { - detLog("wrn: the lastest hls clip is the same as the previous clip. is the recording loop running? waiting 5secs.", shared); - - thread()->sleep(5); - } - else - { - detLog("stream_clip: " + tsPath, shared); - - prevTs = tsPath; - - player->setSource(QUrl::fromLocalFile(tsPath)); - frameAnalyzer->play(tsPath); - player->play(); + shared->recList.append(event); mod = true; } } diff --git a/src/camera.h b/src/camera.h index 8e24d51..454c6c4 100644 --- a/src/camera.h +++ b/src/camera.h @@ -16,7 +16,6 @@ #include "common.h" #include "logger.h" #include "web.h" -#include "mo_detect.h" class Camera : public QObject { @@ -41,12 +40,17 @@ protected: shared_t *shared; QTimer *loopTimer; + bool interruptible; int heartBeat; protected slots: virtual void init(); +signals: + + void loopSig(); + private slots: void loopSlot(); @@ -64,13 +68,21 @@ class RecLoop : public Loop private: - QProcess proc; - QString curUrl; - QByteArray streamMD5; - bool once; + QProcess *recProc; + QProcess *imgProc; + QStringList recList; + QStringList imgList; + QString curUrl; + bool baseListRdy; + bool once; void updateCmd(); void reset(); + void startProc(const QString &desc, QProcess *proc); + +private slots: + + void init(); public: @@ -96,7 +108,7 @@ class EventLoop : public Loop private: - bool wrOutVod(const evt_t &event); + bool wrOutVod(const QString &name, const QStringList &vids); public: @@ -111,21 +123,15 @@ class DetectLoop : public Loop private: - int pcId; - int evId; - QString prevTs; - QMediaPlayer *player; - MoDetect *frameAnalyzer; + QTimer *pcTimer; + bool mod; void resetTimers(); - void resetDetect(); - void timerEvent(QTimerEvent *event); private slots: void init(); - void playError(QMediaPlayer::Error error, const QString &errorString); - void playStateChanged(QMediaPlayer::PlaybackState newState); + void pcBreak(); public: diff --git a/src/common.cpp b/src/common.cpp index 859f32d..81fa9d3 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -49,27 +49,6 @@ QString getParam(const QString &key, const QStringList &args) return ret; } -QByteArray genMD5(const QByteArray &bytes) -{ - QCryptographicHash hasher(QCryptographicHash::Md5); - - hasher.addData(bytes); return hasher.result(); -} - -QByteArray genMD5(const QString &path) -{ - QFile file(path); - - if (file.open(QFile::ReadOnly)) - { - return genMD5(file.readAll()); - } - else - { - return genMD5(QByteArray("EMPTY")); - } -} - QStringList lsFilesInDir(const QString &path, const QString &ext) { QStringList filters; @@ -95,6 +74,37 @@ QStringList lsDirsInDir(const QString &path) return dirObj.entryList(); } +QStringList listFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs, char dir) +{ + QStringList ret; + + for (auto i = 0; i < secs; ++i) + { + QString filePath; + + if (dir == '-') filePath = path + "/" + stamp.addSecs(-i).toString(DATETIME_FMT) + ext; + if (dir == '+') filePath = path + "/" + stamp.addSecs(i).toString(DATETIME_FMT) + ext; + + if (QFile::exists(filePath)) + { + if (dir == '-') ret.insert(0, filePath); + if (dir == '+') ret.append(filePath); + } + } + + return ret; +} + +QStringList backwardFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs) +{ + return listFacingFiles(path, ext, stamp, secs, '-'); +} + +QStringList forwardFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs) +{ + return listFacingFiles(path, ext, stamp, secs, '+'); +} + void enforceMaxEvents(shared_t *share) { auto names = lsFilesInDir("events", ".mp4"); @@ -117,6 +127,18 @@ void enforceMaxEvents(shared_t *share) } } +void enforceMaxImages() +{ + auto names = lsFilesInDir("img", ".bmp"); + + while (names.size() > MAX_IMAGES) + { + QFile::remove("img/" + names[0]); + + names.removeFirst(); + } +} + void rdLine(const QString ¶m, const QString &line, QString *value) { if (line.startsWith(param)) diff --git a/src/common.h b/src/common.h index 39cb57f..26d09d2 100644 --- a/src/common.h +++ b/src/common.h @@ -23,31 +23,25 @@ #include #include #include -#include -#include -#include -#include #include +#include -#include -#include -#include - -using namespace cv; using namespace std; -#define APP_VER "3.0.t5" +#define APP_VER "3.0.t6" #define APP_NAME "Motion Watch" #define APP_BIN "mow" #define REC_LOG_NAME "rec_log_lines.html" #define DET_LOG_NAME "det_log_lines.html" #define UPK_LOG_NAME "upk_log_lines.html" +#define DATETIME_FMT "yyyyMMddhhmmss" +#define STRFTIME_FMT "%Y%m%d%H%M%S" +#define MAX_IMAGES 40 struct evt_t { - QString evName; - QStringList srcPaths; - QImage thumbnail; + QDateTime timeStamp; + QString imgPath; }; struct shared_t @@ -82,16 +76,18 @@ struct shared_t int evInd; }; -QByteArray genMD5(const QString &path); -QByteArray genMD5(const QByteArray &bytes); QString getParam(const QString &key, const QStringList &args); QStringList lsFilesInDir(const QString &path, const QString &ext = QString()); QStringList lsDirsInDir(const QString &path); +QStringList listFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs, char dir); +QStringList backwardFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs); +QStringList forwardFacingFiles(const QString &path, const QString &ext, const QDateTime &stamp, int secs); bool rdConf(const QString &filePath, shared_t *share); bool rdConf(shared_t *share); void rdLine(const QString ¶m, const QString &line, QString *value); void rdLine(const QString ¶m, const QString &line, int *value); void enforceMaxEvents(shared_t *share); +void enforceMaxImages(); class MultiInstance : public QObject { diff --git a/src/mo_detect.cpp b/src/mo_detect.cpp deleted file mode 100644 index 2eec30f..0000000 --- a/src/mo_detect.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// 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 "mo_detect.h" - -MoDetect::MoDetect(shared_t *share, QObject *parent) : QVideoSink(parent) -{ - shared = share; - - connect(this, &MoDetect::videoFrameChanged, this, &MoDetect::newFrame); -} - -void MoDetect::stop() -{ - baseImg = QImage(); gap = 0; shared->maxScore = 0; -} - -void MoDetect::play(const QString &path) -{ - filePath = path; -} - -void MoDetect::newFrame() -{ - if (!baseImg.isNull()) - { - baseImg = videoFrame().toImage().convertToFormat(QImage::Format_Grayscale8); - } - else if (shared->frameGap > gap) - { - gap++; - } - else - { - gap = 0; - - auto nextImg = videoFrame().toImage(); - auto nextImgGray = nextImg.convertToFormat(QImage::Format_Grayscale8); - auto score = 0; - - if (diff(baseImg, nextImgGray, &score)) - { - shared->skipCmd = true; - - if (!shared->curEvent.srcPaths.contains(filePath)) - { - shared->curEvent.srcPaths.append(filePath); - - if (shared->maxScore <= score) - { - shared->maxScore = score; - - shared->curEvent.thumbnail = nextImg; - } - } - } - } -} - -bool MoDetect::diff(QImage &base, QImage &next, int *score) -{ - Mat baseMat = Mat(base.height(), base.width(), CV_8U, base.bits(), base.bytesPerLine()); - Mat nextMat = Mat(next.height(), next.width(), CV_8U, next.bits(), next.bytesPerLine()); - Mat diffMat; - - absdiff(baseMat, nextMat, diffMat); - threshold(diffMat, diffMat, shared->pixThresh, 255, THRESH_BINARY); - - *score = countNonZero(diffMat); - - detLog("diff_score: " + QString::number(*score) + " thresh: " + QString::number(shared->imgThresh), shared); - - return *score >= shared->imgThresh; -} diff --git a/src/mo_detect.h b/src/mo_detect.h deleted file mode 100644 index 3048356..0000000 --- a/src/mo_detect.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef MO_DETECT_H -#define MO_DETECT_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" -#include "logger.h" - -class MoDetect : public QVideoSink -{ - Q_OBJECT - -private: - - QImage baseImg; - QString filePath; - shared_t *shared; - int gap; - - bool diff(QImage &base, QImage &next, int *score); - -private slots: - - void newFrame(); - -public: - - void play(const QString &path); - void stop(); - - explicit MoDetect(shared_t *share, QObject *parent = nullptr); -}; - -#endif // MO_DETECT_H