diff --git a/Source/IpFreely.pro b/Source/IpFreely.pro index 0f4d316..91c9e21 100644 --- a/Source/IpFreely.pro +++ b/Source/IpFreely.pro @@ -68,7 +68,7 @@ else { QMAKE_CXXFLAGS += -std=c++14 # Set version info for library. - VERSION = 1.1.4 + VERSION = 1.1.5 INCLUDEPATH += /usr/local/include \ /home/duncan/projects/ThirdParty \ diff --git a/Source/IpFreely.rc b/Source/IpFreely.rc index 6c98890..f0f4a52 100644 --- a/Source/IpFreely.rc +++ b/Source/IpFreely.rc @@ -7,8 +7,8 @@ IDI_ICON1 ICON DISCARDABLE "IpFreely.ico" VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,1,4,0 - PRODUCTVERSION 1,1,4,0 + FILEVERSION 1,1,5,0 + PRODUCTVERSION 1,1,5,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG @@ -25,11 +25,11 @@ VS_VERSION_INFO VERSIONINFO BEGIN VALUE "CompanyName", "~\0" VALUE "FileDescription", "IP Freely (IP/Web camera stream viewer and recorder)\0" - VALUE "FileVersion", "1.1.4.0\0" + VALUE "FileVersion", "1.1.5.0\0" VALUE "LegalCopyright", "Copyright 2018 Duncan Crutchley\0" VALUE "OriginalFilename", "IpFreely\0" VALUE "ProductName", "IpFreely\0" - VALUE "ProductVersion", "1.1.4.0\0" + VALUE "ProductVersion", "1.1.5.0\0" END END BLOCK "VarFileInfo" diff --git a/Source/IpFreelyCameraDatabase.cpp b/Source/IpFreelyCameraDatabase.cpp index b5df80b..426c3e6 100644 --- a/Source/IpFreelyCameraDatabase.cpp +++ b/Source/IpFreelyCameraDatabase.cpp @@ -272,8 +272,8 @@ void IpFreelyCameraDatabase::Load() *this = std::move(camDb); } -QRect CreateQRectFromVidoFrameDims(int const videoFrameWidth, int const videoFrameHeight, - IpCamera::region_t const& motionRegion) +QRect CreateQRectFromVideoFrameDims(int const videoFrameWidth, int const videoFrameHeight, + IpCamera::region_t const& motionRegion) { QRect r; r.setTop(static_cast(static_cast(videoFrameHeight) * motionRegion.first.second)); diff --git a/Source/IpFreelyCameraDatabase.h b/Source/IpFreelyCameraDatabase.h index 1423ddd..b01f8f8 100644 --- a/Source/IpFreelyCameraDatabase.h +++ b/Source/IpFreelyCameraDatabase.h @@ -336,20 +336,20 @@ class IpFreelyCameraDatabase final } private: - std::string m_dbPath{}; + std::string m_dbPath{}; std::map m_cameras{}; }; /*! - * \brief CreateQRectFromVidoFrameDims create a motion region QRect as subregion of + * \brief CreateQRectFromVideoFrameDims create a motion region QRect as subregion of * a video frame. * \param[in] videoFrameWidth - Video frame's width. * \param[in] videoFrameHeight - Video frame's height. * \param[in] motionRegion - User defined motion detection region. * \return QRect defining motion area. */ -QRect CreateQRectFromVidoFrameDims(int const videoFrameWidth, int const videoFrameHeight, - IpCamera::region_t const& motionRegion); +QRect CreateQRectFromVideoFrameDims(int const videoFrameWidth, int const videoFrameHeight, + IpCamera::region_t const& motionRegion); } // namespace ipfreely diff --git a/Source/IpFreelyMainWindow.cpp b/Source/IpFreelyMainWindow.cpp index 7fae7dd..b31269d 100644 --- a/Source/IpFreelyMainWindow.cpp +++ b/Source/IpFreelyMainWindow.cpp @@ -1299,7 +1299,7 @@ void IpFreelyMainWindow::UpdateCamFeedFrame(ipfreely::eCamId const camId, QImage for (auto const& motionRegion : motionRectAreas) { - auto r = ipfreely::CreateQRectFromVidoFrameDims( + auto r = ipfreely::CreateQRectFromVideoFrameDims( displayFrame.width(), displayFrame.height(), motionRegion); if (rect.intersects(r)) diff --git a/Source/IpFreelyMotionDetector.cpp b/Source/IpFreelyMotionDetector.cpp index 9e87d00..f82103e 100644 --- a/Source/IpFreelyMotionDetector.cpp +++ b/Source/IpFreelyMotionDetector.cpp @@ -25,6 +25,7 @@ #include "IpFreelyMotionDetector.h" #include #include +#include #include #include #include "StringUtils/StringUtils.h" @@ -35,10 +36,11 @@ namespace bfs = boost::filesystem; namespace ipfreely { -static constexpr int MESSAGE_ID = 1; -static constexpr double DIFF_MAX_VALUE = 255.0; -static constexpr int IDEAL_FRAME_HEIGHT = 600; -static constexpr size_t HOLD_ON_OFF_SECS = 10; +static constexpr int MESSAGE_ID = 1; +static constexpr double DIFF_MAX_VALUE = 255.0; +static constexpr int IDEAL_FRAME_HEIGHT = 600; +static constexpr size_t HOLD_ON_OFF_SECS = 10; +static constexpr int BOUNDING_RECT_MARGIN = 1; #if defined(MOTION_DETECTOR_DEBUG) static constexpr int CONTOUR_LINE_THICKNESS = 2; @@ -78,7 +80,10 @@ IpFreelyMotionDetector::IpFreelyMotionDetector(std::string const& name, Initialise(); - DEBUG_MESSAGE_EX_INFO("Started motion detector for stream at: " << m_cameraDetails.streamUrl); + DEBUG_MESSAGE_EX_INFO("Started motion detector for stream at: " + << m_cameraDetails.streamUrl + << ", required file duration (in seconds) set to: " + << m_requiredFileDurationSecs); m_msgQueueThread.RegisterMessageHandler( MESSAGE_ID, @@ -218,8 +223,15 @@ void IpFreelyMotionDetector::UpdateNextFrame() bool IpFreelyMotionDetector::DetectMotion() { - // This algorithm is based on an example given here: + // This algorithm is inspired by an example given here: // https://github.com/cedricve/motion-detection + // However I have taken this basic idea and added + // some extra layers to the algorithm to smooth out + // out the motion region now gets a smoothing rolling + // average applied to it between frames to make it + // less janky. I've also added a check to filter + // out motion regions less than a configurable percentage + // of the frame's total area. // Calculate differences between the images and do AND-operation // then threshold image, low differences are ignored (ex. contrast @@ -248,9 +260,9 @@ bool IpFreelyMotionDetector::DetectMotion() { size_t numChanges = 0; - // Loop over image and detect changes. This is much better - // for CPU performance compared to using OpenCV's contour - // fitting algorithms. + // Loop over image and detect changes. This is better + // for CPU performance compared to using OpenCV's + // fcontour itting algorithms. for (int j = 0; j < motion.rows; j += 2) { for (int i = 0; i < motion.cols; i += 2) @@ -258,7 +270,7 @@ bool IpFreelyMotionDetector::DetectMotion() // check if at pixel (j,i) intensity is equal to 255 // this means that the pixel is different in the sequence // of images (prev_frame, current_frame, next_frame) - if (static_cast(motion.at(j, i)) == 255) + if (static_cast(motion.at(j, i)) == 255) { ++numChanges; @@ -290,24 +302,24 @@ bool IpFreelyMotionDetector::DetectMotion() // that encompasses all the motion detected. if (numChanges > 0) { - if (min_x - 10 > 0) + if (min_x - BOUNDING_RECT_MARGIN > 0) { - min_x -= 10; + min_x -= BOUNDING_RECT_MARGIN; } - if (min_y - 10 > 0) + if (min_y - BOUNDING_RECT_MARGIN > 0) { - min_y -= 10; + min_y -= BOUNDING_RECT_MARGIN; } - if (max_x + 10 < motion.cols - 1) + if (max_x + BOUNDING_RECT_MARGIN < (motion.cols - 1)) { - max_x += 10; + max_x += BOUNDING_RECT_MARGIN; } - if (max_y + 10 < motion.rows - 1) + if (max_y + BOUNDING_RECT_MARGIN < (motion.rows - 1)) { - max_y += 10; + max_y += BOUNDING_RECT_MARGIN; } cv::Point x(min_x, min_y); @@ -334,7 +346,7 @@ bool IpFreelyMotionDetector::DetectMotion() // we ignore small, most likely insignificnt motion. if (maxBoundingRect.area() > m_minImageChangeArea) { - // Create a motin bounding rectangle sclaed to original + // Create a motion bounding rectangle scaled to original // video frame's size. cv::Point tl1(static_cast(static_cast(min_x) / m_motionFrameScalar), static_cast(static_cast(min_y) / m_motionFrameScalar)); @@ -343,9 +355,9 @@ bool IpFreelyMotionDetector::DetectMotion() auto minBoundingRect = cv::Rect(tl1, br1); - // To make the bounding rectangle appear less jerky we'll - // combine it with the previous bounding rectangle used a - // smoothed rolling average controlled by the smoothing factor. + // To make the bounding rectangle appear less janky we'll + // combine it with the previous bounding rectangle using a + // smoothing rolling average controlled by the smoothing factor. std::lock_guard lock(m_motionMutex); double l = (static_cast(m_motionBoundingRect.tl().x) * @@ -373,7 +385,7 @@ bool IpFreelyMotionDetector::DetectMotion() else { // We have no current motion but rather than instantly removing the bounding - // rectangle instead shrink it down to zero area using the bounding rectangle. + // rectangle instead shrink it down to zero area gradually ov er a few iterations. std::lock_guard lock(m_motionMutex); double l = m_motionBoundingRect.tl().x + @@ -412,14 +424,22 @@ bool IpFreelyMotionDetector::CheckForIntersections() for (auto const& region : m_cameraDetails.motionRegions) { - auto r = ipfreely::CreateQRectFromVidoFrameDims(m_originalWidth, m_originalHeight, region); + auto r = ipfreely::CreateQRectFromVideoFrameDims(m_originalWidth, m_originalHeight, region); if (mr.intersects(r)) { motionIntersection = true; - DEBUG_MESSAGE_EX_INFO("Motion detector intersection found, camera stream URL: " - << m_cameraDetails.streamUrl); + DEBUG_MESSAGE_EX_INFO("Motion detector intersection found for camera stream URL: " + << m_cameraDetails.streamUrl + << ", region details: L = " + << r.left() + << ", T = " + << r.top() + << ", W = " + << r.width() + << ", H = " + << r.height()); break; } @@ -468,7 +488,7 @@ bool IpFreelyMotionDetector::MessageHandler(video_frame_t& msg) } } - // If hold-off count reached then redet count and reset writer + // If hold-off count reached then reset count and reset writer // and finally set recording flag to false. if (m_holdOffFrameCount == m_holdOffFrameCountLimit) { @@ -498,12 +518,14 @@ void IpFreelyMotionDetector::CreateCaptureObjects() { if (m_fileDurationSecs < m_requiredFileDurationSecs) { - DEBUG_MESSAGE_EX_DEBUG( - "Motion detector file duration reached for current video file, camera stream URL: " - << m_cameraDetails.streamUrl); return; } + DEBUG_MESSAGE_EX_INFO( + "Motion detector file duration reached for current video file, camera stream URL: " + << m_cameraDetails.streamUrl + << ", file writer being closed."); + m_videoWriter.release(); SetWritingStream(false); } @@ -531,7 +553,9 @@ void IpFreelyMotionDetector::CreateCaptureObjects() p /= oss.str(); - DEBUG_MESSAGE_EX_INFO("Creating new output video file: " << p.string()); + DEBUG_MESSAGE_EX_INFO("Creating new output video file: " << p.string() << ", FPS: " << m_fps); + + m_fileDurationSecs = 0.0; #if BOOST_OS_WINDOWS m_videoWriter = cv::makePtr(p.string().c_str(), @@ -548,10 +572,10 @@ void IpFreelyMotionDetector::CreateCaptureObjects() if (!m_videoWriter->isOpened()) { m_videoWriter.release(); - BOOST_THROW_EXCEPTION(std::runtime_error("Failed to open VideoWriter object")); + DEBUG_MESSAGE_EX_ERROR("Failed to open VideoWriter object: " << p.string()); + return; } - m_fileDurationSecs = 0.0; SetWritingStream(true); } diff --git a/Source/IpFreelyStreamProcessor.cpp b/Source/IpFreelyStreamProcessor.cpp index b1735ba..aad70a9 100644 --- a/Source/IpFreelyStreamProcessor.cpp +++ b/Source/IpFreelyStreamProcessor.cpp @@ -135,9 +135,10 @@ IpFreelyStreamProcessor::IpFreelyStreamProcessor( m_fps = fps; m_updatePeriodMillisecs = static_cast(1000.0 / m_fps); - DEBUG_MESSAGE_EX_INFO("Stream at: " - << m_cameraDetails.streamUrl << " running with FPS of: " << m_fps - << ", thread update period (ms): " << m_updatePeriodMillisecs); + DEBUG_MESSAGE_EX_INFO("Stream at: " << m_cameraDetails.streamUrl << " running with FPS of: " + << m_fps + << ", thread update period (ms): " + << m_updatePeriodMillisecs); DEBUG_MESSAGE_EX_INFO("Creating event thread for stream URL: " << m_cameraDetails.streamUrl); @@ -155,7 +156,8 @@ void IpFreelyStreamProcessor::StartVideoWriting() noexcept if (m_useRecordingSchedule) { DEBUG_MESSAGE_EX_WARNING( - "Manual recording disabled because a schedule is defined. Camera: " << m_name); + "Manual recording disabled because a recording schedule is defined. Camera: " + << m_name); return; } @@ -167,7 +169,8 @@ void IpFreelyStreamProcessor::StopVideoWriting() noexcept if (m_useRecordingSchedule) { DEBUG_MESSAGE_EX_WARNING( - "Manual recording disabled because a schedule is defined. Camera: " << m_name); + "Manual recording disabled because a recording schedule is defined. Camera: " + << m_name); return; } @@ -281,7 +284,8 @@ bool IpFreelyStreamProcessor::VerifySchedule(std::string const& else { DEBUG_MESSAGE_EX_WARNING( - scheduleId << " is disabled. No days/hours are set as active in the schedule."); + scheduleId + << " is disabled. Either no days/hours are set or scheduled recording is disabled."); } return scheduleOk; @@ -364,6 +368,8 @@ void IpFreelyStreamProcessor::CreateCaptureObjects() m_videoWriter.release(); } + m_fileDurationSecs = 0.0; + auto localTime = std::localtime(&m_currentTime); char folderName[9]; std::strftime(folderName, sizeof(folderName), "%Y%m%d", localTime); @@ -376,9 +382,8 @@ void IpFreelyStreamProcessor::CreateCaptureObjects() { if (!bfs::create_directories(p)) { - std::ostringstream oss; - oss << "Failed to create directories: " << p.string(); - BOOST_THROW_EXCEPTION(std::runtime_error(oss.str())); + DEBUG_MESSAGE_EX_ERROR("Failed to create directories: " << p.string()); + return; } } @@ -387,7 +392,8 @@ void IpFreelyStreamProcessor::CreateCaptureObjects() p /= oss.str(); - DEBUG_MESSAGE_EX_INFO("Creating new output video file: " << p.string()); + DEBUG_MESSAGE_EX_INFO( + "Creating new output video file: " << p.string() << ", FPS: " << m_fps); #if BOOST_OS_WINDOWS m_videoWriter = cv::makePtr(p.string().c_str(), @@ -404,13 +410,13 @@ void IpFreelyStreamProcessor::CreateCaptureObjects() if (!m_videoWriter->isOpened()) { m_videoWriter.release(); - BOOST_THROW_EXCEPTION(std::runtime_error("Failed to open VideoWriter object")); + DEBUG_MESSAGE_EX_ERROR("Failed to open VideoWriter object for: " << p.string()); + return; } - - m_fileDurationSecs = 0.0; } else { + DEBUG_MESSAGE_EX_INFO("Video writing disabled, releasing video writer, camera: " << m_name); m_videoWriter.release(); } } @@ -501,7 +507,9 @@ void IpFreelyStreamProcessor::CreateVideoCapture() if (!m_videoCapture->isOpened()) { - BOOST_THROW_EXCEPTION(std::runtime_error("Failed to open VideoCapture object.")); + std::ostringstream oss; + oss << "Failed to open VideoCapture object, url: " << m_cameraDetails.streamUrl; + BOOST_THROW_EXCEPTION(std::runtime_error(oss.str())); } m_videoWidth = static_cast(m_videoCapture->get(CV_CAP_PROP_FRAME_WIDTH)); @@ -523,15 +531,19 @@ void IpFreelyStreamProcessor::CheckFps() if (std::abs(fps - m_fps) > 0.1) { + DEBUG_MESSAGE_EX_WARNING( + "Detected change in FPS for stream: " << m_cameraDetails.streamUrl); + // If the FPS has changed then recreate the video capture object. CreateVideoCapture(); m_fps = fps; m_updatePeriodMillisecs = static_cast(1000.0 / m_fps); - DEBUG_MESSAGE_EX_INFO("Stream at: " - << m_cameraDetails.streamUrl << " running with FPS of: " << m_fps - << ", thread update period (ms): " << m_updatePeriodMillisecs); + DEBUG_MESSAGE_EX_INFO("Stream at: " << m_cameraDetails.streamUrl << " running with FPS of: " + << m_fps + << ", thread update period (ms): " + << m_updatePeriodMillisecs); if (m_videoWriter) { diff --git a/Source/IpFreelyVideoForm.cpp b/Source/IpFreelyVideoForm.cpp index 91d83af..adff2cc 100644 --- a/Source/IpFreelyVideoForm.cpp +++ b/Source/IpFreelyVideoForm.cpp @@ -121,7 +121,7 @@ void IpFreelyVideoForm::SetVideoFrame(QImage const& videoFrame, double const fps for (auto const& motionRegion : motionRegions) { - auto r = ipfreely::CreateQRectFromVidoFrameDims( + auto r = ipfreely::CreateQRectFromVideoFrameDims( resizedImage.width(), resizedImage.height(), motionRegion); if (rect.intersects(r)) diff --git a/Source/main.cpp b/Source/main.cpp index ab10035..71b32c1 100644 --- a/Source/main.cpp +++ b/Source/main.cpp @@ -48,10 +48,10 @@ std::string GetAppVersion(const std::string& appFilePath) std::wstring appFilePathW(appFilePath.begin(), appFilePath.end()); const DWORD blockSize = GetFileVersionInfoSize(appFilePathW.c_str(), NULL); - std::string appVersion; - auto deleter = [](BYTE const* p) { delete[] p; }; + std::string appVersion; + auto deleter = [](BYTE const* p) { delete[] p; }; std::unique_ptr block(new BYTE[blockSize], deleter); - LPVOID pBlock = reinterpret_cast(block.get()); + LPVOID pBlock = reinterpret_cast(block.get()); if (GetFileVersionInfo(appFilePathW.c_str(), NULL, blockSize, pBlock)) { @@ -75,7 +75,7 @@ std::string GetAppVersion(const std::string& appFilePath) return appVersion; } #else -#define IPFREELY_VERSION "1.1.4.0" +#define IPFREELY_VERSION "1.1.5.0" #endif int main(int argc, char* argv[])