Compare commits

...

5 Commits

Author SHA1 Message Date
Maurice ONeal
01a11741c7 v1.5
Release candidate for merging to master.
2022-10-15 10:24:28 -04:00
Maurice ONeal
a0ee8e35f7 v1.4.t11
completely removed object detection code because I don't foresee going
back to that model anytime soon. diffs will not reset to 0 instead
decrement and the consecutive pixel diffs are now adjustable via
consec_threshold.

updated README.md for the changes to pixel diff detection.
2022-10-14 14:31:12 -04:00
Maurice ONeal
9ecace7e4b v1.4.t10
optical flow calculations use up a lot of processing power even at the
block level so I decided to take it back out. once again, no objection
detection is going to be used and will fall back to pixel diffs only.
also modified pixel diffs to decrement pixel diffs of no diff is
detected, going test how this works out.
2022-10-14 11:42:59 -04:00
Maurice ONeal
17c136b6bc v1.4.t9
Motion detection was still not scanning the entire block. potential fix
in secDiff(), correcting offsets in the for loops.
2022-10-06 19:41:47 -04:00
Maurice ONeal
4bf3672a39 v1.4.t8
AI object detection via yolov5 didn't work out too well, in fact it was
crashing the detection threads for whatever reason. I could deep dive
why it was crashing but I think the better solution is to bring back
optical flow detection at the block level. the advantage of this over
object detection is the fact that a block doesn't need to have a whole
object in it.
2022-10-04 18:12:32 -04:00
12 changed files with 71 additions and 313 deletions

View File

@ -3,5 +3,5 @@ project( MotionWatch )
find_package( OpenCV REQUIRED )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread")
include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/obj_detect.cpp )
add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp )
target_link_libraries( mow ${OpenCV_LIBS} )

View File

@ -45,20 +45,15 @@ buff_dir = /tmp/ramdisk/cam_name
# recommend to use a ramdisk tempfs for this since this directory is used
# for lots of writes.
#
color_threshold = 8
# the color levels in each pixel of the camera stream can range from 0-255.
# in an ideal world the color differences in between frames should be 0 if
# there is no motion but must cameras can't do this. the threshold value
# here is used to filter if the pixels are truly different.
consec_threshold = 512
# motion is detected by comparing each frame in the camera feed for
# differences in the pixels. this value determine how many consecutive
# pixels need to different or how large the suspect object in motion
# needs to be.
#
block_threshold = 3456
# this application detects motion by loading frames from the camera and
# then compare the pixels of each back to back frame for any significant
# differences between the pixels based on color_threshold. it loads the
# pixels of each frame in blocks. the size of the blocks are adjustable
# below. it counts how many pixels are different in the block and this is
# used to tell if the footage has motion if the different pixel count
# exceeds it.
block_threshold = 1024
# this value tells the application how many "lines" of pixels need to
# exceed consec_threshold before being considered motion.
#
block_x = 64
# this is the x coordinate size or horizontal size of a block of pixels

View File

@ -1,80 +0,0 @@
person
bicycle
car
motorbike
aeroplane
bus
train
truck
boat
traffic light
fire hydrant
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
backpack
umbrella
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
sofa
pottedplant
bed
diningtable
toilet
tvmonitor
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
book
clock
vase
scissors
teddy bear
hair drier
toothbrush

Binary file not shown.

View File

@ -1,6 +1,3 @@
#!/bin/sh
mkdir -p /etc/mow
cp ./etc/yolov5s.onnx /etc/mow/yolov5s.onnx
cp ./etc/classes.txt /etc/mow/classes.txt
cp ./.build-mow/mow /usr/bin/mow

View File

