From 3408ff2913ce24746a147b8c9dce09fa5745bbae Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Mon, 4 Jul 2016 16:39:36 +0400 Subject: [PATCH 01/17] written binarisation implementation --- src/workaround.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/workaround.cpp b/src/workaround.cpp index 2eba458..caeba3d 100644 --- a/src/workaround.cpp +++ b/src/workaround.cpp @@ -6,5 +6,8 @@ using namespace std; void MatrixProcessor::Threshold(unsigned char* const data, const int width, const int height, const int threshold) { - // TODO: Add thresholding logic here. + // TODO: Add thresholding logic here. + for (int i = 0; i < width*height; i++) { + data[i] = data[i] > threshold ? 255 : 0; + } } From 0734721614fc0c22a7df5cb2119d8c5d2eea0200 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Tue, 5 Jul 2016 16:13:25 +0300 Subject: [PATCH 02/17] Created test application for image processing. Written implementation of ImageProcessor --- samples/imgproc_demo.cpp | 51 ++++++++++++++++++++++++++++++++++++++++ src/image_processing.cpp | 25 ++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 samples/imgproc_demo.cpp create mode 100644 src/image_processing.cpp diff --git a/samples/imgproc_demo.cpp b/samples/imgproc_demo.cpp new file mode 100644 index 0000000..a34914a --- /dev/null +++ b/samples/imgproc_demo.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" + +using namespace std; +using namespace cv; + +#include "image_processing.hpp" + + +const char* kAbout = + "This is image processing test application"; + +const char* kOptions = + "{ @image | | image 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.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; + } + + CertainImageProcessor proc; + proc.CvtColor(src, Rect(0, 0, src.cols, src.rows)); + + // Show source image. + const string kSrcWindowName = "Source image"; + const int kWaitKeyDelay = 0; + namedWindow(kSrcWindowName, WINDOW_NORMAL); + resizeWindow(kSrcWindowName, 640, 480); + imshow(kSrcWindowName, src); + waitKey(kWaitKeyDelay); + + return 0; +} diff --git a/src/image_processing.cpp b/src/image_processing.cpp new file mode 100644 index 0000000..993893f --- /dev/null +++ b/src/image_processing.cpp @@ -0,0 +1,25 @@ +#pragma once +#include "image_processing.hpp" + +using namespace std; +using namespace cv; + +Mat CertainImageProcessor::CvtColor(const cv::Mat &src, const cv::Rect &roi) { + return src; +} + +Mat CertainImageProcessor::Filter(const cv::Mat &src, const cv::Rect &roi, + const int kSize) { + return src; +} + +Mat CertainImageProcessor::DetectEdges(const cv::Mat &src, const cv::Rect &roi, + const int filterSize, const int lowThreshold, const int ratio, + const int kernelSize) { + return src; +} + +Mat CertainImageProcessor::Pixelize(const cv::Mat &src, const cv::Rect &roi, + const int kDivs) { + return src; +} \ No newline at end of file From 6533db920939474271b047436585614c04fb6a0f Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Tue, 5 Jul 2016 17:08:28 +0300 Subject: [PATCH 03/17] written CvtColor implementation --- include/image_processing.hpp | 16 +++++++++++++++- samples/imgproc_demo.cpp | 12 ++++++------ src/image_processing.cpp | 26 +++++++++++++++++++++----- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/include/image_processing.hpp b/include/image_processing.hpp index ca1f116..1a7c2a3 100644 --- a/include/image_processing.hpp +++ b/include/image_processing.hpp @@ -3,7 +3,9 @@ #include #include -#include "opencv2/core/core.hpp" +#include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/imgproc.hpp" class ImageProcessor { public: @@ -15,4 +17,16 @@ class ImageProcessor { const int kernelSize) = 0; virtual cv::Mat Pixelize(const cv::Mat &src, const cv::Rect &roi, const int kDivs) = 0; +}; + +class ImageProcessorImpl { + 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/imgproc_demo.cpp b/samples/imgproc_demo.cpp index a34914a..9e347fe 100644 --- a/samples/imgproc_demo.cpp +++ b/samples/imgproc_demo.cpp @@ -29,22 +29,22 @@ int main(int argc, const char** argv) { } // Read image. - Mat src = imread(parser.get(0), CV_LOAD_IMAGE_GRAYSCALE); + Mat src = imread(parser.get(0)); if (src.empty()) { cout << "Failed to open image file '" + parser.get(0) + "'." << endl; return 0; } - CertainImageProcessor proc; - proc.CvtColor(src, Rect(0, 0, src.cols, src.rows)); + ImageProcessorImpl proc; + Mat res = proc.CvtColor(src, Rect(0, 0, src.cols-30, src.rows-30)); // Show source image. const string kSrcWindowName = "Source image"; - const int kWaitKeyDelay = 0; - namedWindow(kSrcWindowName, WINDOW_NORMAL); - resizeWindow(kSrcWindowName, 640, 480); imshow(kSrcWindowName, src); + const string kResWindowName = "Processed image"; + imshow(kResWindowName, res); + const int kWaitKeyDelay = 0; waitKey(kWaitKeyDelay); return 0; diff --git a/src/image_processing.cpp b/src/image_processing.cpp index 993893f..f56892b 100644 --- a/src/image_processing.cpp +++ b/src/image_processing.cpp @@ -4,22 +4,38 @@ using namespace std; using namespace cv; -Mat CertainImageProcessor::CvtColor(const cv::Mat &src, const cv::Rect &roi) { - return src; +Mat ImageProcessorImpl::CvtColor(const cv::Mat &src, const cv::Rect &roi) { + /*Mat res = src.clone(); + Mat res_roi = res(roi); + Mat res_roi_gray; + cvtColor(res_roi, res_roi_gray, CV_BGR2GRAY); + cvtColor(res_roi_gray, res_roi_gray, CV_GRAY2BGR); + res_roi_gray.copyTo(res_roi); + return res;*/ + + Mat src_copy = src.clone(); + Mat src_copy_roi = src_copy(roi); + Mat dst_gray_roi; + cvtColor(src_copy_roi, dst_gray_roi, CV_BGR2GRAY); + vector channels(src.channels(), dst_gray_roi); + Mat dst_roi; + merge(channels, dst_roi); + dst_roi.copyTo(src_copy_roi); + return src_copy; } -Mat CertainImageProcessor::Filter(const cv::Mat &src, const cv::Rect &roi, +Mat ImageProcessorImpl::Filter(const cv::Mat &src, const cv::Rect &roi, const int kSize) { return src; } -Mat CertainImageProcessor::DetectEdges(const cv::Mat &src, const cv::Rect &roi, +Mat ImageProcessorImpl::DetectEdges(const cv::Mat &src, const cv::Rect &roi, const int filterSize, const int lowThreshold, const int ratio, const int kernelSize) { return src; } -Mat CertainImageProcessor::Pixelize(const cv::Mat &src, const cv::Rect &roi, +Mat ImageProcessorImpl::Pixelize(const cv::Mat &src, const cv::Rect &roi, const int kDivs) { return src; } \ No newline at end of file From 8b83dc16e5f0d734e09a60e27b323729a10002d5 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Tue, 5 Jul 2016 17:41:36 +0300 Subject: [PATCH 04/17] written Filter and DetectEdges implementation --- samples/imgproc_demo.cpp | 8 +++++++- src/image_processing.cpp | 38 ++++++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/samples/imgproc_demo.cpp b/samples/imgproc_demo.cpp index 9e347fe..03b26ce 100644 --- a/samples/imgproc_demo.cpp +++ b/samples/imgproc_demo.cpp @@ -38,7 +38,13 @@ int main(int argc, const char** argv) { ImageProcessorImpl proc; Mat res = proc.CvtColor(src, Rect(0, 0, src.cols-30, src.rows-30)); - + res = proc.Filter(src, Rect(10, 20, src.cols - 50, src.rows - 50), 11); + int filterSize = 5; + int lowThreshold = 50; + int ratio = 3; + int kernelSize = 5; + res = proc.DetectEdges(src, Rect(10, 20, src.cols - 50, src.rows - 50), + filterSize, lowThreshold, ratio, kernelSize); // Show source image. const string kSrcWindowName = "Source image"; imshow(kSrcWindowName, src); diff --git a/src/image_processing.cpp b/src/image_processing.cpp index f56892b..21df355 100644 --- a/src/image_processing.cpp +++ b/src/image_processing.cpp @@ -5,14 +5,6 @@ using namespace std; using namespace cv; Mat ImageProcessorImpl::CvtColor(const cv::Mat &src, const cv::Rect &roi) { - /*Mat res = src.clone(); - Mat res_roi = res(roi); - Mat res_roi_gray; - cvtColor(res_roi, res_roi_gray, CV_BGR2GRAY); - cvtColor(res_roi_gray, res_roi_gray, CV_GRAY2BGR); - res_roi_gray.copyTo(res_roi); - return res;*/ - Mat src_copy = src.clone(); Mat src_copy_roi = src_copy(roi); Mat dst_gray_roi; @@ -26,13 +18,39 @@ Mat ImageProcessorImpl::CvtColor(const cv::Mat &src, const cv::Rect &roi) { Mat ImageProcessorImpl::Filter(const cv::Mat &src, const cv::Rect &roi, const int kSize) { - return src; + Mat src_copy = src.clone(); + Mat src_copy_roi = src_copy(roi); + medianBlur(src_copy_roi, src_copy_roi, kSize); + return src_copy; } Mat ImageProcessorImpl::DetectEdges(const cv::Mat &src, const cv::Rect &roi, const int filterSize, const int lowThreshold, const int ratio, const int kernelSize) { - return src; + Mat src_roi = src.clone()(roi); + Mat src_gray_roi; + cvtColor(src_roi, src_gray_roi, CV_BGR2GRAY); + Mat gray_blurred; + blur(src_gray_roi, gray_blurred, Size(filterSize, filterSize)); + Mat detected_edges; + Canny(gray_blurred, detected_edges, lowThreshold, lowThreshold * ratio); + Mat dst = src.clone(); + Mat dst_roi = dst(roi); + dst_roi.setTo(Scalar::all(0)); + src_roi.copyTo(dst_roi, detected_edges); + return dst; + + /*Выделить подматрицу src_roi из копии src. + Сконвертировать матрицу src_roi в оттенки серого, результат записать в матрицу src_gray_roi. + Отфильтровать src_gray_roi с использованием линейного фильтра, результат записать в матрицу gray_blurred.Примечание: для фильтрации можно использовать функцию blur. + Построить ребра detected_edges на изображении gray_blurred с помощью функции Canny. + Создать матрицу dst. + Скопировать изображение src в dst. + Выделить подматрицу dst_roi из dst в соответствии с областью roi. + Обнулить все значения в подматрице dst_roi.Примечание : необходимо использовать статический метод all класса Scalar. + Скопировать src_roi в dst_roi в соответствии с маской detected_edges. + Вернуть dst.*/ + } Mat ImageProcessorImpl::Pixelize(const cv::Mat &src, const cv::Rect &roi, From 769c668ec32bea6c87f909f38041bee014a3906c Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Tue, 5 Jul 2016 17:56:00 +0300 Subject: [PATCH 05/17] written Pixelize implementation --- samples/imgproc_demo.cpp | 1 + src/image_processing.cpp | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/samples/imgproc_demo.cpp b/samples/imgproc_demo.cpp index 03b26ce..e2a66b3 100644 --- a/samples/imgproc_demo.cpp +++ b/samples/imgproc_demo.cpp @@ -45,6 +45,7 @@ int main(int argc, const char** argv) { int kernelSize = 5; res = proc.DetectEdges(src, Rect(10, 20, src.cols - 50, src.rows - 50), filterSize, lowThreshold, ratio, kernelSize); + res = proc.Pixelize(src, Rect(10, 20, src.cols - 50, src.rows - 50), 5); // Show source image. const string kSrcWindowName = "Source image"; imshow(kSrcWindowName, src); diff --git a/src/image_processing.cpp b/src/image_processing.cpp index 21df355..857ac1a 100644 --- a/src/image_processing.cpp +++ b/src/image_processing.cpp @@ -39,21 +39,21 @@ Mat ImageProcessorImpl::DetectEdges(const cv::Mat &src, const cv::Rect &roi, dst_roi.setTo(Scalar::all(0)); src_roi.copyTo(dst_roi, detected_edges); return dst; - - /*Выделить подматрицу src_roi из копии src. - Сконвертировать матрицу src_roi в оттенки серого, результат записать в матрицу src_gray_roi. - Отфильтровать src_gray_roi с использованием линейного фильтра, результат записать в матрицу gray_blurred.Примечание: для фильтрации можно использовать функцию blur. - Построить ребра detected_edges на изображении gray_blurred с помощью функции Canny. - Создать матрицу dst. - Скопировать изображение src в dst. - Выделить подматрицу dst_roi из dst в соответствии с областью roi. - Обнулить все значения в подматрице dst_roi.Примечание : необходимо использовать статический метод all класса Scalar. - Скопировать src_roi в dst_roi в соответствии с маской detected_edges. - Вернуть dst.*/ - } Mat ImageProcessorImpl::Pixelize(const cv::Mat &src, const cv::Rect &roi, const int kDivs) { - return src; + Mat src_copy = src.clone(); + Mat src_copy_roi = src_copy(roi); + int block_size_x = roi.width / kDivs; + int block_size_y = roi.height / kDivs; + for (int i = 0; i < kDivs; i++) { + for (int j = 0; j < kDivs; j ++) { + int x = i*block_size_x; + int y = j*block_size_y; + Mat src_roi_block = src_copy_roi(Rect(x, y, block_size_x, block_size_y)); + blur(src_roi_block, src_roi_block, Size(block_size_x, block_size_y)); + } + } + return src_copy; } \ No newline at end of file From caf6d414bc3ad0fd8c2b5e0320e145cde61328d0 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Tue, 5 Jul 2016 18:07:50 +0300 Subject: [PATCH 06/17] Parced command line options --- samples/imgproc_demo.cpp | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/samples/imgproc_demo.cpp b/samples/imgproc_demo.cpp index e2a66b3..4930534 100644 --- a/samples/imgproc_demo.cpp +++ b/samples/imgproc_demo.cpp @@ -14,8 +14,12 @@ const char* kAbout = "This is image processing test application"; const char* kOptions = - "{ @image | | image 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 }"; int main(int argc, const char** argv) { // Parse command line arguments. @@ -37,16 +41,26 @@ int main(int argc, const char** argv) { } ImageProcessorImpl proc; - Mat res = proc.CvtColor(src, Rect(0, 0, src.cols-30, src.rows-30)); - res = proc.Filter(src, Rect(10, 20, src.cols - 50, src.rows - 50), 11); - int filterSize = 5; - int lowThreshold = 50; - int ratio = 3; - int kernelSize = 5; - res = proc.DetectEdges(src, Rect(10, 20, src.cols - 50, src.rows - 50), - filterSize, lowThreshold, ratio, kernelSize); - res = proc.Pixelize(src, Rect(10, 20, src.cols - 50, src.rows - 50), 5); - // Show source image. + Mat res = src.clone(); + if (parser.has("gray")) { + res = proc.CvtColor(src, Rect(0, 0, src.cols - 30, src.rows - 30)); + } + else if (parser.has("median")) { + res = proc.Filter(src, Rect(10, 20, src.cols - 50, src.rows - 50), 11); + } + else if (parser.has("edges")) { + int filterSize = 5; + int lowThreshold = 50; + int ratio = 3; + int kernelSize = 5; + res = proc.DetectEdges(src, Rect(10, 20, src.cols - 50, src.rows - 50), + filterSize, lowThreshold, ratio, kernelSize); + } + else if (parser.has("pix")) { + res = proc.Pixelize(src, Rect(10, 20, src.cols - 50, src.rows - 50), 5); + } + + // Show images. const string kSrcWindowName = "Source image"; imshow(kSrcWindowName, src); const string kResWindowName = "Processed image"; From a804100fb432ac114a5b7a4141902e17b81c8381 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Tue, 5 Jul 2016 18:16:57 +0300 Subject: [PATCH 07/17] Added callback --- samples/imgproc_demo.cpp | 44 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/samples/imgproc_demo.cpp b/samples/imgproc_demo.cpp index 4930534..a1772a4 100644 --- a/samples/imgproc_demo.cpp +++ b/samples/imgproc_demo.cpp @@ -10,6 +10,36 @@ using namespace cv; #include "image_processing.hpp" +struct MouseCallbackState { + bool is_selection_started; + bool is_selection_finished; + Point point_first; + Point point_second; +}; + +void CallBackFunc(int event, int x, int y, int flags, void* userdata) +{ + /*if (event == EVENT_LBUTTONDOWN) + { + cout << "Left button of the mouse is clicked - position (" << x << ", " << y << ")" << endl; + MouseCallbackState * p_mc_state = (MouseCallbackState *)userdata; + p_mc_state->point_first = + } + else if (event == EVENT_RBUTTONDOWN) + { + cout << "Right button of the mouse is clicked - position (" << x << ", " << y << ")" << endl; + } + else if (event == EVENT_MBUTTONDOWN) + { + cout << "Middle button of the mouse is clicked - position (" << x << ", " << y << ")" << endl; + } + else if (event == EVENT_MOUSEMOVE) + { + cout << "Mouse move over the window - position (" << x << ", " << y << ")" << endl; + + }*/ +} + const char* kAbout = "This is image processing test application"; @@ -40,6 +70,17 @@ int main(int argc, const char** argv) { return 0; } + //set the callback function for any mouse event + // Show source image. + const string kSrcWindowName = "Source image"; + imshow(kSrcWindowName, src); + MouseCallbackState mc_state; + setMouseCallback(kSrcWindowName, CallBackFunc, (void *)&mc_state); + + while (!mc_state.is_selection_finished) { + waitKey(30); + } + ImageProcessorImpl proc; Mat res = src.clone(); if (parser.has("gray")) { @@ -60,9 +101,6 @@ int main(int argc, const char** argv) { res = proc.Pixelize(src, Rect(10, 20, src.cols - 50, src.rows - 50), 5); } - // Show images. - const string kSrcWindowName = "Source image"; - imshow(kSrcWindowName, src); const string kResWindowName = "Processed image"; imshow(kResWindowName, res); const int kWaitKeyDelay = 0; From 21e2848a8bf6cabff4d5cc282d6c8830ca382254 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Wed, 6 Jul 2016 14:09:08 +0300 Subject: [PATCH 08/17] commit before pull --- samples/imgproc_demo.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/imgproc_demo.cpp b/samples/imgproc_demo.cpp index a1772a4..fb147ac 100644 --- a/samples/imgproc_demo.cpp +++ b/samples/imgproc_demo.cpp @@ -19,11 +19,11 @@ struct MouseCallbackState { void CallBackFunc(int event, int x, int y, int flags, void* userdata) { - /*if (event == EVENT_LBUTTONDOWN) + if (event == EVENT_LBUTTONDOWN) { cout << "Left button of the mouse is clicked - position (" << x << ", " << y << ")" << endl; - MouseCallbackState * p_mc_state = (MouseCallbackState *)userdata; - p_mc_state->point_first = + /*MouseCallbackState * p_mc_state = (MouseCallbackState *)userdata; + p_mc_state->point_first = */ } else if (event == EVENT_RBUTTONDOWN) { @@ -37,7 +37,7 @@ void CallBackFunc(int event, int x, int y, int flags, void* userdata) { cout << "Mouse move over the window - position (" << x << ", " << y << ")" << endl; - }*/ + } } const char* kAbout = @@ -77,9 +77,9 @@ int main(int argc, const char** argv) { MouseCallbackState mc_state; setMouseCallback(kSrcWindowName, CallBackFunc, (void *)&mc_state); - while (!mc_state.is_selection_finished) { + /*while (!mc_state.is_selection_finished) { waitKey(30); - } + }*/ ImageProcessorImpl proc; Mat res = src.clone(); From cdbb6a1bad5bc639ab43247acaaf84ab04acf217 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Wed, 6 Jul 2016 14:31:13 +0300 Subject: [PATCH 09/17] draw rectangle --- samples/imgproc_demo.cpp | 42 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/samples/imgproc_demo.cpp b/samples/imgproc_demo.cpp index fb147ac..2009412 100644 --- a/samples/imgproc_demo.cpp +++ b/samples/imgproc_demo.cpp @@ -15,29 +15,23 @@ struct MouseCallbackState { bool is_selection_finished; Point point_first; Point point_second; + + MouseCallbackState() : is_selection_started(false), is_selection_finished(false) {} }; void CallBackFunc(int event, int x, int y, int flags, void* userdata) { + MouseCallbackState * p_mc_state = (MouseCallbackState *)userdata; if (event == EVENT_LBUTTONDOWN) { - cout << "Left button of the mouse is clicked - position (" << x << ", " << y << ")" << endl; - /*MouseCallbackState * p_mc_state = (MouseCallbackState *)userdata; - p_mc_state->point_first = */ - } - else if (event == EVENT_RBUTTONDOWN) - { - cout << "Right button of the mouse is clicked - position (" << x << ", " << y << ")" << endl; - } - else if (event == EVENT_MBUTTONDOWN) - { - cout << "Middle button of the mouse is clicked - position (" << x << ", " << y << ")" << endl; + p_mc_state->point_first = Point(x, y); + p_mc_state->is_selection_started = true; } - else if (event == EVENT_MOUSEMOVE) + else if (event == EVENT_LBUTTONUP) { - cout << "Mouse move over the window - position (" << x << ", " << y << ")" << endl; - + p_mc_state->is_selection_finished = true; } + p_mc_state->point_second = Point(x, y); } const char* kAbout = @@ -77,28 +71,36 @@ int main(int argc, const char** argv) { MouseCallbackState mc_state; setMouseCallback(kSrcWindowName, CallBackFunc, (void *)&mc_state); - /*while (!mc_state.is_selection_finished) { + Rect roi; + + while (!mc_state.is_selection_finished) { + if (mc_state.is_selection_started) { + Mat src_copy = src.clone(); + roi = Rect(mc_state.point_first, mc_state.point_second); + rectangle(src_copy, roi, Scalar(255, 0, 0)); + imshow(kSrcWindowName, src_copy); + } waitKey(30); - }*/ + } ImageProcessorImpl proc; Mat res = src.clone(); if (parser.has("gray")) { - res = proc.CvtColor(src, Rect(0, 0, src.cols - 30, src.rows - 30)); + res = proc.CvtColor(src, roi); } else if (parser.has("median")) { - res = proc.Filter(src, Rect(10, 20, src.cols - 50, src.rows - 50), 11); + res = proc.Filter(src, roi, 11); } else if (parser.has("edges")) { int filterSize = 5; int lowThreshold = 50; int ratio = 3; int kernelSize = 5; - res = proc.DetectEdges(src, Rect(10, 20, src.cols - 50, src.rows - 50), + res = proc.DetectEdges(src, roi, filterSize, lowThreshold, ratio, kernelSize); } else if (parser.has("pix")) { - res = proc.Pixelize(src, Rect(10, 20, src.cols - 50, src.rows - 50), 5); + res = proc.Pixelize(src, roi, 5); } const string kResWindowName = "Processed image"; From d536737a3127f4f608683e43e82248694a793509 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Wed, 6 Jul 2016 14:45:19 +0300 Subject: [PATCH 10/17] written stubs for CascadeDetector --- include/detection.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/detection.hpp b/include/detection.hpp index 46930d3..4d064b7 100644 --- a/include/detection.hpp +++ b/include/detection.hpp @@ -4,6 +4,7 @@ #include #include "opencv2/core/core.hpp" +#include "opencv2/objdetect.hpp" class Detector { public: @@ -12,3 +13,13 @@ class Detector { virtual void Detect(const cv::Mat& frame, std::vector& objects, std::vector& scores) = 0; }; + +class CascadeDetector : public Detector { +public: + 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; +}; From f9c8db101e31361f8d3e4586988d99bc874337dc Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Wed, 6 Jul 2016 16:54:00 +0300 Subject: [PATCH 11/17] Written implementaton for CascadeDetector. Written implementation of detection on video --- src/detection.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/detection.cpp b/src/detection.cpp index 15e7fd1..32e1fb1 100644 --- a/src/detection.cpp +++ b/src/detection.cpp @@ -4,10 +4,32 @@ using std::string; using std::shared_ptr; +using std::vector; using namespace cv; shared_ptr Detector::CreateDetector(const string& name) { - std::cerr << "Failed to create detector with name '" << name << "'" - << std::endl; - return nullptr; + if (name == "cascade") { + return std::make_shared(); + } + else { + std::cerr << "Failed to create detector with name '" << name << "'" + << std::endl; + return nullptr; + } } + +bool CascadeDetector::Init(const std::string& model_file_path) { + return detector.load(model_file_path); +} + +void CascadeDetector::Detect(const cv::Mat& frame, std::vector& objects, + std::vector& scores) { + if (!detector.empty()) { + vector dscores; + detector.detectMultiScale(frame, objects, dscores); + scores.resize(dscores.size()); + for (int i = 0; i < dscores.size(); i++) { + scores[i] = (double)dscores[i]; + } + } +} \ No newline at end of file From 1a1d763be86b48ca7a7f8ba853d9c9a99da7d703 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Wed, 6 Jul 2016 17:27:16 +0300 Subject: [PATCH 12/17] detection demo added to control --- samples/detection_demo.cpp | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 samples/detection_demo.cpp diff --git a/samples/detection_demo.cpp b/samples/detection_demo.cpp new file mode 100644 index 0000000..dc72770 --- /dev/null +++ b/samples/detection_demo.cpp @@ -0,0 +1,80 @@ +#include +#include + +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/highgui.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 | | detector model }" + "{ h ? help usage | | print help message }"; + +void processImage(string & windowName, Mat & frame, CascadeDetector & detector) { + vector objects; + vector scores; + detector.Detect(frame, objects, scores); + Mat frame_copy = frame.clone(); + for each (Rect object in objects) + { + rectangle(frame_copy, object, Scalar(255, 0, 0)); + } + imshow(windowName, frame_copy); + waitKey(1); +} + +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; + } + + if (!parser.has("model")) { + cerr << "Specify detector model" << endl; + return -1; + } + + string mfilepath = parser.get("model"); + + CascadeDetector detector; + detector.CreateDetector("cascade"); + if (!detector.Init(mfilepath)) { + cerr << "Can't load cascade!" << endl; + return -1; + } + + string windowName("Detected objects"); + + if (parser.has("video")) { + string vfilepath = parser.get("video"); + VideoCapture cap(vfilepath); + Mat frame; + if(!cap.isOpened()) { + cerr << "Can't open video capture!" << parser.get("video") << endl; + return -1; + } + cap >> frame; + while (frame.cols * frame.rows) + { + processImage(windowName, frame, detector); + cap >> frame; + } + } + + return 0; +} From 235b6556006031b01bb6bd843bfbd62e5edbc358 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Thu, 7 Jul 2016 17:23:40 +0300 Subject: [PATCH 13/17] simple median flow tracking realisation --- include/tracking.hpp | 11 ++++++++ src/tracking.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/include/tracking.hpp b/include/tracking.hpp index f80004c..7533fb6 100644 --- a/include/tracking.hpp +++ b/include/tracking.hpp @@ -11,3 +11,14 @@ 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 : public Tracker { +public: + 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_; +}; + diff --git a/src/tracking.cpp b/src/tracking.cpp index 26cbe06..0347223 100644 --- a/src/tracking.cpp +++ b/src/tracking.cpp @@ -4,10 +4,73 @@ using std::string; using std::shared_ptr; +using std::vector; using namespace cv; +#include "opencv2/features2d.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/video.hpp" + shared_ptr Tracker::CreateTracker(const string &name) { - std::cerr << "Failed to create tracker with name '" << name << "'" - << std::endl; + if (name == "median_flow") { + return std::make_shared(); + } + else { + std::cerr << "Failed to create tracker with name '" << name << "'" + << std::endl; + } return nullptr; } + +bool MedianFlowTracker::Init(const cv::Mat &frame, const cv::Rect &roi) { + frame_ = frame; + position_ = roi; + return frame.cols && frame.rows && roi.width && roi.height; +} + +Rect MedianFlowTracker::Track(const Mat &frame) { + vector corners; + const int maxCorners = 50; + const double qualityLevel = 0.01; + const double minDistance = 5.0; + Mat mask = Mat::zeros(frame.size(), CV_8UC1); + rectangle(mask, position_, 255, -1); + Mat gray_frame_; + cvtColor(frame_, gray_frame_, CV_BGR2GRAY); + + goodFeaturesToTrack(gray_frame_, corners, maxCorners, qualityLevel, minDistance, mask); + if (!corners.size()) return position_; + + Mat gray_frame; + cvtColor(frame, gray_frame, CV_BGR2GRAY); + + /*Mat markers_frame = frame_.clone(); + for each (auto corner in corners) { + drawMarker(markers_frame, corner, Scalar(0, 255, 0)); + } + imshow("markers", markers_frame); + waitKey(1);*/ + vector new_corners; + vector status; + Mat error; + calcOpticalFlowPyrLK(gray_frame_, gray_frame, corners, new_corners, status, error); + + vector shifts_x; + vector shifts_y; + for (int i = 0; i < corners.size(); i++) { + if (status[i]) { + shifts_x.push_back(new_corners[i].x - corners[i].x); + shifts_y.push_back(new_corners[i].y - corners[i].y); + } + } + + std::nth_element(shifts_x.begin(), shifts_x.begin() + shifts_x.size() / 2, shifts_x.end()); + std::nth_element(shifts_y.begin(), shifts_y.begin() + shifts_y.size() / 2, shifts_y.end()); + + position_.x += shifts_x[shifts_x.size() / 2] + 0.5; + position_.y += shifts_y[shifts_y.size() / 2] + 0.5; + + frame_ = frame.clone(); + return position_; +} From f0f1ec704a158a617d2d597e713c5ddc93f2384f Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Thu, 7 Jul 2016 17:24:21 +0300 Subject: [PATCH 14/17] simple median flow tracking realisation --- samples/tracking_demo.cpp | 95 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 samples/tracking_demo.cpp diff --git a/samples/tracking_demo.cpp b/samples/tracking_demo.cpp new file mode 100644 index 0000000..072f305 --- /dev/null +++ b/samples/tracking_demo.cpp @@ -0,0 +1,95 @@ +#include +#include + +#include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/imgproc.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; + + MouseCallbackState() : is_selection_started(false), is_selection_finished(false) {} +}; + +void CallBackFunc(int event, int x, int y, int flags, void* userdata) +{ + MouseCallbackState * p_mc_state = (MouseCallbackState *)userdata; + if (event == EVENT_LBUTTONDOWN) + { + p_mc_state->point_first = Point(x, y); + p_mc_state->is_selection_started = true; + } + else if (event == EVENT_LBUTTONUP) + { + p_mc_state->is_selection_finished = true; + } + p_mc_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; + } + + if (parser.has("video")) { + string vfilepath = parser.get("video"); + VideoCapture cap(vfilepath); + Mat frame; + if (!cap.isOpened()) { + cerr << "Can't open video capture!" << parser.get("video") << endl; + return -1; + } + cap >> frame; + + const string kSrcWindowName = "Tracking object"; + imshow(kSrcWindowName, frame); + MouseCallbackState mc_state; + setMouseCallback(kSrcWindowName, CallBackFunc, (void *)&mc_state); + Rect roi; + + while (!mc_state.is_selection_finished) { + if (mc_state.is_selection_started) { + Mat frame_copy = frame.clone(); + roi = Rect(mc_state.point_first, mc_state.point_second); + rectangle(frame_copy, roi, Scalar(255, 0, 0)); + imshow(kSrcWindowName, frame_copy); + } + waitKey(30); + } + + MedianFlowTracker tracker; + tracker.Init(frame, roi); + while (frame.cols * frame.rows) + { + Rect obj = tracker.Track(frame); + Mat tracking_frame = frame.clone(); + rectangle(tracking_frame, obj, Scalar(255, 0, 0)); + imshow("Tracking object", tracking_frame); + waitKey(30); + cap >> frame; + } + } + + return 0; +} From bcf6def58bf218a3c329adc41bee5622bdde7a10 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Thu, 7 Jul 2016 18:10:18 +0300 Subject: [PATCH 15/17] written forward backward filtration --- src/tracking.cpp | 60 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/tracking.cpp b/src/tracking.cpp index 0347223..e02ad86 100644 --- a/src/tracking.cpp +++ b/src/tracking.cpp @@ -29,6 +29,18 @@ bool MedianFlowTracker::Init(const cv::Mat &frame, const cv::Rect &roi) { return frame.cols && frame.rows && roi.width && roi.height; } +template void erase_elements (vector & data, vector & status) { + int erase_index = 0; + for (int i = 0; i < data.size(); i++) { + if (!status[i]) { + data.erase(data.begin() + erase_index); + } + else { + erase_index++; + } + } +}; + Rect MedianFlowTracker::Track(const Mat &frame) { vector corners; const int maxCorners = 50; @@ -45,24 +57,40 @@ Rect MedianFlowTracker::Track(const Mat &frame) { Mat gray_frame; cvtColor(frame, gray_frame, CV_BGR2GRAY); - /*Mat markers_frame = frame_.clone(); - for each (auto corner in corners) { - drawMarker(markers_frame, corner, Scalar(0, 255, 0)); + vector corners_forward, corners_backward; + vector status_forward, status_backward; + Mat error_forward, error_backward; + + calcOpticalFlowPyrLK(gray_frame_, gray_frame, corners, corners_forward, status_forward, error_forward); + erase_elements(corners, status_forward); + erase_elements(corners_forward, status_forward); + + calcOpticalFlowPyrLK(gray_frame, gray_frame_, corners_forward, corners_backward, status_backward, error_backward); + erase_elements(corners, status_backward); + erase_elements(corners_forward, status_backward); + erase_elements(corners_backward, status_backward); + + vector fb_error(corners.size()); + for (int i = 0; i < fb_error.size(); i++) { + fb_error[i] = (corners[i].x - corners_backward[i].x) * (corners[i].x - corners_backward[i].x) + + (corners[i].y - corners_backward[i].y) * (corners[i].y - corners_backward[i].y); + } + std::nth_element(fb_error.begin(), fb_error.begin() + fb_error.size() / 2, fb_error.end()); + float median_error = fb_error[fb_error.size() / 2]; + + vector error_status(fb_error.size()); + for (int i = 0; i < error_status.size(); i++) { + error_status[i] = fb_error[i] > median_error ? 0 : 1; } - imshow("markers", markers_frame); - waitKey(1);*/ - vector new_corners; - vector status; - Mat error; - calcOpticalFlowPyrLK(gray_frame_, gray_frame, corners, new_corners, status, error); - - vector shifts_x; - vector shifts_y; + erase_elements(corners, error_status); + erase_elements(corners_forward, error_status); + erase_elements(corners_backward, error_status); + + vector shifts_x(corners.size()); + vector shifts_y(corners.size()); for (int i = 0; i < corners.size(); i++) { - if (status[i]) { - shifts_x.push_back(new_corners[i].x - corners[i].x); - shifts_y.push_back(new_corners[i].y - corners[i].y); - } + shifts_x[i] = (corners_forward[i].x - corners[i].x); + shifts_y[i] = (corners_forward[i].y - corners[i].y); } std::nth_element(shifts_x.begin(), shifts_x.begin() + shifts_x.size() / 2, shifts_x.end()); From 6b186d7d686c4c9b523a63ef9b75d6dbd7c4d3f2 Mon Sep 17 00:00:00 2001 From: ChumankinYuriy Date: Thu, 7 Jul 2016 18:21:13 +0300 Subject: [PATCH 16/17] written scaling --- src/tracking.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/tracking.cpp b/src/tracking.cpp index e02ad86..37d43ea 100644 --- a/src/tracking.cpp +++ b/src/tracking.cpp @@ -100,5 +100,21 @@ Rect MedianFlowTracker::Track(const Mat &frame) { position_.y += shifts_y[shifts_y.size() / 2] + 0.5; frame_ = frame.clone(); + + vector scales; + float old_distance(0), new_distance(0); + + for (int i = 0; i < corners.size(); i++) { + for (int j = i+1; j < corners.size(); j++) { + old_distance = (corners[i].x - corners[j].x) * (corners[i].x - corners[j].x) + + (corners[i].y - corners[j].y) * (corners[i].y - corners[j].y); + new_distance = (corners_forward[i].x - corners_forward[j].x) * (corners_forward[i].x - corners_forward[j].x) + + (corners_forward[i].y - corners_forward[j].y) * (corners_forward[i].y - corners_forward[j].y); + scales.push_back(old_distance / new_distance); + } + } + std::nth_element(scales.begin(), scales.begin() + scales.size() / 2, scales.end()); + float median_scale = scales[scales.size() / 2]; + return position_; } From 1b7db11d8b98023662e3be87fadc90a4d88a5456 Mon Sep 17 00:00:00 2001 From: Chumankin Yuriy Date: Thu, 7 Jul 2016 22:42:03 +0400 Subject: [PATCH 17/17] bug fix in scale calculation, Detect refactoring --- samples/tracking_demo.cpp | 4 ++ src/tracking.cpp | 89 +++++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/samples/tracking_demo.cpp b/samples/tracking_demo.cpp index 072f305..2723498 100644 --- a/samples/tracking_demo.cpp +++ b/samples/tracking_demo.cpp @@ -84,6 +84,10 @@ int main(int argc, const char** argv) { { Rect obj = tracker.Track(frame); Mat tracking_frame = frame.clone(); + if (! (obj.width * obj.height)) { + cout << "Tracking lost!" << endl; + break; + } rectangle(tracking_frame, obj, Scalar(255, 0, 0)); imshow("Tracking object", tracking_frame); waitKey(30); diff --git a/src/tracking.cpp b/src/tracking.cpp index 37d43ea..96ea66d 100644 --- a/src/tracking.cpp +++ b/src/tracking.cpp @@ -12,6 +12,35 @@ using namespace cv; #include "opencv2/highgui.hpp" #include "opencv2/video.hpp" +template bool eraseElements(vector & data, vector & status) { + int erase_index = 0; + for (int i = 0; i < data.size(); i++) { + if (!status[i]) { + data.erase(data.begin() + erase_index); + } + else { + erase_index++; + } + } + return data.size() > 0; +}; + +template T getMedian(vector & data) { + std::nth_element(data.begin(), data.begin() + data.size() / 2, data.end()); + return data[data.size() / 2]; +} + +Rect rescaleRect(Rect& rect, float scale) { + Rect res = rect; + int new_width = res.width / scale + 0.5; + int new_height = res.height / scale + 0.5; + res.x += (res.width - new_width) / 2; + res.y += (res.height - new_height) / 2; + res.width = new_width; + res.height = new_height; + return res; +} + shared_ptr Tracker::CreateTracker(const string &name) { if (name == "median_flow") { return std::make_shared(); @@ -23,36 +52,27 @@ shared_ptr Tracker::CreateTracker(const string &name) { return nullptr; } + bool MedianFlowTracker::Init(const cv::Mat &frame, const cv::Rect &roi) { frame_ = frame; position_ = roi; return frame.cols && frame.rows && roi.width && roi.height; } -template void erase_elements (vector & data, vector & status) { - int erase_index = 0; - for (int i = 0; i < data.size(); i++) { - if (!status[i]) { - data.erase(data.begin() + erase_index); - } - else { - erase_index++; - } - } -}; - Rect MedianFlowTracker::Track(const Mat &frame) { vector corners; const int maxCorners = 50; const double qualityLevel = 0.01; const double minDistance = 5.0; + const int minCorners = 5; + Mat mask = Mat::zeros(frame.size(), CV_8UC1); rectangle(mask, position_, 255, -1); Mat gray_frame_; cvtColor(frame_, gray_frame_, CV_BGR2GRAY); goodFeaturesToTrack(gray_frame_, corners, maxCorners, qualityLevel, minDistance, mask); - if (!corners.size()) return position_; + if (corners.size() < minCorners) return Rect(); Mat gray_frame; cvtColor(frame, gray_frame, CV_BGR2GRAY); @@ -62,29 +82,30 @@ Rect MedianFlowTracker::Track(const Mat &frame) { Mat error_forward, error_backward; calcOpticalFlowPyrLK(gray_frame_, gray_frame, corners, corners_forward, status_forward, error_forward); - erase_elements(corners, status_forward); - erase_elements(corners_forward, status_forward); + eraseElements(corners, status_forward); + eraseElements(corners_forward, status_forward); + if (corners.size() < minCorners) return Rect(); calcOpticalFlowPyrLK(gray_frame, gray_frame_, corners_forward, corners_backward, status_backward, error_backward); - erase_elements(corners, status_backward); - erase_elements(corners_forward, status_backward); - erase_elements(corners_backward, status_backward); + eraseElements(corners, status_backward); + eraseElements(corners_forward, status_backward); + eraseElements(corners_backward, status_backward); + if (corners.size() < minCorners) return Rect(); vector fb_error(corners.size()); for (int i = 0; i < fb_error.size(); i++) { - fb_error[i] = (corners[i].x - corners_backward[i].x) * (corners[i].x - corners_backward[i].x) + - (corners[i].y - corners_backward[i].y) * (corners[i].y - corners_backward[i].y); + fb_error[i] = norm(corners[i] - corners_backward[i]); } - std::nth_element(fb_error.begin(), fb_error.begin() + fb_error.size() / 2, fb_error.end()); - float median_error = fb_error[fb_error.size() / 2]; + float median_error = getMedian(fb_error); vector error_status(fb_error.size()); for (int i = 0; i < error_status.size(); i++) { error_status[i] = fb_error[i] > median_error ? 0 : 1; } - erase_elements(corners, error_status); - erase_elements(corners_forward, error_status); - erase_elements(corners_backward, error_status); + eraseElements(corners, error_status); + eraseElements(corners_forward, error_status); + eraseElements(corners_backward, error_status); + if (corners.size() < minCorners) return Rect(); vector shifts_x(corners.size()); vector shifts_y(corners.size()); @@ -93,11 +114,8 @@ Rect MedianFlowTracker::Track(const Mat &frame) { shifts_y[i] = (corners_forward[i].y - corners[i].y); } - std::nth_element(shifts_x.begin(), shifts_x.begin() + shifts_x.size() / 2, shifts_x.end()); - std::nth_element(shifts_y.begin(), shifts_y.begin() + shifts_y.size() / 2, shifts_y.end()); - - position_.x += shifts_x[shifts_x.size() / 2] + 0.5; - position_.y += shifts_y[shifts_y.size() / 2] + 0.5; + position_.x += getMedian(shifts_x) + 0.5; + position_.y += getMedian(shifts_y) + 0.5; frame_ = frame.clone(); @@ -106,15 +124,14 @@ Rect MedianFlowTracker::Track(const Mat &frame) { for (int i = 0; i < corners.size(); i++) { for (int j = i+1; j < corners.size(); j++) { - old_distance = (corners[i].x - corners[j].x) * (corners[i].x - corners[j].x) + - (corners[i].y - corners[j].y) * (corners[i].y - corners[j].y); - new_distance = (corners_forward[i].x - corners_forward[j].x) * (corners_forward[i].x - corners_forward[j].x) + - (corners_forward[i].y - corners_forward[j].y) * (corners_forward[i].y - corners_forward[j].y); + old_distance = norm(corners[i] - corners[j]); + new_distance = norm(corners_forward[i] - corners_forward[j]); scales.push_back(old_distance / new_distance); } } - std::nth_element(scales.begin(), scales.begin() + scales.size() / 2, scales.end()); - float median_scale = scales[scales.size() / 2]; + float median_scale = getMedian(scales); + + position_ = rescaleRect(position_, median_scale); return position_; }