Compare commits

..

No commits in common. "01a11741c780113becbea2ce5cec8a27bbe5a1bc" and "42814ab8ca10d4ce098531524f38d7a2df5d24fa" have entirely different histories.

12 changed files with 313 additions and 71 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 )
add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/obj_detect.cpp )
target_link_libraries( mow ${OpenCV_LIBS} )

View File

@ -45,15 +45,20 @@ buff_dir = /tmp/ramdisk/cam_name
# recommend to use a ramdisk tempfs for this since this directory is used
# for lots of writes.
#
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.
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.
#
block_threshold = 1024
# this value tells the application how many "lines" of pixels need to
# exceed consec_threshold before being considered motion.
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_x = 64
# this is the x coordinate size or horizontal size of a block of pixels

80
etc/classes.txt Normal file
View File

@ -0,0 +1,80 @@
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

BIN
etc/yolov5s.onnx Normal file

Binary file not shown.

View File

@ -1,3 +1,6 @@
#!/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,6 +140,18 @@ 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;
@ -169,6 +181,21 @@ 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());
@ -188,15 +215,20 @@ bool rdConf(shared_t *share)
share->postCmd.clear();
share->buffDir.clear();
share->consecThresh = 512;
share->colorThresh = 5;
share->secs = 60;
share->blockX = 32;
share->blockY = 32;
share->blockThresh = 1024;
share->blockThresh = 900;
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
{
@ -207,7 +239,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("consec_threshold = ", line, &share->consecThresh);
rdLine("color_threshold = ", line, &share->colorThresh);
rdLine("duration = ", line, &share->secs);
rdLine("buff_dir = ", line, &share->buffDir);
rdLine("block_x = ", line, &share->blockX);
@ -234,9 +266,6 @@ 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;
}
@ -307,10 +336,10 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
void statOut(shared_t *share)
{
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
createDirTree(cleanDir(share->buffDir));
auto path = string(cleanDir(share->buffDir) + "/stat");
auto fd = open(path.c_str(), fstream::out | fstream::trunc);
auto fd = open(path.c_str(), O_WRONLY);
write(fd, share->stat.c_str(), share->stat.size() + 1);
close(fd);

View File

@ -37,29 +37,36 @@ using namespace std;
using namespace std::filesystem;
#define BUF_SZ 10
#define APP_VER "1.5"
#define APP_VER "1.4.t7"
struct shared_t
{
string stat;
string recordUrl;
string outDir;
string postCmd;
string conf;
string buffDir;
string vidExt;
bool init;
bool recLoopWait;
bool skipCmd;
int consecThresh;
int secs;
int blockThresh;
int blockX;
int blockY;
int maxDays;
int retCode;
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 secs;
int blockThresh;
int blockX;
int blockY;
int maxDays;
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);
@ -77,5 +84,6 @@ 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,6 +11,7 @@
// GNU General Public License for more details.
#include "mo_detect.h"
#include "obj_detect.h"
void detectLoop(shared_t *share)
{
@ -22,15 +23,25 @@ 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, share))
if (moDetect(fullPath, &blockArea, &blockImg, share))
{
share->skipCmd = true;
if (objectInImage(blockImg, blockArea, share))
{
share->skipCmd = true;
wrOut(fullPath, share);
wrOut(fullPath, share);
}
else
{
remove(fullPath.c_str());
}
}
else
{
@ -97,6 +108,7 @@ 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,30 +14,33 @@
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share)
{
if (pixA > pixB) return true;
if (pixB > pixA) return true;
auto diff = 0;
return false;
if (pixA > pixB) diff = pixA - pixB;
if (pixB > pixA) diff = pixB - pixA;
if (diff < share->colorThresh)
{
diff = 0;
}
return diff != 0;
}
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)
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)
{
auto diff = 0;
auto pnts = 0;
for (auto y = rowOffs; y < (rowOffs + rows); y++)
for (auto y = rowOffs; y < rows; y++)
{
for (auto x = colOffs; x < (colOffs + cols); x++)
for (auto x = colOffs; x < cols; x++)
{
auto pixA = imgA.at<uchar>(Point(x, y));
auto pixB = imgB.at<uchar>(Point(x, y));
if (pixDiff(pixA, pixB, share)) pnts += 1;
else pnts = 0;
if (pnts >= share->consecThresh)
if (pixDiff(pixA, pixB, share))
{
diff += 1;
pnts += 1;
}
}
}
@ -48,28 +51,27 @@ void secDiff(const Mat &imgA, const Mat &imgB, int id, int rows, int cols, int r
res.y = rowOffs;
res.xSize = cols;
res.ySize = rows;
res.pixDiff = diff;
res.id = id;
res.pixDiff = pnts;
lock_guard<mutex> guard(*secMutex);
results->push_back(res);
}
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
bool imgDiff(const Mat &prev, const Mat &next, Rect *block, 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, id += 1)
for (auto y = 0; y < prev.rows; y += share->blockY)
{
threads.push_back(thread(secDiff, prev, next, id, share->blockY, share->blockX, y, x, &results, &secMutex, share));
threads.push_back(thread(secDiff, prev, next, share->blockY, share->blockX, y, x, &results, &secMutex, share));
}
}
@ -89,13 +91,13 @@ bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
auto x = results[i].x;
auto y = results[i].y;
auto diff = results[i].pixDiff;
auto id = results[i].id;
if (diff > 0)
{
share->stat += string("block_thread:") + " id=" + to_string(id) + " diff=" + to_string(diff) + "\n";
}
share->stat += string("block_thread:")
+ " x=" + to_string(x)
+ " y=" + to_string(y)
+ " pixdiff=" + to_string(diff)
+ "\n";
if ((results[i].pixDiff >= share->blockThresh) && (results[i].pixDiff > maxPixDiff))
{
maxPixDiff = results[i].pixDiff;
@ -103,10 +105,25 @@ bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
}
}
return maxPixDiff >= share->blockThresh;
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;
}
bool moDetect(const string &buffFile, shared_t *share)
bool moDetect(const string &buffFile, Rect *block, Mat *img, shared_t *share)
{
auto mod = false;
@ -117,10 +134,15 @@ bool moDetect(const string &buffFile, shared_t *share)
Mat prev;
Mat next;
while (capPair(prev, next, capture, share))
share->stat += "motion_detection-- clip_file - " + buffFile + "\n";
for (auto i = 0; capPair(prev, next, capture, share); ++i)
{
if (imgDiff(toGray(prev), toGray(next), share))
share->stat += "frame_pair-- " + to_string(i) + "\n";
if (imgDiff(toGray(prev), toGray(next), block, share))
{
*img = next;
mod = true; break;
}
}

View File

@ -17,7 +17,6 @@
struct sec_t
{
int id;
int x;
int y;
int xSize;
@ -25,9 +24,9 @@ struct sec_t
int pixDiff;
};
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);
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);
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share);
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share);
bool moDetect(const string &buffFile, 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);
#endif // MO_DETECT_H

64
src/obj_detect.cpp Normal file
View File

@ -0,0 +1,64 @@
// 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;
}

20
src/obj_detect.h Normal file
View File

@ -0,0 +1,20 @@
#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