I'm going to test a move away from opencv's videoio module.
Videoio simply refuses to open any video file even with
FFMPEG builtin. I tested old v2.2 code and even that failed
on a fresh install of ubuntu sever so this tells me an
update on opencv's side broke something.

This issue is not new and frankly I'm tired of chasing it.
I'm giving QT's QMediaPlayer a try to see how it works out.
Will still need opencv for the absdiff and threshold
functions, otherwise I would have dropped the API
altogeather.

Now that the app has QT::Multimedia, QT6 is now the minimum
version it will support. CMakeList.txt and the setup script
updated accordingly.
This commit is contained in:
Maurice ONeal 2023-05-20 19:18:55 -04:00
parent 40ef014e0f
commit 496bac7d7e
7 changed files with 138 additions and 119 deletions

View File

@ -13,8 +13,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(${OpenCV_INCLUDE_DIRS}) include_directories(${OpenCV_INCLUDE_DIRS})
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) find_package(QT NAMES Qt6 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Multimedia REQUIRED)
find_package(OpenCV REQUIRED) find_package(OpenCV REQUIRED)
add_executable(mow add_executable(mow
@ -31,4 +31,4 @@ add_executable(mow
src/camera.cpp src/camera.cpp
) )
target_link_libraries(mow Qt${QT_VERSION_MAJOR}::Core ${OpenCV_LIBS}) target_link_libraries(mow Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Multimedia ${OpenCV_LIBS})

View File

@ -1,24 +1,4 @@
#!/bin/sh #!/bin/sh
apt update -y apt update -y
apt install -y pkg-config apt install -y pkg-config cmake make g++ wget unzip git
apt install -y cmake 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 apache2
apt install -y make
apt install -y g++
apt install -y wget
apt install -y unzip
apt install -y git
apt install -y ffmpeg
apt install -y libavcodec-dev
apt install -y libavformat-dev
apt install -y libavutil-dev
apt install -y libswscale-dev
apt install -y libgstreamer1.0-dev
apt install -y x264
apt install -y libx264-dev
apt install -y libilmbase-dev
apt install -y libopencv-dev
apt install -y qtbase5-dev
apt install -y qtchooser
apt install -y qt5-qmake
apt install -y qtbase5-dev-tools
apt install -y apache2

View File

@ -19,6 +19,7 @@ Camera::Camera(QObject *parent) : QObject(parent)
shared.camName.clear(); shared.camName.clear();
shared.retCode = 0; shared.retCode = 0;
shared.maxScore = 0;
shared.pixThresh = 50; shared.pixThresh = 50;
shared.imgThresh = 800; shared.imgThresh = 800;
shared.maxEvents = 40; shared.maxEvents = 40;
@ -240,7 +241,8 @@ bool EventLoop::exec()
if (wrOutVod(event)) if (wrOutVod(event))
{ {
genHTMLvod(event.evName); genHTMLvod(event.evName);
imwrite(QString("events/" + event.evName + ".jpg").toUtf8().data(), event.thumbnail);
event.thumbnail.save(QString("events/" + event.evName + ".jpg"));
} }
shared->recList.removeFirst(); shared->recList.removeFirst();
@ -314,16 +316,22 @@ bool EventLoop::wrOutVod(const evt_t &event)
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent) DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : Loop(sharedRes, thr, parent)
{ {
heartBeat = 0; heartBeat = 0;
evId = 0; evId = 0;
pcId = 0; pcId = 0;
player = new QMediaPlayer(this);
frameAnalyzer = new MoDetect(shared, this);
player->setVideoSink(frameAnalyzer);
connect(player, &QMediaPlayer::errorOccurred, this, &DetectLoop::playError);
} }
void DetectLoop::init() void DetectLoop::init()
{ {
thread()->sleep(1); thread()->sleep(1);
resetTimers(); Loop::init(); resetTimers();
} }
void DetectLoop::resetTimers() void DetectLoop::resetTimers()
@ -357,7 +365,8 @@ void DetectLoop::timerEvent(QTimerEvent *event)
shared->curEvent.srcPaths.clear(); shared->curEvent.srcPaths.clear();
shared->curEvent.evName.clear(); shared->curEvent.evName.clear();
shared->curEvent.thumbnail.release();
shared->curEvent.thumbnail = QImage();
shared->maxScore = 0; shared->maxScore = 0;
} }
@ -380,6 +389,31 @@ void DetectLoop::timerEvent(QTimerEvent *event)
} }
} }
void DetectLoop::resetDetect()
{
if (player->playbackState() != QMediaPlayer::PlayingState)
{
frameAnalyzer->stop();
exec();
}
}
void DetectLoop::playError(QMediaPlayer::Error error, const QString &errorString)
{
Q_UNUSED(error)
detLog("err: media player error - " + errorString, shared);
resetDetect();
}
void DetectLoop::playStateChanged(QMediaPlayer::PlaybackState newState)
{
Q_UNUSED(newState)
resetDetect();
}
bool DetectLoop::exec() bool DetectLoop::exec()
{ {
QFile fileIn("stream.m3u8"); QFile fileIn("stream.m3u8");
@ -429,12 +463,9 @@ bool DetectLoop::exec()
{ {
prevTs = tsPath; prevTs = tsPath;
if (moDetect(tsPath, thread(), shared)) player->setSource(QUrl::fromLocalFile(tsPath));
{ frameAnalyzer->play(tsPath);
shared->curEvent.srcPaths.append(tsPath); player->play();
shared->skipCmd = true;
}
} }
return Loop::exec(); return Loop::exec();

View File

@ -114,16 +114,21 @@ class DetectLoop : public Loop
private: private:
int pcId; int pcId;
int evId; int evId;
QString prevTs; QString prevTs;
QMediaPlayer *player;
MoDetect *frameAnalyzer;
void resetTimers(); void resetTimers();
void resetDetect();
void timerEvent(QTimerEvent *event); void timerEvent(QTimerEvent *event);
private slots: private slots:
void init(); void init();
void playError(QMediaPlayer::Error error, const QString &errorString);
void playStateChanged(QMediaPlayer::PlaybackState newState);
public: public:

View File

@ -23,6 +23,10 @@
#include <QFile> #include <QFile>
#include <QDateTime> #include <QDateTime>
#include <QThread> #include <QThread>
#include <QMediaPlayer>
#include <QVideoSink>
#include <QVideoFrame>
#include <QImage>
#include <opencv4/opencv2/opencv.hpp> #include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/videoio.hpp> #include <opencv4/opencv2/videoio.hpp>
@ -31,7 +35,7 @@
using namespace cv; using namespace cv;
using namespace std; using namespace std;
#define APP_VER "3.0.t3" #define APP_VER "3.0.t4"
#define APP_NAME "Motion Watch" #define APP_NAME "Motion Watch"
#define APP_BIN "mow" #define APP_BIN "mow"
#define REC_LOG_NAME "rec_log_lines.html" #define REC_LOG_NAME "rec_log_lines.html"
@ -42,7 +46,7 @@ struct evt_t
{ {
QString evName; QString evName;
QStringList srcPaths; QStringList srcPaths;
Mat thumbnail; QImage thumbnail;
}; };
struct shared_t struct shared_t

View File

@ -12,93 +12,70 @@
#include "mo_detect.h" #include "mo_detect.h"
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share) MoDetect::MoDetect(shared_t *share, QObject *parent) : QVideoSink(parent)
{ {
Mat prevGray; shared = share;
Mat nextGray;
cvtColor(prev, prevGray, COLOR_BGR2GRAY); connect(this, &MoDetect::videoFrameChanged, this, &MoDetect::newFrame);
cvtColor(next, nextGray, COLOR_BGR2GRAY);
Mat diff;
absdiff(prevGray, nextGray, diff);
threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY);
score = countNonZero(diff);
detLog("diff_score: " + QString::number(score) + " thresh: " + QString::number(share->imgThresh), share);
return score >= share->imgThresh;
} }
bool moDetect(const QString &buffFile, QThread *thr, shared_t *share) void MoDetect::stop()
{ {
auto score = 0; baseImg = QImage(); gap = 0; shared->maxScore = 0;
auto mod = false; }
detLog("stream_clip: " + buffFile, share); void MoDetect::play(const QString &path)
{
filePath = path;
}
VideoCapture capture; void MoDetect::newFrame()
{
if (!capture.open(buffFile.toUtf8().data(), CAP_FFMPEG)) if (!baseImg.isNull())
{ {
thr->usleep(500); baseImg = videoFrame().toImage().convertToFormat(QImage::Format_Grayscale8);
capture.open(buffFile.toUtf8().data(), CAP_FFMPEG);
} }
else if (shared->frameGap > gap)
if (capture.isOpened())
{ {
Mat prev; gap++;
Mat next;
int fps = capture.get(cv::CAP_PROP_FPS);
for (auto gap = 0, frm = fps; capture.grab(); ++gap, ++frm)
{
if (frm == fps) thr->sleep(1); frm = 1;
if (prev.empty())
{
capture.retrieve(prev);
}
else if (gap == (share->frameGap - 1))
{
capture.retrieve(next);
if (!next.empty())
{
if (imgDiff(prev, next, score, share))
{
mod = true;
if (share->maxScore <= score)
{
share->maxScore = score;
resize(next, share->curEvent.thumbnail, Size(720, 480), INTER_LINEAR);
}
}
}
prev = next.clone();
gap = 0;
next.release();
}
else
{
capture.grab();
}
}
} }
else else
{ {
detLog("err: failed to open: " + buffFile + " after 500 msecs. giving up.", share); 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;
}
}
}
} }
}
capture.release();
bool MoDetect::diff(QImage &base, QImage &next, int *score)
return mod; {
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;
} }

View File

@ -16,7 +16,29 @@
#include "common.h" #include "common.h"
#include "logger.h" #include "logger.h"
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share); class MoDetect : public QVideoSink
bool moDetect(const QString &buffFile, QThread *thr, shared_t *share); {
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 #endif // MO_DETECT_H