From e21b7316a7cae6c4495f6db01463723b9f5f2fd3 Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Mon, 4 Jul 2016 16:07:44 +0300 Subject: [PATCH 1/8] Implementation of threshold function --- src/workaround.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/workaround.cpp b/src/workaround.cpp index 2eba458..e389fb7 100644 --- a/src/workaround.cpp +++ b/src/workaround.cpp @@ -6,5 +6,14 @@ using namespace std; void MatrixProcessor::Threshold(unsigned char* const data, const int width, const int height, const int threshold) { - // TODO: Add thresholding logic here. + for (int i = 0; i < height; i++) + { + for (int j = 0; j < width; j++) + { + if (data[i * width + j] > threshold) + { + data[i * width + j] = 0; + } + } + } } From dcc3fdacc2d42e03df7c0ae60c8c3a6afd24da1c Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Mon, 4 Jul 2016 17:48:11 +0300 Subject: [PATCH 2/8] Added average function --- include/workaround.hpp | 1 + samples/devtools_demo.cpp | 11 ++++++----- src/workaround.cpp | 28 ++++++++++++++++++++++++++++ test/test_dummy.cpp | 12 ++++++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/include/workaround.hpp b/include/workaround.hpp index 18ffbc4..c1e571a 100644 --- a/include/workaround.hpp +++ b/include/workaround.hpp @@ -4,4 +4,5 @@ class MatrixProcessor { public: void Threshold(unsigned char* const data, const int width, const int height, const int threshold); + void Average(unsigned char* const data, const int width, const int height); }; diff --git a/samples/devtools_demo.cpp b/samples/devtools_demo.cpp index 8c40769..557e1d9 100644 --- a/samples/devtools_demo.cpp +++ b/samples/devtools_demo.cpp @@ -43,14 +43,15 @@ int main(int argc, const char** argv) { imshow(kSrcWindowName, src); waitKey(kWaitKeyDelay); - // Threshold data. + // Average data. MatrixProcessor processor; const int threshold = parser.get("t"); try { - processor.Threshold(src.data, src.cols, src.rows, threshold); - } catch (const std::exception& ex) { - cout << ex.what() << endl; - return 0; + processor.Average(src.data, src.cols, src.rows); + } + catch (const std::exception& ex) { + cout << ex.what() << endl; + return 0; } // Show destination image. diff --git a/src/workaround.cpp b/src/workaround.cpp index e389fb7..36c9053 100644 --- a/src/workaround.cpp +++ b/src/workaround.cpp @@ -17,3 +17,31 @@ void MatrixProcessor::Threshold(unsigned char* const data, const int width, } } } + +void MatrixProcessor::Average(unsigned char* const data, const int width, + const int height) { + + int N = width * height; + unsigned char* src = new unsigned char[N]; + + for (int i = 0; i < N; i++) + { + src[i] = data[i]; + } + + for (int i = 1; i < height - 1; i++) + { + for (int j = 1; j < width - 1; j++) + { + //data[i * width + j] = (src[(height + i - 1) / height * width + (width + j - 1 / width)] + src[(height + i - 1) / height * width + j] + src[(height + i - 1) / height * width + (j + 1 / width)] + + // src[i * width + (width + j - 1 / width)] + src[i * width + j] + src[i * width + (j + 1 / width)] + + // src[((i + 1) / height) * width + (width + j - 1 / width)] + src[((i + 1) / height) * width + j] + src[((i + 1) / height) * width + (j + 1 / width)]) / 9; + + data[i * width + j] = (src[(i - 1) * width + j - 1] + src[(i - 1) * width + j] + src[(i - 1) * width + j + 1 ] + + src[i * width + j - 1] + src[i * width + j] + src[i * width + j + 1] + + src[(i + 1) * width + j - 1] + src[(i + 1) * width + j] + src[(i + 1) * width + j + 1]) / 9; + } + } + + delete[] src; +} diff --git a/test/test_dummy.cpp b/test/test_dummy.cpp index bc8a1a9..31f0f2d 100644 --- a/test/test_dummy.cpp +++ b/test/test_dummy.cpp @@ -4,6 +4,8 @@ #include +#include "workaround.hpp" + using namespace cv; TEST(dummy, dummy_test) @@ -12,3 +14,13 @@ TEST(dummy, dummy_test) Mat submat = mat(Rect(0, 0, 2, 3)); EXPECT_FALSE(submat.isContinuous()); } + +TEST(Average, MatrixProcessor_test) +{ + MatrixProcessor processor; + unsigned char* data = new unsigned char[9] { 1, 9, 1, 1, 1, 1, 1, 2, 1 }; + + processor.Average(data, 3, 3); + ASSERT_TRUE(data[4] == 2); +} + From 3eb0d1762a5cbb1ca5d13e7208a9d2c6237e7bf1 Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Tue, 5 Jul 2016 14:46:45 +0300 Subject: [PATCH 3/8] Added devtools_demo_kud.cpp and image_proc.cpp --- samples/devtools_demo.cpp | 2 +- samples/devtools_demo_kud.cpp | 65 +++++++++++++++++++++++++++++++++++ samples/image_proc.cpp | 33 ++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 samples/devtools_demo_kud.cpp create mode 100644 samples/image_proc.cpp diff --git a/samples/devtools_demo.cpp b/samples/devtools_demo.cpp index 557e1d9..504c2a0 100644 --- a/samples/devtools_demo.cpp +++ b/samples/devtools_demo.cpp @@ -47,7 +47,7 @@ int main(int argc, const char** argv) { MatrixProcessor processor; const int threshold = parser.get("t"); try { - processor.Average(src.data, src.cols, src.rows); + processor.Threshold(src.data, src.cols, src.rows, threshold); } catch (const std::exception& ex) { cout << ex.what() << endl; diff --git a/samples/devtools_demo_kud.cpp b/samples/devtools_demo_kud.cpp new file mode 100644 index 0000000..557e1d9 --- /dev/null +++ b/samples/devtools_demo_kud.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" + +#include "workaround.hpp" + +using namespace std; +using namespace cv; + +const char* kAbout = "Application for practice #1."; + +const char* kOptions = + "{ @image | | image to process }" + "{ t | 128 | threshold }" + "{ h ? help usage | | print help message }"; + +int main(int argc, const char** argv) { + // Parse command line arguments. + CommandLineParser parser(argc, argv, kOptions); + parser.about(kAbout); + + // If help option is given, print help message and exit. + if (parser.has("help")) { + parser.printMessage(); + return 0; + } + + // Read image. + Mat src = imread(parser.get(0), CV_LOAD_IMAGE_GRAYSCALE); + if (src.empty()) { + cout << "Failed to open image file '" + parser.get(0) + "'." + << endl; + return 0; + } + + // Show source image. + const string kSrcWindowName = "Source image"; + const int kWaitKeyDelay = 1; + namedWindow(kSrcWindowName, WINDOW_NORMAL); + resizeWindow(kSrcWindowName, 640, 480); + imshow(kSrcWindowName, src); + waitKey(kWaitKeyDelay); + + // Average data. + MatrixProcessor processor; + const int threshold = parser.get("t"); + try { + processor.Average(src.data, src.cols, src.rows); + } + catch (const std::exception& ex) { + cout << ex.what() << endl; + return 0; + } + + // Show destination image. + const string kDstWindowName = "Destination image"; + namedWindow(kDstWindowName, WINDOW_NORMAL); + resizeWindow(kDstWindowName, 640, 480); + imshow(kDstWindowName, src); + waitKey(); + + return 0; +} diff --git a/samples/image_proc.cpp b/samples/image_proc.cpp new file mode 100644 index 0000000..3b9eb74 --- /dev/null +++ b/samples/image_proc.cpp @@ -0,0 +1,33 @@ +#include +#include + +#include "opencv2/core.hpp" + +using namespace std; +using namespace cv; + +const char* kAbout = + "This is an empty application that can be treated as a template for your " + "own doing-something-cool applications."; + +const char* kOptions = + "{ v video | | video to process }" + "{ h ? help usage | | print help message }"; + + +int main(int argc, const char** argv) { + // Parse command line arguments. + CommandLineParser parser(argc, argv, kOptions); + parser.about(kAbout); + + // If help option is given, print help message and exit. + if (parser.get("help")) { + parser.printMessage(); + return 0; + } + + // Do something cool. + cout << "This is empty template sample." << endl; + + return 0; +} From e68469cbab5b9b3d5b703e3a15795446c44000e1 Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Tue, 5 Jul 2016 17:48:03 +0300 Subject: [PATCH 4/8] Added filters --- include/image_processing.hpp | 13 ++++++ samples/devtools_demo.cpp | 2 +- samples/image_proc.cpp | 91 ++++++++++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/include/image_processing.hpp b/include/image_processing.hpp index ca1f116..2d621bb 100644 --- a/include/image_processing.hpp +++ b/include/image_processing.hpp @@ -4,6 +4,7 @@ #include #include "opencv2/core/core.hpp" +#include "opencv2/imgproc.hpp" class ImageProcessor { public: @@ -15,4 +16,16 @@ class ImageProcessor { const int kernelSize) = 0; virtual cv::Mat Pixelize(const cv::Mat &src, const cv::Rect &roi, const int kDivs) = 0; +}; + +class MyImageProcessor { +public: + virtual cv::Mat CvtColor(const cv::Mat &src, const cv::Rect &roi); + virtual cv::Mat Filter(const cv::Mat &src, const cv::Rect &roi, + const int kSize); + virtual cv::Mat DetectEdges(const cv::Mat &src, const cv::Rect &roi, + const int filterSize, const int lowThreshold, const int ratio, + const int kernelSize); + virtual cv::Mat Pixelize(const cv::Mat &src, const cv::Rect &roi, + const int kDivs); }; \ No newline at end of file diff --git a/samples/devtools_demo.cpp b/samples/devtools_demo.cpp index 504c2a0..dea5cd2 100644 --- a/samples/devtools_demo.cpp +++ b/samples/devtools_demo.cpp @@ -43,7 +43,7 @@ int main(int argc, const char** argv) { imshow(kSrcWindowName, src); waitKey(kWaitKeyDelay); - // Average data. + // Threshold data. MatrixProcessor processor; const int threshold = parser.get("t"); try { diff --git a/samples/image_proc.cpp b/samples/image_proc.cpp index 3b9eb74..a065255 100644 --- a/samples/image_proc.cpp +++ b/samples/image_proc.cpp @@ -2,6 +2,9 @@ #include #include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" + +#include "image_processing.hpp" using namespace std; using namespace cv; @@ -11,9 +14,36 @@ const char* kAbout = "own doing-something-cool applications."; const char* kOptions = - "{ v video | | video to process }" - "{ h ? help usage | | print help message }"; + "{ @image | | image to process }" + "{ gray | | convert ROI to gray scale }" + "{ median | | apply median filter for ROI }" + "{ edges | | detect edges in ROI }" + "{ pix | | pixelize ROI }" + "{ h ? help usage | | print help message }"; + +struct MouseCallbackState { + bool is_selection_started; + bool is_selection_finished; + Point point_first; + Point point_second; +}; +void OnMouse(int event, int x, int y, int flags, void* userdata) +{ + MouseCallbackState* state = reinterpret_cast(userdata); + if (event == EVENT_LBUTTONDOWN) + { + state->is_selection_started = true; + state->is_selection_finished = false; + state->point_first = Point(x, y); + } + if (event == EVENT_LBUTTONUP) + { + state->is_selection_started = false; + state->is_selection_finished = true; + state->point_second = Point(x, y); + } +} int main(int argc, const char** argv) { // Parse command line arguments. @@ -21,13 +51,64 @@ int main(int argc, const char** argv) { parser.about(kAbout); // If help option is given, print help message and exit. - if (parser.get("help")) { + if (parser.has("help")) { parser.printMessage(); return 0; } - // Do something cool. - cout << "This is empty template sample." << endl; + Mat src = imread(parser.get(0), CV_LOAD_IMAGE_COLOR); + if (src.empty()) { + cout << "Failed to open image file '" + parser.get(0) + "'." + << endl; + return 0; + } + + // Show source image. + const string kSrcWindowName = "Source image"; + const int kWaitKeyDelay = 1; + namedWindow(kSrcWindowName); + imshow(kSrcWindowName, src); + waitKey(kWaitKeyDelay); + + MouseCallbackState mouse_state; + mouse_state.is_selection_finished = false; + setMouseCallback(kSrcWindowName, OnMouse, &mouse_state); + + for (;;) + { + if (mouse_state.is_selection_finished) + { + int x = mouse_state.point_first.x; + int y = mouse_state.point_first.y; + int x2 = mouse_state.point_second.x; + int y2 = mouse_state.point_second.y; + + Rect roi; + if (x2 > x && y2 > y) + roi = Rect(x, y, x2 - x, y2 - y); + else + roi = Rect(100, 100, 300, 300); + + Mat dsr; + + MyImageProcessor processor; + + try { + dsr = processor.CvtColor(src, roi); + } + catch (const std::exception& ex) { + cout << ex.what() << endl; + return 0; + } + + const string kDstWindowName = "Destination image"; + namedWindow(kDstWindowName); + imshow(kDstWindowName, dsr); + } + waitKey(kWaitKeyDelay); + } + + waitKey(); return 0; } From 85778b285f0eee3b47a508c8e11948dc0225adba Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Tue, 5 Jul 2016 17:56:25 +0300 Subject: [PATCH 5/8] Added missing file --- src/image_processing.cpp | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/image_processing.cpp diff --git a/src/image_processing.cpp b/src/image_processing.cpp new file mode 100644 index 0000000..9eb4190 --- /dev/null +++ b/src/image_processing.cpp @@ -0,0 +1,72 @@ +#include "image_processing.hpp" + +cv::Mat MyImageProcessor::CvtColor(const cv::Mat &src, const cv::Rect &roi) +{ + cv::Mat src_copy = src.clone(); + cv::Mat dst_gray_roi; + + cv::Mat src_copy_roi = src_copy(roi); + cv::cvtColor(src_copy_roi, dst_gray_roi, cv::COLOR_BGR2GRAY); + cv::cvtColor(dst_gray_roi, dst_gray_roi, cv::COLOR_GRAY2BGR); + + dst_gray_roi.copyTo(src_copy_roi); + + return src_copy; +} + +cv::Mat MyImageProcessor::Filter(const cv::Mat &src, const cv::Rect &roi, const int kSize) +{ + cv::Mat src_copy = src.clone(); + + cv::Mat src_copy_roi = src_copy(roi); + cv::medianBlur(src_copy_roi, src_copy_roi, kSize); + + return src_copy; +} + +cv::Mat MyImageProcessor::DetectEdges(const cv::Mat &src, const cv::Rect &roi, + const int filterSize, const int lowThreshold, const int ratio, + const int kernelSize) +{ + cv::Mat src_copy = src.clone(); + cv::Mat dst = src.clone(); + cv::Mat src_gray_roi; + cv::Mat gray_blurred; + cv::Mat detected_edges; + cv::Size_ size(filterSize, filterSize); + + cv::Mat src_roi = src_copy(roi); + + cv::cvtColor(src_roi, src_gray_roi, cv::COLOR_BGR2GRAY); + cv::blur(src_gray_roi, gray_blurred, size); + cv::Canny(gray_blurred, detected_edges, lowThreshold, lowThreshold * ratio); + + cv::Mat dst_roi = dst(roi); + cv::Scalar scal; + dst_roi = scal.all(0); + + src_roi.copyTo(dst_roi, detected_edges); + + return dst; +} + +cv::Mat MyImageProcessor::Pixelize(const cv::Mat &src, const cv::Rect &roi, const int kDivs) +{ + cv::Mat src_copy = src.clone(); + cv::Mat src_roi = src_copy(roi); + + int block_size_x = roi.width / kDivs; + int block_size_y = roi.height / kDivs; + + cv::Size_ size(block_size_x, block_size_y); + + for (int i = 0; i < roi.width; i += block_size_x) + for (int j = 0; j < roi.height; j += block_size_y) + { + cv::Rect block (i, j, block_size_x, block_size_x); + cv::Mat src_block_roi = src_roi(block); + cv::blur(src_block_roi, src_block_roi, size); + } + + return src_copy; +} \ No newline at end of file From e425c32c586ab33c440f3ea1857ece8110467af7 Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Wed, 6 Jul 2016 18:39:58 +0400 Subject: [PATCH 6/8] Added application detection_demo --- include/detection.hpp | 26 +++++-- samples/detection_demo.cpp | 143 +++++++++++++++++++++++++++++++++++++ src/detection.cpp | 29 ++++++++ 3 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 samples/detection_demo.cpp diff --git a/include/detection.hpp b/include/detection.hpp index 46930d3..dfd32a7 100644 --- a/include/detection.hpp +++ b/include/detection.hpp @@ -4,11 +4,27 @@ #include #include "opencv2/core/core.hpp" +#include "opencv2/opencv.hpp" class Detector { - public: - static std::shared_ptr CreateDetector(const std::string& name); - virtual bool Init(const std::string& model_file_path) = 0; - virtual void Detect(const cv::Mat& frame, std::vector& objects, - std::vector& scores) = 0; +public: + static std::shared_ptr CreateDetector(const std::string& name); + virtual bool Init(const std::string& model_file_path) = 0; + virtual void Detect(const cv::Mat& frame, std::vector& objects, + std::vector& scores) = 0; +}; + +class CascadeDetector : Detector { +public: + static std::shared_ptr CreateDetector(const std::string& name) + { + if (name == "cascade") { + return std::make_shared(); + } + }; + virtual bool Init(const std::string& model_file_path); + virtual void Detect(const cv::Mat& frame, std::vector& objects, + std::vector& scores); +protected: + cv::CascadeClassifier detector; }; diff --git a/samples/detection_demo.cpp b/samples/detection_demo.cpp new file mode 100644 index 0000000..d46edf8 --- /dev/null +++ b/samples/detection_demo.cpp @@ -0,0 +1,143 @@ +#include +#include + +#include "opencv2/core.hpp" +#include "detection.hpp" + +using namespace std; +using namespace cv; + +const char* kAbout = + "This is an empty application that can be treated as a template for your " + "own doing-something-cool applications."; + +const char* kOptions = +"{ i image | | image to process }" +"{ v video | | video to process }" +"{ c camera | | camera to get video from }" +"{ m model | | }" +"{ h ? help usage | | print help message }"; + + +int getMode(CommandLineParser& parser) +{ + if (parser.has("i")) + return 0; + if (parser.has("v")) + return 1; + if (parser.has("c")) + return 3; +} + +void ImageProc(shared_ptr& detector, Mat& src) +{ + vector objects; + vector scores; + + detector->Detect(src, objects, scores); + + for each (Rect itm in objects) + { + rectangle(src, itm, Scalar(0, 255, 255)); + } +} + +int imageDetect(CommandLineParser& parser, shared_ptr& detector) +{ + Mat src = imread(parser.get("i"), CV_LOAD_IMAGE_COLOR); + if (src.empty()) { + cout << "Failed to open image file '" + parser.get(0) + "'." + << endl; + return 0; + } + + ImageProc(detector, src); + + const string kSrcWindowName = "Detection image"; + const int kWaitKeyDelay = 1; + namedWindow(kSrcWindowName); + imshow(kSrcWindowName, src); + waitKey(0); +} + +int cameraDetect(CommandLineParser& parser, shared_ptr& detector) +{ + VideoCapture cap(0); + + if (!cap.isOpened()) { + cout << "Fail." << endl; + return -1; + } + + for (;;) + { + Mat img; + cap >> img; if (img.empty()) break; + + ImageProc(detector, img); + + imshow("Detection video", img); + waitKey(5); + } + waitKey(0); +} + +int videoDetect(CommandLineParser& parser, shared_ptr& detector) +{ + string src = parser.get("v"); + VideoCapture cap; + cap.open(src); + + if (!cap.isOpened()) { + cout << "Fail." << endl; + return -1; + } + + + for (;;) + { + Mat img; + cap >> img; if (img.empty()) break; + + ImageProc(detector, img); + + imshow("Detection video", img); + waitKey(25); + } + waitKey(0); +} + + +int main(int argc, const char** argv) { + // Parse command line arguments. + CommandLineParser parser(argc, argv, kOptions); + parser.about(kAbout); + + // If help option is given, print help message and exit. + if (parser.has("help")) { + parser.printMessage(); + return 0; + } + + string pathToDetector = parser.get("model"); + shared_ptr detector = CascadeDetector::CreateDetector("cascade"); + bool flag = detector->Init(pathToDetector); + + if (flag == false) { + cout << "Failfail." << endl; + return -1; + } + + int mode = getMode(parser); + + if (mode == 0) + imageDetect(parser, detector); + + if (mode == 1) + videoDetect(parser, detector); + + if (mode == 3) + cameraDetect(parser, detector); + + return 0; +} diff --git a/src/detection.cpp b/src/detection.cpp index 15e7fd1..ccded2a 100644 --- a/src/detection.cpp +++ b/src/detection.cpp @@ -11,3 +11,32 @@ shared_ptr Detector::CreateDetector(const string& name) { << std::endl; return nullptr; } + +bool CascadeDetector::Init(const std::string& model_file_path) { + + try + { + detector = CascadeClassifier(model_file_path); + } + catch (const std::exception& ex) + { + return false; + } + + return true; +} + +void CascadeDetector::Detect(const cv::Mat& frame, std::vector& objects, + std::vector& scores) +{ + if (detector.empty()) + return; + + std::vector scoresInt; + detector.detectMultiScale(frame, objects, scoresInt); + + for_each(scoresInt.begin(), scoresInt.end(), [&](int _n) + { + scores.push_back(_n); + }); +} From 08d18fbe831a5db46595a34636e6d3ce6e429076 Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Wed, 6 Jul 2016 18:47:07 +0400 Subject: [PATCH 7/8] Refactoring detection_demo.cpp --- samples/detection_demo.cpp | 42 +++++++++++--------------------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/samples/detection_demo.cpp b/samples/detection_demo.cpp index d46edf8..d7643d8 100644 --- a/samples/detection_demo.cpp +++ b/samples/detection_demo.cpp @@ -60,40 +60,13 @@ int imageDetect(CommandLineParser& parser, shared_ptr& detector waitKey(0); } -int cameraDetect(CommandLineParser& parser, shared_ptr& detector) +int videoDetect(VideoCapture& cap, CommandLineParser& parser, shared_ptr& detector) { - VideoCapture cap(0); - - if (!cap.isOpened()) { - cout << "Fail." << endl; - return -1; - } - - for (;;) - { - Mat img; - cap >> img; if (img.empty()) break; - - ImageProc(detector, img); - - imshow("Detection video", img); - waitKey(5); - } - waitKey(0); -} - -int videoDetect(CommandLineParser& parser, shared_ptr& detector) -{ - string src = parser.get("v"); - VideoCapture cap; - cap.open(src); - if (!cap.isOpened()) { cout << "Fail." << endl; return -1; } - for (;;) { Mat img; @@ -134,10 +107,19 @@ int main(int argc, const char** argv) { imageDetect(parser, detector); if (mode == 1) - videoDetect(parser, detector); + { + string src = parser.get("v"); + VideoCapture cap; + cap.open(src); + videoDetect(cap, parser, detector); + } if (mode == 3) - cameraDetect(parser, detector); + { + VideoCapture cap(0); + videoDetect(cap, parser, detector); + } + return 0; } From 2ec094c258c32106562053faf33ac137f6b80acf Mon Sep 17 00:00:00 2001 From: Nikita-K Date: Thu, 7 Jul 2016 18:56:58 +0400 Subject: [PATCH 8/8] Added tracking_demo.cpp --- include/tracking.hpp | 17 ++++++ samples/tracking_demo.cpp | 117 ++++++++++++++++++++++++++++++++++++++ src/tracking.cpp | 85 +++++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 samples/tracking_demo.cpp diff --git a/include/tracking.hpp b/include/tracking.hpp index f80004c..d56d150 100644 --- a/include/tracking.hpp +++ b/include/tracking.hpp @@ -3,6 +3,7 @@ #include #include +#include "opencv2/opencv.hpp" #include "opencv2/core/core.hpp" class Tracker { @@ -11,3 +12,19 @@ class Tracker { virtual bool Init(const cv::Mat &frame, const cv::Rect &roi) = 0; virtual cv::Rect Track(const cv::Mat &frame) = 0; }; + +class MedianFlowTracker : Tracker { +public: + static std::shared_ptr CreateTracker(const std::string &name) + { + if (name == "median_flow") { + return std::make_shared(); + } + }; + virtual bool Init(const cv::Mat &frame, const cv::Rect &roi); + virtual cv::Rect Track(const cv::Mat &frame); + +protected: + cv::Rect position_; + cv::Mat frame_; +}; \ No newline at end of file diff --git a/samples/tracking_demo.cpp b/samples/tracking_demo.cpp new file mode 100644 index 0000000..9e6fca0 --- /dev/null +++ b/samples/tracking_demo.cpp @@ -0,0 +1,117 @@ +#include +#include + +#include "opencv2/core.hpp" +#include "tracking.hpp" + +using namespace std; +using namespace cv; + +const char* kAbout = + "This is an empty application that can be treated as a template for your " + "own doing-something-cool applications."; + +const char* kOptions = + "{ v video | | video to process }" + "{ h ? help usage | | print help message }"; + +struct MouseCallbackState { + bool is_selection_started; + bool is_selection_finished; + Point point_first; + Point point_second; +}; + +void OnMouse(int event, int x, int y, int flags, void* userdata) +{ + MouseCallbackState* state = reinterpret_cast(userdata); + if (event == EVENT_LBUTTONDOWN) + { + state->is_selection_started = true; + state->is_selection_finished = false; + state->point_first = Point(x, y); + } + if (event == EVENT_LBUTTONUP) + { + state->is_selection_started = false; + state->is_selection_finished = true; + state->point_second = Point(x, y); + } +} + +int main(int argc, const char** argv) { + // Parse command line arguments. + CommandLineParser parser(argc, argv, kOptions); + parser.about(kAbout); + + // If help option is given, print help message and exit. + if (parser.get("help")) { + parser.printMessage(); + return 0; + } + + string pathToFile; + if (parser.has("v")) + pathToFile = parser.get("v"); + else + return -1; + + VideoCapture cap; + cap.open(pathToFile); + + if (!cap.isOpened()) { + cout << "Fail." << endl; + return -1; + } + + Mat img; + cap >> img; if (img.empty()) return 0; + const string kSrcWindowName = "Tracking video"; + imshow(kSrcWindowName, img); + + MouseCallbackState mouse_state; + mouse_state.is_selection_finished = false; + setMouseCallback(kSrcWindowName, OnMouse, &mouse_state); + Rect roi; + + for (;;) + { + if (mouse_state.is_selection_finished) + { + int x = mouse_state.point_first.x; + int y = mouse_state.point_first.y; + int x2 = mouse_state.point_second.x; + int y2 = mouse_state.point_second.y; + + if (x2 > x && y2 > y) + roi = Rect(x, y, x2 - x, y2 - y); + + rectangle(img, roi, Scalar(0, 255, 255)); + imshow(kSrcWindowName, img); + + break; + } + waitKey(5); + } + + shared_ptr tracker = MedianFlowTracker::CreateTracker("median_flow"); + Mat img_gray; + cvtColor(img, img_gray, CV_BGR2GRAY); + if (!tracker->Init(img_gray, roi)) + return 0; + + for (;;) + { + cap >> img; if (img.empty()) break; + cvtColor(img, img_gray, CV_BGR2GRAY); + Rect roi = tracker->Track(img_gray); + + rectangle(img, roi, Scalar(0, 255, 255)); + imshow(kSrcWindowName, img); + waitKey(10); + } + + waitKey(0); + + return 0; +} diff --git a/src/tracking.cpp b/src/tracking.cpp index 26cbe06..b5c6674 100644 --- a/src/tracking.cpp +++ b/src/tracking.cpp @@ -11,3 +11,88 @@ shared_ptr Tracker::CreateTracker(const string &name) { << std::endl; return nullptr; } + + +bool MedianFlowTracker::Init(const cv::Mat &frame, const cv::Rect &roi) +{ + if (frame.empty()) + return false; + if (roi.area() == 0) + return false; + + position_ = roi; + frame_ = frame; + + return true; +} + +struct sortVecY { + bool operator() (Vec2f vec1, Vec2f vec2) { return (vec1[1] < vec2[1]); } +} sortY; + +struct sortVecX { + bool operator() (Vec2f vec1, Vec2f vec2) { return (vec1[0] < vec2[0]); } +} sortX; + +Rect MedianFlowTracker::Track(const cv::Mat &frame) +{ + std::vector corners; + std::vector cornersNext; + std::vector status; + std::vector err; + int maxCorners = 15; + double qualityLevel = 0.01; + double minDistance = 10; + + Mat frame_roi = frame_(position_); + + goodFeaturesToTrack(frame_roi, corners, maxCorners, qualityLevel, minDistance); + + for (int i = 0; i < corners.size(); i++) + { + corners[i].x += position_.tl().x; + corners[i].y += position_.tl().y; + } + + calcOpticalFlowPyrLK(frame_, frame, corners, cornersNext, status, err, Size(10, 10)); + + std::vector cornersNew; + std::vector cornersNextNew; + int i = 0; + + for_each(status.begin(), status.end(), [&](int stat) + { + if (stat) + { + cornersNew.push_back(corners[i]); + cornersNextNew.push_back(cornersNext[i]); + i++; + } + }); + + std::vector vectors; + std::vector vectorsSortX; + std::vector vectorsSortY; + + for (int i = 0; i < cornersNew.size(); i++) + { + vectors.push_back(cornersNextNew[i] - cornersNew[i]); + vectorsSortX.push_back(vectors[i]); + vectorsSortY.push_back(vectors[i]); + } + + sort(vectorsSortX.begin(), vectorsSortX.end(), sortX); + sort(vectorsSortY.begin(), vectorsSortY.end(), sortY); + + int idxMedian = vectors.size() / 2; + Vec2f medianCenter(vectorsSortX[idxMedian][0], vectorsSortY[idxMedian][1]); + + Point2f pointNew1 = Point2f(position_.x + medianCenter[0], position_.y + medianCenter[1]); + Point2f pointNew2 = Point2f(position_.x + position_.width + medianCenter[0], position_.y + position_.height + medianCenter[1]); + Rect positionNext = Rect(pointNew1, pointNew2); + + position_ = positionNext; + frame_ = frame.clone(); + + return position_; +}