diff --git a/CMakeLists.txt b/CMakeLists.txt index 213eb8d..0e38807 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,9 @@ project(Kerberos) # --------------------------- # XCode system library - link_directories("/usr/local/lib") + if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + link_directories("/usr/local/lib") + endif() # --------- # Package diff --git a/cmake/External-OpenCV.cmake b/cmake/External-OpenCV.cmake index 1dfd7f4..01d63ed 100644 --- a/cmake/External-OpenCV.cmake +++ b/cmake/External-OpenCV.cmake @@ -18,14 +18,14 @@ ExternalProject_Add(opencv -DBUILD_opencv_core=ON -DBUILD_opencv_imgproc=ON -DBUILD_opencv_highgui=ON - -DBUILD_opencv_video=OFF + -DBUILD_opencv_video=ON -DBUILD_opencv_apps=OFF - -DBUILD_opencv_flann=OFF + -DBUILD_opencv_flann=ON -DBUILD_opencv_gpu=OFF - -DBUILD_opencv_ml=OFF + -DBUILD_opencv_ml=ON -DBUILD_opencv_legacy=OFF -DBUILD_opencv_calib3d=OFF - -DBUILD_opencv_features2d=OFF + -DBUILD_opencv_features2d=ON -DBUILD_opencv_java=OFF -DBUILD_opencv_objdetect=OFF -DBUILD_opencv_photo=OFF @@ -38,6 +38,7 @@ ExternalProject_Add(opencv -DBUILD_SHARED_LIBS:BOOL=OFF -DBUILD_TESTS:BOOL=OFF -DBUILD_PERF_TESTS:BOOL=OFF + -DBUILD_opencv_contrib=ON -DCMAKE_BUILD_TYPE:STRING=Release -DWITH_FFMPEG:BOOL=ON -DWITH_IPP:BOOL=OFF @@ -56,7 +57,7 @@ else() set(OPENCV_LIBRARY_DIR ${CMAKE_BINARY_DIR}/thirdparty/x86/vc12/lib) endif() -set(OPENCV_LIBRARIES opencv_imgproc opencv_core opencv_highgui opencv_videoio opencv_imgcodecs) +set(OPENCV_LIBRARIES opencv_imgproc opencv_core opencv_highgui opencv_video opencv_videoio opencv_imgcodecs opencv_features2d) include_directories(${OPENCV_INCLUDE_DIR}) link_directories(${OPENCV_LIBRARY_DIR}) diff --git a/config/algorithm.xml b/config/algorithm.xml index 5411f96..a82128c 100755 --- a/config/algorithm.xml +++ b/config/algorithm.xml @@ -6,4 +6,14 @@ 15 + + true + 50 + 5 + 1 + 5 + 7 + 20 + + diff --git a/config/capture.xml b/config/capture.xml index 097fd09..a43725a 100755 --- a/config/capture.xml +++ b/config/capture.xml @@ -18,10 +18,18 @@ + 1280 + 720 + 500 + 0 + + + 800 640 + 0 500 0 - + diff --git a/config/config.xml b/config/config.xml index ca72bd3..77fb86f 100755 --- a/config/config.xml +++ b/config/config.xml @@ -6,6 +6,7 @@ false Europe-Brussels RaspiCamera + Mjpg Enabled DifferentialCollins Hull diff --git a/config/heuristic.xml b/config/heuristic.xml index d8fd2d2..1ec5807 100755 --- a/config/heuristic.xml +++ b/config/heuristic.xml @@ -6,5 +6,15 @@ 2 1000 + + + 5 + 90 + 1400 + true + 20 + 1000 + 100,100|100,200|200,100|200,200 + diff --git a/config/io.xml b/config/io.xml index c2f38f4..c93db02 100644 --- a/config/io.xml +++ b/config/io.xml @@ -4,6 +4,8 @@ timestamp_microseconds_instanceName_regionCoordinates_numberOfChanges_token.jpg /etc/opt/kerberosio/capture/ + false + white 17 diff --git a/config/stream.xml b/config/stream.xml new file mode 100644 index 0000000..aa7b503 --- /dev/null +++ b/config/stream.xml @@ -0,0 +1,10 @@ + + + + + true + 8889 + 75 + + + diff --git a/docker/Dockerfile b/docker/Dockerfile index cecfa50..c578906 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM phusion/baseimage:latest +FROM phusion/baseimage:0.9.16 MAINTAINER "Cédric Verstraeten" diff --git a/include/kerberos/Globals.h b/include/kerberos/Globals.h index 5b4d246..a90cb93 100644 --- a/include/kerberos/Globals.h +++ b/include/kerberos/Globals.h @@ -14,7 +14,7 @@ #ifndef __Version_H_INCLUDED__ // if Version.h hasn't been included yet... #define __Version_H_INCLUDED__ // #define this so the compiler knows it has been included - #define VERSION "2.0.0" + #define VERSION "2.1.0" #define HADES "https://hades.kerberos.io" #define SYMBOL_DIRECTORY "/etc/opt/kerberosio/symbols/" #define CONFIGURATION_PATH "/etc/opt/kerberosio/config/config.xml" diff --git a/include/kerberos/Kerberos.h b/include/kerberos/Kerberos.h index 7cf28f1..465fee3 100644 --- a/include/kerberos/Kerberos.h +++ b/include/kerberos/Kerberos.h @@ -35,7 +35,8 @@ namespace kerberos void configure(const std::string & configuration); void configureCapture(StringMap & settings); void configureCloud(StringMap & settings); - void startStreamThread(); + void configureStream(StringMap & settings); + void startStreamThread(); void stopStreamThread(); void startIOThread(); void stopIOThread(); diff --git a/include/kerberos/capture/Image.h b/include/kerberos/capture/Image.h index 621eaf5..255e86f 100644 --- a/include/kerberos/capture/Image.h +++ b/include/kerberos/capture/Image.h @@ -53,6 +53,7 @@ namespace kerberos void difference(const Image & image, Image & result); void bitwiseAnd(const Image & image, Image & result); void erode(const Image & kernel); + void dilate(const Image & kernel); void threshold(const int threshold); int brightness(); static cv::Mat createKernel(int width, int height) diff --git a/include/kerberos/capture/Stream.h b/include/kerberos/capture/Stream.h index da5babf..04ca79f 100644 --- a/include/kerberos/capture/Stream.h +++ b/include/kerberos/capture/Stream.h @@ -25,6 +25,7 @@ #include #include #include +#include #define PORT unsigned short #define SOCKET int #define HOSTENT struct hostent @@ -42,8 +43,10 @@ namespace kerberos std::vector clients; SOCKET sock; fd_set master; - int timeout; // master sock timeout, shutdown after timeout millis. - int quality; // jpeg compression [1..100] + bool m_enabled; + int m_streamPort; + int m_timeout; // master sock timeout, shutdown after timeout millis. + int m_quality; // jpeg compression [1..100] int _write( int sock, char *s, int len ) { @@ -57,10 +60,9 @@ namespace kerberos public: - Stream(int port = 0) : sock(INVALID_SOCKET), timeout(10), quality(50) + Stream() : sock(INVALID_SOCKET), m_timeout(10), m_quality(70) { FD_ZERO( &master ); - if (port) open(port); } ~Stream() @@ -68,8 +70,9 @@ namespace kerberos release(); } + void configureStream(StringMap & settings); bool release(); - bool open(int port); + bool open(); bool isOpened(); bool connect(); void write(Image image); diff --git a/include/kerberos/capture/VideoCapture.h b/include/kerberos/capture/VideoCapture.h new file mode 100644 index 0000000..88b36c0 --- /dev/null +++ b/include/kerberos/capture/VideoCapture.h @@ -0,0 +1,63 @@ +// +// Class: VideoCapture +// Description: Class that plays a video file. +// Created: 17/05/2016 +// Author: Cédric Verstraeten +// Mail: hello@cedric.ws +// Website: www.cedric.ws +// +// The copyright to the computer program(s) herein +// is the property of Cédric Verstraeten, Belgium. +// The program(s) may be used and/or copied . +// +///////////////////////////////////////////////////// + +#ifndef __VideoCapture_H_INCLUDED__ // if VideoCapture.h hasn't been included yet... +#define __VideoCapture_H_INCLUDED__ // #define this so the compiler knows it has been included + +#include "capture/Capture.h" +#include "Executor.h" + +namespace kerberos +{ + char VideoCaptureName[] = "VideoCapture"; + class VideoCapture : public CaptureCreator + { + private: + cv::VideoCapture * m_video; + std::string m_path; + + public: + VideoCapture() + { + try + { + m_video = new cv::VideoCapture(); + } + catch(cv::Exception & ex) + { + throw OpenCVException(ex.msg.c_str()); + } + } + + VideoCapture(int width, int height); + virtual ~VideoCapture(){}; + void setup(StringMap & settings); + void setImageSize(int width, int height); + void setRotation(int angle){Capture::setRotation(angle);} + void setDelay(int msec){Capture::setDelay(msec);} + void setPath(std::string path){m_path=path;} + std::string getPath(){return m_path;} + + void grab(); + Image retrieve(); + Image * takeImage(); + + void open(); + void close(); + void update(); + bool isOpened(); + }; +} + +#endif \ No newline at end of file diff --git a/include/kerberos/machinery/algorithm/BackgroundSubtraction.h b/include/kerberos/machinery/algorithm/BackgroundSubtraction.h new file mode 100644 index 0000000..f5740c4 --- /dev/null +++ b/include/kerberos/machinery/algorithm/BackgroundSubtraction.h @@ -0,0 +1,45 @@ +// +// Class: BackgroundSubtraction +// Description: An algorithm to detect motion using background subtraction. +// Created: 17/05/2016 +// Author: Cédric Verstraeten +// Mail: hello@cedric.ws +// Website: www.kerberos.io +// +// The copyright to the computer program(s) herein +// is the property of kerberos.io, Belgium. +// The program(s) may be used and/or copied . +// +///////////////////////////////////////////////////// + +#ifndef __BackgroundSubtraction_H_INCLUDED__ // if BackgroundSubtraction.h hasn't been included yet... +#define __BackgroundSubtraction_H_INCLUDED__ // #define this so the compiler knows it has been included + +#include "machinery/algorithm/Algorithm.h" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/video/video.hpp" + +namespace kerberos +{ + char BackgroundSubtractionName[] = "BackgroundSubtraction"; + class BackgroundSubtraction : public AlgorithmCreator + { + private: + int m_threshold; + Image m_erodeKernel; + Image m_dilateKernel; + Image m_backgroud; + cv::Ptr m_subtractor; + + public: + BackgroundSubtraction(){} + void setup(const StringMap & settings); + + void setErodeKernel(int width, int height); + void setDilateKernel(int width, int height); + void setThreshold(int threshold); + void initialize(ImageVector & images); + Image evaluate(ImageVector & images, JSON & data); + }; +} +#endif \ No newline at end of file diff --git a/include/kerberos/machinery/heuristic/Counter.h b/include/kerberos/machinery/heuristic/Counter.h new file mode 100644 index 0000000..4b7f2a0 --- /dev/null +++ b/include/kerberos/machinery/heuristic/Counter.h @@ -0,0 +1,87 @@ +// +// Class: Counter +// Description: The counter heuristic will check if a valid in-out (out-in) +// happened. The detections in and out are kept in memory. +// E.g. total people in the building. +// Created: 28/04/2015 +// Author: Cédric Verstraeten +// Mail: hello@cedric.ws +// Website: www.kerberos.io +// +// The copyright to the computer program(s) herein +// is the property of kerberos.io, Belgium. +// The program(s) may be used and/or copied . +// +///////////////////////////////////////////////////// + +#ifndef __Counter_H_INCLUDED__ // if Counter.h hasn't been included yet... +#define __Counter_H_INCLUDED__ // #define this so the compiler knows it has been included + +#include "machinery/heuristic/Heuristic.h" + +namespace kerberos +{ + char CounterName[] = "Counter"; + + class Feature + { + private: + int x; + int y; + int area; + int appearance; + + public: + Feature(int x, int y, int area, int appearance):x(x), y(y), area(area), appearance(appearance){}; + int getX(){return x;} + int getY(){return y;} + int getArea(){return area;} + int getAppearance(){return appearance;} + + double distance(Feature f) + { + return sqrt((f.x - x) * (f.x - x) + (f.y - y) * (f.y - y)); + } + + double areaDistance(Feature f) + { + return sqrt((f.area - area) * (f.area - area)); + } + + void decreaseAppearance() + { + appearance--; + } + }; + + enum Direction{left, right, top, bottom, parallell}; + + class Counter : public HeuristicCreator + { + private: + std::vector m_in; + std::vector m_out; + int m_minimumChanges; + int m_noMotionDelayTime; + int m_appearance; + int m_maxDistance; + int m_minArea; + bool m_onlyTrueWhenCounted; + + std::vector > m_features; + cv::Mat m_status; + + public: + Counter(){} + void setup(const StringMap & settings); + void setMinimumChanges(int changes){m_minimumChanges=changes;}; + void setNoMotionDelayTime(int delay){m_noMotionDelayTime=delay;}; + void setAppearance(int appearance){m_appearance=appearance;}; + void setMaxDistance(int maxDistance){m_maxDistance=maxDistance;}; + void setMinArea(int minArea){m_minArea=minArea;}; + void setOnlyTrueWhenCounted(bool onlyTrueWhenCounted){m_onlyTrueWhenCounted=onlyTrueWhenCounted;}; + bool intersection(cv::Point2f o1, cv::Point2f p1, cv::Point2f o2, cv::Point2f p2, cv::Point2f &r); + bool isValid(const Image & evaluation, const ImageVector & images, JSON & data); + }; +} +#endif \ No newline at end of file diff --git a/include/kerberos/machinery/io/IoDisk.h b/include/kerberos/machinery/io/IoDisk.h index 5af414d..a4a7bc9 100644 --- a/include/kerberos/machinery/io/IoDisk.h +++ b/include/kerberos/machinery/io/IoDisk.h @@ -25,16 +25,27 @@ namespace kerberos private: std::string m_instanceName; std::string m_fileFormat; + bool m_drawTimestamp; + cv::Scalar m_timestampColor; + std::string m_timezone; FileManager m_fileManager; public: IoDisk(){}; void setup(const StringMap & settings); - void setInstanceName(std::string instanceName){m_instanceName=instanceName;}; + cv::Scalar getColor(const std::string name); + bool getDrawTimestamp(){return m_drawTimestamp;}; + void setDrawTimestamp(bool drawTimestamp){m_drawTimestamp=drawTimestamp;}; + std::string getTimezone(){return m_timezone;}; + void setTimezone(std::string timezone){m_timezone=timezone;}; + cv::Scalar getTimestampColor(){return m_timestampColor;}; + void setTimestampColor(cv::Scalar timestampColor){m_timestampColor=timestampColor;}; std::string getInstanceName(){return m_instanceName;}; + void setInstanceName(std::string instanceName){m_instanceName=instanceName;}; std::string getFileFormat(){return m_fileFormat;}; void setFileFormat(std::string fileFormat){m_fileFormat = fileFormat;}; std::string buildPath(std::string pathToImage); + void drawDateOnImage(Image & image, std::string timestamp); bool save(Image & image); bool save(Image & image, JSON & data); }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a1233e..a00c37a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,9 +130,9 @@ SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Cédric Verstraeten") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Kerberos is a low-budget surveillance solution, that uses computer vision algorithms to detect changes, and that can trigger other devices. Kerberos is open source so you, but also other people, can customize the source to your needs and share it with our community. It has a green footprint when deploying on the Raspberry Pi and it's easy to install, you only need to transfer the image to the SD card and you're done.") - set(CPACK_PACKAGE_VERSION "2.0.0") + set(CPACK_PACKAGE_VERSION "2.1.0") set(CPACK_PACKAGE_VERSION_MAJOR "2") - set(CPACK_PACKAGE_VERSION_MINOR "0") + set(CPACK_PACKAGE_VERSION_MINOR "1") set(CPACK_PACKAGE_VERSION_PATCH "0") string(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE) diff --git a/src/kerberos/CMakeLists.txt b/src/kerberos/CMakeLists.txt index d1ea45a..e489da8 100644 --- a/src/kerberos/CMakeLists.txt +++ b/src/kerberos/CMakeLists.txt @@ -31,19 +31,22 @@ cloud/GoogleDrive.cpp capture/USBCamera.cpp capture/IPCamera.cpp + capture/VideoCapture.cpp machinery/condition/Time.cpp machinery/condition/Enabled.cpp machinery/algorithm/DifferentialCollins.cpp + machinery/algorithm/BackgroundSubtraction.cpp machinery/expositor/RectangleExpositor.cpp machinery/expositor/HullExpositor.cpp machinery/heuristic/Sequence.cpp + machinery/heuristic/Counter.cpp machinery/io/IoDisk.cpp machinery/io/IoTCP.cpp machinery/io/IoWebhook.cpp ) set(KERBEROS_DEPENDENCIES ${KERBEROS_DEPENDENCIES} opencv restclient) - set(KERBEROS_LIBRARIES ${KERBEROS_LIBRARIES} ${OPENCV_LIBRARIES} ${RESTCLIENT_LIBRARIES}) + set(KERBEROS_LIBRARIES ${KERBEROS_LIBRARIES} ${OPENCV_LIBRARIES} ${RESTCLIENT_LIBRARIES} pthread) # ------------------------------------------- diff --git a/src/kerberos/Kerberos.cpp b/src/kerberos/Kerberos.cpp index 90ef66d..5347d90 100644 --- a/src/kerberos/Kerberos.cpp +++ b/src/kerberos/Kerberos.cpp @@ -9,22 +9,12 @@ namespace kerberos setParameters(parameters); - // ---------------- - // Initialize mutex - - pthread_mutex_init(&m_streamLock, NULL); - // --------------------- // Initialize kerberos std::string configuration = (helper::getValueByKey(parameters, "config")) ?: CONFIGURATION_PATH; configure(configuration); - // ------------------ - // Open the stream - - startStreamThread(); - // ------------------ // Open the io thread @@ -95,7 +85,6 @@ namespace kerberos // Shift images m_images = capture->shiftImage(); - usleep(250*1000); } } @@ -193,14 +182,20 @@ namespace kerberos } // ---------------------------------- - // Configure capture device + thread + // Configure capture device + stream void Kerberos::configureCapture(StringMap & settings) { - // --------------------------- - // Initialize capture device + // ----------------------- + // Stop stream and capture + + if(stream != 0) + { + LINFO << "Stopping streaming"; + stopStreamThread(); + delete stream; + } - pthread_mutex_lock(&m_streamLock); if(capture != 0) { LINFO << "Stopping capture device"; @@ -209,11 +204,20 @@ namespace kerberos delete capture; } + // --------------------------- + // Initialize capture device + LINFO << "Starting capture device: " + settings.at("capture"); capture = Factory::getInstance()->create(settings.at("capture")); capture->setup(settings); capture->startGrabThread(); - pthread_mutex_unlock(&m_streamLock); + + // ------------------ + // Initialize stream + + stream = new Stream(); + stream->configureStream(settings); + startStreamThread(); } // ---------------------------------- @@ -249,7 +253,6 @@ namespace kerberos { try { - pthread_mutex_lock(&kerberos->m_streamLock); kerberos->stream->connect(); Image image = kerberos->capture->retrieve(); @@ -258,14 +261,9 @@ namespace kerberos image.rotate(kerberos->capture->m_angle); } kerberos->stream->write(image); - - pthread_mutex_unlock(&kerberos->m_streamLock); - usleep(800*100); - } - catch(cv::Exception & ex) - { - pthread_mutex_unlock(&kerberos->m_streamLock); + usleep(200*1000); // sleep 200ms } + catch(cv::Exception & ex){} } } @@ -274,11 +272,10 @@ namespace kerberos // ------------------------------------------------ // Start a new thread that streams MJPEG's continuously. - if(stream == 0) + if(stream != 0) { - int port = 8888; - LINFO << "Starting stream on port " + helper::to_string(port); - stream = new Stream(port); + //if stream object just exists try to open configured stream port + stream->open(); } pthread_create(&m_streamThread, NULL, streamContinuously, this); diff --git a/src/kerberos/capture/Image.cpp b/src/kerberos/capture/Image.cpp index 83c5cb4..b46016a 100644 --- a/src/kerberos/capture/Image.cpp +++ b/src/kerberos/capture/Image.cpp @@ -156,6 +156,18 @@ namespace kerberos } } + void Image::dilate(const Image & kernel) + { + try + { + cv::dilate(m_image, m_image, kernel.m_image); + } + catch(cv::Exception & ex) + { + throw OpenCVException(ex.msg.c_str()); + } + } + int Image::brightness() { try diff --git a/src/kerberos/capture/RaspiCamera.cpp b/src/kerberos/capture/RaspiCamera.cpp index f9f64bd..32f146a 100644 --- a/src/kerberos/capture/RaspiCamera.cpp +++ b/src/kerberos/capture/RaspiCamera.cpp @@ -115,6 +115,13 @@ namespace kerberos void RaspiCamera::open() { m_camera->open(); + + // ---------- + // sleep a second, to be sure the camera is enabled properly. + // when reconfiguring the machinery, the process get blocked, if + // the raspicamera isn't enabled properly. + + usleep(1000*1000); } void RaspiCamera::close() diff --git a/src/kerberos/capture/Stream.cpp b/src/kerberos/capture/Stream.cpp index d6ae413..ba9368a 100644 --- a/src/kerberos/capture/Stream.cpp +++ b/src/kerberos/capture/Stream.cpp @@ -2,6 +2,32 @@ namespace kerberos { + + // ---------------------------------- + // Configure stream thread settings + + void Stream::configureStream(StringMap & settings) + { + //read port from settings + int enabled = (settings.at("streams.Mjpg.enabled") == "true"); + int port = std::atoi(settings.at("streams.Mjpg.streamPort").c_str()); + int quality = std::atoi(settings.at("streams.Mjpg.quality").c_str()); + + //use port up to well known ports range + if(port >= 1024) + { + //TODO: here it would be nice to check if port is valid and free + m_enabled = enabled; + m_streamPort = port; + m_quality = quality; + } + else + { + LERROR << "Settings: can't use invalid port"; + //TODO: manage invalid port error + } + } + bool Stream::release() { for(int i = 0; i < clients.size(); i++) @@ -15,41 +41,57 @@ namespace kerberos if (sock != INVALID_SOCKET) { shutdown(sock, 2); + close(sock); } sock = (INVALID_SOCKET); + + LINFO << "Stream: Succesfully closed streaming"; return false; } - bool Stream::open(int port) + bool Stream::open() { - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - int reuse = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); - - SOCKADDR_IN address; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_family = AF_INET; - address.sin_port = htons(port); - - while(bind(sock, (SOCKADDR*) &address, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) + if(m_enabled) { - LERROR << "Stream: couldn't bind sock"; - release(); - usleep(1000*10000); - sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - } - - while(listen(sock, 2) == SOCKET_ERROR) - { - LERROR << "Stream: couldn't listen on sock"; - usleep(1000*10000); + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + int reuse = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); + + struct timeval timeout; + timeout.tv_sec = 3; + timeout.tv_usec = 0; + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (struct timeval *)&timeout,sizeof(timeout)); + + SOCKADDR_IN address; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_family = AF_INET; + address.sin_port = htons(m_streamPort); + + while(bind(sock, (SOCKADDR*) &address, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) + { + LERROR << "Stream: couldn't bind sock"; + release(); + usleep(1000*10000); + sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } + + while(listen(sock, 2) == SOCKET_ERROR) + { + LERROR << "Stream: couldn't listen on sock"; + usleep(1000*10000); + } + + FD_SET(sock, &master); + + + LINFO << "Stream: Configured stream on port " << helper::to_string(m_streamPort) << " with quality: " << helper::to_string(m_quality); + + return true; } - - FD_SET(sock, &master); - - return true; + + return false; } bool Stream::isOpened() @@ -60,7 +102,7 @@ namespace kerberos bool Stream::connect() { fd_set rread = master; - struct timeval to = {0,timeout}; + struct timeval to = {0,m_timeout}; SOCKET maxfd = sock+1; if(select( maxfd, &rread, NULL, NULL, &to ) <= 0) @@ -69,15 +111,20 @@ namespace kerberos int addrlen = sizeof(SOCKADDR); SOCKADDR_IN address = {0}; SOCKET client = accept(sock, (SOCKADDR*)&address, (socklen_t*) &addrlen); - + if (client == SOCKET_ERROR) { LERROR << "Stream: couldn't accept connection on sock"; - LINFO << "Stream: reopening master sock"; + LERROR << "Stream: reopening master sock"; release(); - open(8888); + open(); return false; } + + struct timeval timeout; + timeout.tv_sec = 3; + timeout.tv_usec = 0; + setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&timeout, sizeof(timeout)); maxfd=(maxfd>client?maxfd:client); FD_SET( client, &master ); @@ -90,6 +137,8 @@ namespace kerberos "Pragma: no-cache\r\n" "Content-Type: multipart/x-mixed-replace; boundary=mjpegstream\r\n" "\r\n",0); + + LINFO << "Stream: opening socket for new client"; clients.push_back(client); packetsSend[client] = 0; @@ -112,7 +161,7 @@ namespace kerberos std::vectoroutbuf; std::vector params; params.push_back(cv::IMWRITE_JPEG_QUALITY); - params.push_back(quality); + params.push_back(m_quality); cv::imencode(".jpg", frame, outbuf, params); int outlen = outbuf.size(); @@ -154,4 +203,4 @@ namespace kerberos } catch(cv::Exception & ex){} } -} \ No newline at end of file +} diff --git a/src/kerberos/capture/VideoCapture.cpp b/src/kerberos/capture/VideoCapture.cpp new file mode 100644 index 0000000..1af0753 --- /dev/null +++ b/src/kerberos/capture/VideoCapture.cpp @@ -0,0 +1,154 @@ +#include "capture/VideoCapture.h" + +namespace kerberos +{ + void VideoCapture::setup(kerberos::StringMap &settings) + { + int width = std::atoi(settings.at("captures.VideoCapture.frameWidth").c_str()); + int height = std::atoi(settings.at("captures.VideoCapture.frameHeight").c_str()); + std::string path = settings.at("captures.VideoCapture.path"); + int angle = std::atoi(settings.at("captures.VideoCapture.angle").c_str()); + int delay = std::atoi(settings.at("captures.VideoCapture.delay").c_str()); + + // Save width and height in settings + Capture::setup(settings, width, height); + setImageSize(width, height); + setRotation(angle); + setDelay(delay); + setPath(path); + + // Initialize video + open(); + } + + VideoCapture::VideoCapture(int width, int height) + { + try + { + m_video = new cv::VideoCapture(); + setImageSize(width, height); + } + catch(cv::Exception & ex) + { + throw OpenCVException(ex.msg.c_str()); + } + }; + + void VideoCapture::grab() + { + try + { + pthread_mutex_lock(&m_lock); + // A video doesn't need a grabber. + // m_video->grab(); + pthread_mutex_unlock(&m_lock); + } + catch(cv::Exception & ex) + { + pthread_mutex_unlock(&m_lock); + pthread_mutex_destroy(&m_lock); + throw OpenCVException(ex.msg.c_str()); + } + } + + Image VideoCapture::retrieve() + { + try + { + Image image; + pthread_mutex_lock(&m_lock); + m_video->retrieve(image.getImage()); + pthread_mutex_unlock(&m_lock); + return image; + } + catch(cv::Exception & ex) + { + pthread_mutex_unlock(&m_lock); + pthread_mutex_destroy(&m_lock); + throw OpenCVException(ex.msg.c_str()); + } + } + + Image * VideoCapture::takeImage() + { + // Take image + try + { + // Delay camera for some time.. + usleep(m_delay*1000); + cv::waitKey(10); // this is needed for video files. + + // Get image from camera + Image * image = new Image(); + + pthread_mutex_lock(&m_lock); + m_video->grab(); + m_video->retrieve(image->getImage()); + pthread_mutex_unlock(&m_lock); + + // Check if need to rotate the image + image->rotate(m_angle); + + return image; + } + catch(cv::Exception & ex) + { + pthread_mutex_unlock(&m_lock); + throw OpenCVException(ex.msg.c_str()); + } + } + + + void VideoCapture::setImageSize(int width, int height) + { + Capture::setImageSize(width, height); + try + { + m_video->set(CV_CAP_PROP_FRAME_WIDTH, m_frameWidth); + m_video->set(CV_CAP_PROP_FRAME_HEIGHT, m_frameHeight); + } + catch(cv::Exception & ex) + { + throw OpenCVException(ex.msg.c_str()); + } + } + + void VideoCapture::open() + { + try + { + if(!isOpened()) + { + m_video->release(); + m_video->open(getPath()); + //m_video->set(CV_CAP_PROP_POS_FRAMES, 1850); + setImageSize(m_frameWidth, m_frameHeight); + } + } + catch(cv::Exception & ex) + { + throw OpenCVException(ex.msg.c_str()); + } + } + + void VideoCapture::close() + { + try + { + pthread_mutex_unlock(&m_lock); + pthread_mutex_destroy(&m_lock); + m_video->release(); + } + catch(cv::Exception & ex) + { + throw OpenCVException(ex.msg.c_str()); + } + } + + void VideoCapture::update(){} + + bool VideoCapture::isOpened() + { + return m_video->isOpened(); + } +} \ No newline at end of file diff --git a/src/kerberos/machinery/algorithm/BackgroundSubtraction.cpp b/src/kerberos/machinery/algorithm/BackgroundSubtraction.cpp new file mode 100644 index 0000000..51fde6f --- /dev/null +++ b/src/kerberos/machinery/algorithm/BackgroundSubtraction.cpp @@ -0,0 +1,68 @@ +#include "machinery/algorithm/BackgroundSubtraction.h" + +namespace kerberos +{ + void BackgroundSubtraction::setup(const StringMap & settings) + { + Algorithm::setup(settings); + int erode = std::atoi(settings.at("algorithms.BackgroundSubtraction.erode").c_str()); + int dilate = std::atoi(settings.at("algorithms.BackgroundSubtraction.dilate").c_str()); + setErodeKernel(erode, erode); + setDilateKernel(dilate, dilate); + + m_subtractor = cv::createBackgroundSubtractorMOG2(); + + std::string shadows = settings.at("algorithms.BackgroundSubtraction.shadows"); + int history = std::atoi(settings.at("algorithms.BackgroundSubtraction.history").c_str()); + int nmixtures = std::atoi(settings.at("algorithms.BackgroundSubtraction.nmixtures").c_str()); + double ratio = std::atof(settings.at("algorithms.BackgroundSubtraction.ratio").c_str()); + int threshold = std::atoi(settings.at("algorithms.BackgroundSubtraction.threshold").c_str()); + m_subtractor->setDetectShadows((shadows == "true")); + m_subtractor->setHistory(history); + m_subtractor->setNMixtures(nmixtures); + m_subtractor->setBackgroundRatio(ratio); + m_subtractor->setVarThreshold(threshold); + m_subtractor->setVarThresholdGen(threshold); + } + + // --------------------------------------------- + // Convert all images (except last one) to gray + + void BackgroundSubtraction::initialize(ImageVector & images) + { + for(int i = 0; i < images.size()-1; i++) + { + m_subtractor->apply(images[i]->getImage(), m_backgroud.getImage()); + } + } + + Image BackgroundSubtraction::evaluate(ImageVector & images, JSON & data) + { + // ----------- + // Calculate + + m_subtractor->apply(images[2]->getImage(), m_backgroud.getImage()); + + cv::Mat brackgroundmodel; + m_subtractor->getBackgroundImage(brackgroundmodel); + m_backgroud.erode(m_erodeKernel); + m_backgroud.dilate(m_dilateKernel); + + return m_backgroud; + } + + void BackgroundSubtraction::setErodeKernel(int width, int height) + { + m_erodeKernel.setImage(Image::createKernel(width, height)); + } + + void BackgroundSubtraction::setDilateKernel(int width, int height) + { + m_dilateKernel.setImage(Image::createKernel(width, height)); + } + + void BackgroundSubtraction::setThreshold(int threshold) + { + m_threshold = threshold; + } +} \ No newline at end of file diff --git a/src/kerberos/machinery/heuristic/Counter.cpp b/src/kerberos/machinery/heuristic/Counter.cpp new file mode 100644 index 0000000..78aaddf --- /dev/null +++ b/src/kerberos/machinery/heuristic/Counter.cpp @@ -0,0 +1,289 @@ +#include "machinery/heuristic/Counter.h" + +namespace kerberos +{ + void Counter::setup(const StringMap & settings) + { + std::vector coordinates; + + // Set incoming coordinates + helper::tokenize(settings.at("heuristics.Counter.markers"), coordinates, "|"); + for(int i = 0; i < 2; i++) + { + std::vector fromAndTo; + helper::tokenize(coordinates[i], fromAndTo, ","); + int from = std::atoi(fromAndTo[0].c_str()); + int to = std::atoi(fromAndTo[1].c_str()); + cv::Point p(from ,to); + m_in.push_back(p); + } + + // Set outgoing coordinates + + for(int i = 2; i < 4; i++) + { + std::vector fromAndTo; + helper::tokenize(coordinates[i], fromAndTo, ","); + int from = std::atoi(fromAndTo[0].c_str()); + int to = std::atoi(fromAndTo[1].c_str()); + cv::Point p(from ,to); + m_out.push_back(p); + } + + setMinimumChanges(std::atoi(settings.at("heuristics.Counter.minimumChanges").c_str())); + setNoMotionDelayTime(std::atoi(settings.at("heuristics.Counter.noMotionDelayTime").c_str())); + setAppearance(std::atoi(settings.at("heuristics.Counter.appearance").c_str())); + setMaxDistance(std::atoi(settings.at("heuristics.Counter.maxDistance").c_str())); + setMinArea(std::atoi(settings.at("heuristics.Counter.minArea").c_str())); + setOnlyTrueWhenCounted((settings.at("heuristics.Counter.onlyTrueWhenCounted") == "true")); + } + + bool Counter::intersection(cv::Point2f o1, cv::Point2f p1, cv::Point2f o2, cv::Point2f p2, cv::Point2f &r) + { + cv::Point2f x = o2 - o1; + cv::Point2f d1 = p1 - o1; + cv::Point2f d2 = p2 - o2; + + float cross = d1.x * d2.y - d1.y * d2.x; + + if (std::abs(cross) < 1e-8) + { + return false; + } + + double t1 = (x.x * d2.y - x.y * d2.x)/cross; + r = o1 + d1 * t1; + + if((r.x <= MAX(o1.x, p1.x) && r.x >= MIN(o1.x, p1.x) && r.y <= MAX(o1.y, p1.y) && r.y >= MIN(o1.y, p1.y)) && + (r.x <= MAX(o2.x, p2.x) && r.x >= MIN(o2.x, p2.x) && r.y <= MAX(o2.y, p2.y) && r.y >= MIN(o2.y, p2.y))) + { + return true; + } + + return false; + } + + bool Counter::isValid(const Image & evaluation, const ImageVector & images, JSON & data) + { + int numberOfChanges; + Rectangle rectangle; + numberOfChanges = data["numberOfChanges"].GetInt(); + + int incoming = 0; + int outgoing = 0; + + kerberos::Image image = evaluation; + cv::Mat img = image.getImage(); + cv::dilate(img, img, cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(25,25))); + cv::erode(img, img, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(10,10))); + + cv::Point & outTop = m_out[0]; + cv::Point & outBottom = m_out[1]; + + cv::Point & inTop = m_in[0]; + cv::Point & inBottom = m_in[1]; + + std::vector > contours; + std::vector hierarchy; + cv::findContours(image.getImage(), contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE); + + int numberOfContours= 0; + for(int i = 0; i < m_features.size(); i++) + { + int mostRecent = m_features[i].size() - 1; + m_features[i][mostRecent].decreaseAppearance(); + } + + // Find new tracks + if(contours.size() > 0) + { + for( int i = 0; i< contours.size(); i++ ) + { + cv::Moments moments = cv::moments(contours[i]); + int x = moments.m10/moments.m00; + int y = moments.m01/moments.m00; + int area = cv::contourArea(contours[i]); + + if(area < m_minArea) continue; + + Feature current(x, y, area, m_appearance); + int best = -1; + double bestValue = 99999999; + double bestArea = 99999999; + + for(int j = 0; j < m_features.size(); j++) + { + int mostRecent = m_features[j].size() - 1; + double distance = current.distance(m_features[j][mostRecent]); + double areaDistance = current.areaDistance(m_features[j][mostRecent]); + + if(distance < m_maxDistance && distance < bestValue + m_maxDistance/2) + { + if(areaDistance < bestArea) + { + best = j; + bestValue = distance; + } + } + } + + if(best == -1) + { + std::vector tracking; + tracking.push_back(current); + m_features.push_back(tracking); + } + else + { + m_features[best].push_back(current); + } + + numberOfContours++; + } + } + + // Remove old tracks + std::vector >::iterator it = m_features.begin(); + while(it != m_features.end()) + { + Feature & back = it->back(); + back.decreaseAppearance(); + + if(back.getAppearance() < 0) + { + it = m_features.erase(it); + } + else + { + it++; + } + } + + // Check existing tracks if they crossed the line + it = m_features.begin(); + while(it != m_features.end()) + { + if(it->size() > 1) + { + for(int j = 1; j < it->size(); j++) + { + cv::Point2f prev((*it)[j-1].getX(),(*it)[j-1].getY()); + cv::Point2f curr((*it)[j].getX(),(*it)[j].getY()); + } + + // Check if cross line + cv::Point2f start((*it)[0].getX(),(*it)[0].getY()); + cv::Point2f end((*it)[it->size()-1].getX(),(*it)[it->size()-1].getY()); + + // Check if interset in line + cv::Point2f intersectionPointOutgoing; + bool inLine = false; + if(intersection(start, end, outTop, outBottom, intersectionPointOutgoing)) + { + inLine = true; + } + // Check if interset out line + cv::Point2f intersectionPointIncoming; + bool outLine = false; + if(intersection(start, end, inTop, inBottom, intersectionPointIncoming)) + { + outLine = true; + } + + // Check if interesected both + Direction xDirection = parallell; + Direction yDirection = parallell; + + if(inLine && outLine) + { + // What is the direction (incoming our outgoing?) + if(start.x - end.x < 0) + { + xDirection = right; + } + else if(start.x - end.x > 0) + { + xDirection = left; + } + if(start.y - end.y < 0) + { + yDirection = bottom; + } + else if(start.y - end.y > 0) + { + yDirection = top; + } + + // Check which intersection point comes first + if(xDirection != parallell) + { + if(xDirection == left) + { + // Check which intersection point is most right; + if(intersectionPointIncoming.x > intersectionPointOutgoing.x) + { + incoming++; + } + else + { + outgoing++; + } + } + else if(xDirection == right) + { + // Check which intersection point is most right; + if(intersectionPointIncoming.x < intersectionPointOutgoing.x) + { + incoming++; + } + else + { + outgoing++; + } + } + } + else + { + + } + + it = m_features.erase(it); + } + else + { + it++; + } + } + else + { + it++; + } + } + + if(numberOfChanges >= m_minimumChanges) + { + JSON::AllocatorType& allocator = data.GetAllocator(); + data.AddMember("incoming", incoming, allocator); + data.AddMember("outgoing", outgoing, allocator); + + if(m_onlyTrueWhenCounted) + { + if(incoming > 0 || outgoing > 0) + { + BINFO << "Counter: in (" << helper::to_string(incoming) << "), out (" << helper::to_string(outgoing) << ")"; + return true; + } + } + else + { + return true; + } + } + else + { + usleep(m_noMotionDelayTime*1000); + } + + return false; + } +} diff --git a/src/kerberos/machinery/io/IoDisk.cpp b/src/kerberos/machinery/io/IoDisk.cpp index cbf72b0..13d0b46 100644 --- a/src/kerberos/machinery/io/IoDisk.cpp +++ b/src/kerberos/machinery/io/IoDisk.cpp @@ -11,6 +11,19 @@ namespace kerberos std::string instanceName = settings.at("name"); setInstanceName(instanceName); + + // -------------------------- + // Check if need to draw timestamp + + bool drawTimestamp = (settings.at("ios.Disk.markWithTimestamp") == "true"); + setDrawTimestamp(drawTimestamp); + cv::Scalar color = getColor(settings.at("ios.Disk.timestampColor")); + setTimestampColor(color); + + std::string timezone = settings.at("timezone"); + std::replace(timezone.begin(), timezone.end(), '-', '/'); + std::replace(timezone.begin(), timezone.end(), '$', '_'); + setTimezone(timezone); // ------------------------------------------------------------- // Filemanager is mapped to a directory and is used by an image @@ -19,6 +32,19 @@ namespace kerberos setFileFormat(settings.at("ios.Disk.fileFormat")); m_fileManager.setBaseDirectory(settings.at("ios.Disk.directory")); } + + cv::Scalar IoDisk::getColor(const std::string name) + { + std::map m_colors; + + m_colors["white"] = cv::Scalar(255,255,255); + m_colors["black"] = cv::Scalar(0,0,0); + m_colors["red"] = cv::Scalar(0,0,255); + m_colors["green"] = cv::Scalar(0,255,0); + m_colors["blue"] = cv::Scalar(255,0,0); + + return m_colors.at(name); + } std::string IoDisk::buildPath(std::string pathToImage) { @@ -31,6 +57,29 @@ namespace kerberos return pathToImage; } + void IoDisk::drawDateOnImage(Image & image, std::string timestamp) + { + if(m_drawTimestamp) + { + struct tm tstruct; + char buf[80]; + + time_t now = std::atoi(timestamp.c_str()); + + char * timeformat = "%d-%m-%Y %X"; + if(m_timezone != "") + { + setenv("TZ", m_timezone.c_str(), 1); + tzset(); + } + + tstruct = *localtime(&now); + strftime(buf, sizeof(buf), timeformat, &tstruct); + + cv::putText(image.getImage(), buf, cv::Point(10,30), cv::FONT_HERSHEY_SIMPLEX, 0.5, getTimestampColor()); + } + } + bool IoDisk::save(Image & image) { // ---------------------------------------- @@ -46,6 +95,7 @@ namespace kerberos std::string timestamp = kerberos::helper::getTimestamp(); kerberos::helper::replace(pathToImage, "timestamp", timestamp); + drawDateOnImage(image, timestamp); std::string microseconds = kerberos::helper::getMicroseconds(); std::string size = kerberos::helper::to_string((int)microseconds.length()); @@ -123,8 +173,13 @@ namespace kerberos path.SetString(pathToImage.c_str(), allocator); data.AddMember("pathToImage", path, allocator); - // --------------------------------------------------------------------- - // Save original version & generate unique timestamp for current image + // ------------------ + // Draw date on image + + drawDateOnImage(image, data["timestamp"].GetString()); + + // ------------------------- + // Save original version BINFO << "IoDisk: saving image " + pathToImage; return m_fileManager.save(image, pathToImage);