@ -140,18 +140,6 @@ string genDstFile(const string &dirOut, const char *fmt, const string &ext)
return cleanDir(dirOut) + string("/") + genTimeStr(fmt) + ext;
}
string genTmpFile(const string &dirOut, const string &ext, shared_t *share)
{
createDirTree(cleanDir(dirOut));
if (share->tmpId == 9999999)
{
share->tmpId = 0;
}
return cleanDir(dirOut) + string("/") + to_string(share->tmpId++) + ext;
}
Mat toGray(const Mat &src)
{
Mat ret;
@ -181,21 +169,6 @@ void rdLine(const string &param, const string &line, int *value)
}
}
vector<string> loadClassList()
{
vector<string> ret;
ifstream ifs("/etc/mow/classes.txt");
string line;
while (getline(ifs, line))
{
ret.push_back(line);
}
return ret;
}
bool rdConf(shared_t *share)
{
ifstream varFile(share->conf.c_str());
@ -215,20 +188,15 @@ bool rdConf(shared_t *share)
share->postCmd.clear();
share->buffDir.clear();
share->colorThresh = 5;
share->consecThresh = 512;
share->secs = 60;
share->blockX = 32;
share->blockY = 32;
share->blockThresh = 900;
share->blockThresh = 1024;
share->maxDays = 5;
share->vidExt = "mp4";
share->recLoopWait = false;
share->skipCmd = false;
share->network = dnn::readNet("/etc/mow/yolov5s.onnx");
share->classNames = loadClassList();
share->network.setPreferableBackend(dnn::DNN_BACKEND_OPENCV);
share->network.setPreferableTarget(dnn::DNN_TARGET_CPU);
do
{
@ -239,7 +207,7 @@ bool rdConf(shared_t *share)
rdLine("recording_stream = ", line, &share->recordUrl);
rdLine("output_dir = ", line, &share->outDir);
rdLine("post_cmd = ", line, &share->postCmd);
rdLine("color_threshold = ", line, &share->colorThresh);
rdLine("consec_threshold = ", line, &share->consecThresh);
rdLine("duration = ", line, &share->secs);
rdLine("buff_dir = ", line, &share->buffDir);
rdLine("block_x = ", line, &share->blockX);
@ -266,6 +234,9 @@ bool rdConf(shared_t *share)
new thread(enforceMaxDays, share);
createDirTree(cleanDir(share->buffDir));
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
share->retCode = 0;
}
@ -336,10 +307,10 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
void statOut(shared_t *share)
{
createDirTree(cleanDir(share->buffDir));
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
auto path = string(cleanDir(share->buffDir) + "/stat");
auto fd = open(path.c_str(), O_WRONLY);
auto fd = open(path.c_str(), fstream::out | fstream::trunc);
write(fd, share->stat.c_str(), share->stat.size() + 1);
close(fd);

View File

@ -37,27 +37,21 @@ using namespace std;
using namespace std::filesystem;
#define BUF_SZ 10
#define APP_VER "1.4.t7"
#define APP_VER "1.5"
struct shared_t
{
vector<string> classNames;
dnn::Net network;
string stat;
string recordUrl;
string outDir;
string postCmd;
string conf;
string buffDir;
string concatTxtTmp;
string concatShTmp;
string createShTmp;
string vidExt;
bool init;
bool recLoopWait;
bool skipCmd;
int tmpId;
int colorThresh;
int consecThresh;
int secs;
int blockThresh;
int blockX;
@ -66,7 +60,6 @@ struct shared_t
int retCode;
};
string genTmpFile(const string &dirOut, const string &ext, shared_t *share);
string genDstFile(const string &dirOut, const char *fmt, const string &ext);
string genTimeStr(const char *fmt);
string cleanDir(const string &path);
@ -84,6 +77,5 @@ 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> loadClassList();
#endif // COMMON_H

View File

@ -11,7 +11,6 @@
// GNU General Public License for more details.
#include "mo_detect.h"
#include "obj_detect.h"
void detectLoop(shared_t *share)
{
@ -23,16 +22,11 @@ void detectLoop(shared_t *share)
if ((bufFiles.size() >= 2) || (share->recLoopWait && !bufFiles.empty()))
{
Rect blockArea;
Mat blockImg;
auto fullPath = cleanDir(share->buffDir) + "/" + bufFiles[0];
share->stat.clear();
if (moDetect(fullPath, &blockArea, &blockImg, share))
{
if (objectInImage(blockImg, blockArea, share))
if (moDetect(fullPath, share))
{
share->skipCmd = true;
@ -42,11 +36,6 @@ void detectLoop(shared_t *share)
{
remove(fullPath.c_str());
}
}
else
{
remove(fullPath.c_str());
}
statOut(share);
}
@ -108,7 +97,6 @@ int main(int argc, char** argv)
else
{
sharedRes.retCode = 0;
sharedRes.tmpId = 0;
sharedRes.recLoopWait = false;
sharedRes.skipCmd = false;
sharedRes.init = true;

View File

@ -14,33 +14,30 @@
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share)
{
auto diff = 0;
if (pixA > pixB) return true;
if (pixB > pixA) return true;
if (pixA > pixB) diff = pixA - pixB;
if (pixB > pixA) diff = pixB - pixA;
if (diff < share->colorThresh)
{
diff = 0;
}
return diff != 0;
return false;
}
void secDiff(const Mat &imgA, const Mat &imgB, int rows, int cols, int rowOffs, int colOffs, vector<sec_t> *results, mutex *secMutex, shared_t *share)
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 < rows; y++)
for (auto y = rowOffs; y < (rowOffs + rows); y++)
{
for (auto x = colOffs; x < cols; x++)
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))
if (pixDiff(pixA, pixB, share)) pnts += 1;
else pnts = 0;
if (pnts >= share->consecThresh)
{
pnts += 1;
diff += 1;
}
}
}
@ -51,27 +48,28 @@ void secDiff(const Mat &imgA, const Mat &imgB, int rows, int cols, int rowOffs,
res.y = rowOffs;
res.xSize = cols;
res.ySize = rows;
res.pixDiff = pnts;
res.pixDiff = diff;
res.id = id;
lock_guard<mutex> guard(*secMutex);
results->push_back(res);
}
bool imgDiff(const Mat &prev, const Mat &next, Rect *block, shared_t *share)
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
{
auto moInBlock = false;
vector<thread> threads;
vector<sec_t> results;
mutex secMutex;
auto id = 0;
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)
for (auto y = 0; y < prev.rows; y += share->blockY, id += 1)
{
threads.push_back(thread(secDiff, prev, next, share->blockY, share->blockX, y, x, &results, &secMutex, share));
threads.push_back(thread(secDiff, prev, next, id, share->blockY, share->blockX, y, x, &results, &secMutex, share));
}
}
@ -91,12 +89,12 @@ bool imgDiff(const Mat &prev, const Mat &next, Rect *block, shared_t *share)
auto x = results[i].x;
auto y = results[i].y;
auto diff = results[i].pixDiff;
auto id = results[i].id;
share->stat += string("block_thread:")
+ " x=" + to_string(x)
+ " y=" + to_string(y)
+ " pixdiff=" + to_string(diff)
+ "\n";
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))
{
@ -105,25 +103,10 @@ bool imgDiff(const Mat &prev, const Mat &next, Rect *block, shared_t *share)
}
}
if (maxPixDiff >= share->blockThresh)
{
// return true on this function with the block with the
// high pixDiff value which should be at or exceeds
// the block_threshold set by the conf file.
auto res = results[blockPick];
block->x = res.x;
block->y = res.y;
block->height = res.ySize;
block->width = res.xSize;
moInBlock = true;
}
return moInBlock;
return maxPixDiff >= share->blockThresh;
}
bool moDetect(const string &buffFile, Rect *block, Mat *img, shared_t *share)
bool moDetect(const string &buffFile, shared_t *share)
{
auto mod = false;
@ -134,15 +117,10 @@ bool moDetect(const string &buffFile, Rect *block, Mat *img, shared_t *share)
Mat prev;
Mat next;
share->stat += "motion_detection-- clip_file - " + buffFile + "\n";
for (auto i = 0; capPair(prev, next, capture, share); ++i)
while (capPair(prev, next, capture, share))
{
share->stat += "frame_pair-- " + to_string(i) + "\n";
if (imgDiff(toGray(prev), toGray(next), block, share))
if (imgDiff(toGray(prev), toGray(next), share))
{
*img = next;
mod = true; break;
}
}

View File

@ -17,6 +17,7 @@
struct sec_t
{
int id;
int x;
int y;
int xSize;
@ -24,9 +25,9 @@ struct sec_t
int pixDiff;
};
void secDiff(const Mat &imgA, const Mat &imgB, int rows, int cols, int rowOffs, int colOffs, vector<sec_t> *results, mutex *secMutex, shared_t *share);
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, Rect *block, shared_t *share);
bool moDetect(const string &buffFile, Rect *block, Mat *img, shared_t *share);
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share);
bool moDetect(const string &buffFile, shared_t *share);
#endif // MO_DETECT_H

View File

@ -1,64 +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 "obj_detect.h"
bool objectInImage(const Mat &src, const Rect &area, shared_t *share)
{
// reference: https://github.com/doleron/yolov5-opencv-cpp-python/blob/main/cpp/yolo.cpp
Mat blockImage(src, area);
Mat blob;
dnn::blobFromImage(blockImage, blob, 1./255., Size(area.width, area.height), cv::Scalar(), true, false);
share->network.setInput(blob);
vector<Mat> outputs;
share->network.forward(outputs, share->network.getUnconnectedOutLayersNames());
float *data = (float *)outputs[0].data;
share->stat += "object_detection---\n";
share->stat += " outputs_size=" + to_string(outputs.size()) + " looking_for_objects...\n";
for (int i = 0; i < 25200; ++i)
{
// confidence level data[4];
if (data[4] >= 0.4)
{
float *classesScores = data + 5;
Mat scores(1, share->classNames.size(), CV_32FC1, classesScores);
Point classId;
double maxClassScore;
minMaxLoc(scores, 0, &maxClassScore, 0, &classId);
share->stat += " potential_object[confidence_level]=" + to_string(data[4]) + "\n";
share->stat += " potential_object[class_score]=" + to_string(maxClassScore) + "\n";
if (maxClassScore > 0.2)
{
share->stat += " object_confirmed\n";
return true;
}
}
data += 85;
}
share->stat += " no_objects_found\n";
return false;
}

View File

@ -1,20 +0,0 @@
#ifndef OBJ_DETECT_H
#define OBJ_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"
bool objectInImage(const Mat &image, const Rect &area, shared_t *share);
#endif // OBJ_DETECT_H