Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Edge-Preserving Filter #1690

Merged
merged 26 commits into from
Aug 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e23e838
Module EPF - Edge-Preserving Filter added
Jul 12, 2018
3891dd5
Changed name from template to epf
Jul 12, 2018
538049f
Removed clang-format file
Jul 12, 2018
0d96f6d
Added header Files. Eliminated showWindow function. Used CommandLineP…
Jul 12, 2018
c272800
Moved filter from epf module to ximgproc
Jul 12, 2018
23d45a6
Removed header files from sample
Jul 13, 2018
82dfa23
Minor bug fix in demo. Pointers in demo removed.
Jul 13, 2018
88ce7a7
Pointers removed. InputArray/OutputArray added
Jul 13, 2018
1f115b6
License header added
Jul 31, 2018
19686de
License header from sample file removed
Jul 31, 2018
9cc391b
Unit test for performance added
Jul 31, 2018
c411b39
Replaced manual mean computation with cv::mean
Jul 31, 2018
55766d9
Beautified code via clang-format and https://raw.githubusercontent.co…
Jul 31, 2018
7e89c3d
Merged historic if... else if statement into one if statement
Jul 31, 2018
1b079bd
Trailing whitespace removed and .. changed into .
Aug 1, 2018
16ee637
Tabs replaced with 4 spaces.
Aug 1, 2018
3216c78
Removed subwindow = src(roi);
Aug 1, 2018
5fa0aba
Moved type test to beginning of code
Aug 1, 2018
845d807
Removed indentation from namespace and added //! @}
Aug 1, 2018
c6ea43f
Added name to header
Aug 1, 2018
a91e629
git cleanup introduced some errors fixed here
Aug 1, 2018
7e7d108
Changed path testdata/perf/320x260.png to perf/320x260.png
Aug 1, 2018
acb5b27
Fixed warning declaration of 'subwindow1' hides previous local declar…
Aug 1, 2018
c7e4c3a
Fixed warning 'const' qualifier on reference type 'cv::InputArray' (a…
Aug 1, 2018
fee6573
Accuracy test added/
Aug 1, 2018
5795c0a
Renamed void edgepreservingFilter to void edgePreservingFilter
Aug 2, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/ximgproc/include/opencv2/ximgproc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "ximgproc/ridgefilter.hpp"
#include "ximgproc/brightedges.hpp"
#include "ximgproc/run_length_morphology.hpp"
#include "ximgproc/edgepreserving_filter.hpp"


/** @defgroup ximgproc Extended Image Processing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.

#ifndef __OPENCV_EDGEPRESERVINGFILTER_HPP__
#define __OPENCV_EDGEPRESERVINGFILTER_HPP__

#include <opencv2/core.hpp>

namespace cv { namespace ximgproc {

//! @addtogroup ximgproc
//! @{

/**
* @brief Smoothes an image using the Edge-Preserving filter.
*
* The function smoothes Gaussian noise as well as salt & pepper noise.
* For more details about this implementation, please see
* [ReiWoe18] Reich, S. and Wörgötter, F. and Dellen, B. (2018). A Real-Time Edge-Preserving Denoising Filter. Proceedings of the 13th International Joint Conference on Computer Vision, Imaging and Computer Graphics Theory and Applications (VISIGRAPP): Visapp, 85-94, 4. DOI: 10.5220/0006509000850094.
*
* @param src Source 8-bit 3-channel image.
* @param dst Destination image of the same size and type as src.
* @param d Diameter of each pixel neighborhood that is used during filtering. Must be greater or equal 3.
* @param threshold Threshold, which distinguishes between noise, outliers, and data.
*/
CV_EXPORTS_W void edgePreservingFilter( InputArray src, OutputArray dst, int d, double threshold );

}} // namespace

//! @}

#endif
45 changes: 45 additions & 0 deletions modules/ximgproc/perf/perf_edgepreserving_filter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html.
//
// Created by Simon Reich
//
#include "perf_precomp.hpp"

namespace opencv_test
{
namespace
{

/* 1. Define parameter type and test fixture */
typedef tuple<int, double> RGFTestParam;
typedef TestBaseWithParam<RGFTestParam> EdgepreservingFilterTest;

/* 2. Declare the testsuite */
PERF_TEST_P(EdgepreservingFilterTest, perf,
Combine(Values(-20, 0, 10), Values(-100, 0, 20)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be we should start from accuracy test and do some checks?
Performance tests have not any checks usually, they just measure performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any resources on OpenCV on how to write an accuracy test available? I just found lengthy example code at https://github.com/opencv/opencv/wiki/QA_in_OpenCV.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an accuracy test.

However, some more resources would be helpful.

{
/* 3. Get actual test parameters */
RGFTestParam params = GetParam();
int kernelSize = get<0>(params);
double threshold = get<1>(params);

/* 4. Allocate and initialize arguments for tested function */
std::string filename = getDataPath("perf/320x260.png");
Mat src = imread(filename, 1);
Mat dst(src.size(), src.type());

/* 5. Manifest your expectations about this test */
declare.in(src).out(dst);

/* 6. Collect the samples! */
PERF_SAMPLE_BEGIN();
ximgproc::edgePreservingFilter(src, dst, kernelSize, threshold);
PERF_SAMPLE_END();

/* 7. Do not check anything */
SANITY_CHECK_NOTHING();
}

} // namespace
} // namespace opencv_test
42 changes: 42 additions & 0 deletions modules/ximgproc/samples/edgepreserving_filter_demo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <iostream>
#include <opencv2/highgui.hpp>
#include <opencv2/ximgproc.hpp>
#include <string>

using namespace cv;

int main(int argc, char **argv)
{
cv::CommandLineParser parser(
argc, argv,
"{help h ? | | help message}"
"{@image | | Image filename to process }");
if (parser.has("help") || !parser.has("@image"))
{
parser.printMessage();
return 0;
}

// Load image from first parameter
std::string filename = parser.get<std::string>("@image");
Mat image = imread(filename, 1), res;

if (!image.data)
{
std::cerr << "No image data at " << filename << std::endl;
throw;
}

// Before filtering
imshow("Original image", image);
waitKey(0);

// Initialize filter. Kernel size 5x5, threshold 20
ximgproc::edgePreservingFilter(image, res, 9, 20);

// After filtering
imshow("Filtered image", res);
waitKey(0);

return 0;
}
236 changes: 236 additions & 0 deletions modules/ximgproc/src/edgepreserving_filter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html.
//
// Created by Simon Reich
//

#include "precomp.hpp"

namespace cv
{
namespace ximgproc
{
using namespace std;

void edgePreservingFilter(InputArray _src, OutputArray _dst, int d,
double threshold)
{
CV_Assert(_src.type() == CV_8UC3);

Mat src = _src.getMat();

// [re]create the output array so that it has the proper size and type.
_dst.create(src.size(), src.type());
Mat dst = _dst.getMat();
src.copyTo(dst);

if (d < 3)
d = 3;
int subwindowX = d, subwindowY = d;

if (threshold < 0)
threshold = 0;

// number of image channels
int nChannel = src.channels();

vector<double> pixel(nChannel, 0);
vector<vector<double>> line1(src.rows, pixel);
vector<vector<vector<double>>> weight(src.cols,
line1); // global weights
vector<vector<vector<double>>> imageResult(
src.cols, line1); // global normalized image

// do algorithm
cv::Mat subwindow, subwindow1;
for (int posX = 0; posX < src.cols - subwindowX; posX++)
{
for (int posY = 0; posY < src.rows - subwindowY; posY++)
{
cv::Rect roi =
cv::Rect(posX, posY, subwindowX, subwindowY);
subwindow1 = src(roi);
cv::GaussianBlur(subwindow1, subwindow, cv::Size(5, 5),
0.3, 0.3);

// compute arithmetic mean of subwindow
cv::Scalar ArithmeticMean = cv::mean(subwindow);

// compute pixelwise distance
vector<vector<double>> pixelwiseDist;

for (int subPosX = 0; subPosX < subwindow.cols;
subPosX++)
{
vector<double> line;
for (int subPosY = 0; subPosY < subwindow.rows;
subPosY++)
{
cv::Vec3b intensity =
subwindow.at<cv::Vec3b>(subPosY,
subPosX);
double distance =
((double)intensity.val[0] -
ArithmeticMean[0]) *
((double)intensity.val[0] -
ArithmeticMean[0]) +
((double)intensity.val[1] -
ArithmeticMean[1]) *
((double)intensity.val[1] -
ArithmeticMean[1]) +
((double)intensity.val[2] -
ArithmeticMean[2]) *
((double)intensity.val[2] -
ArithmeticMean[2]);
distance = sqrt(distance);

line.push_back(distance);
};

pixelwiseDist.push_back(line);
};

// compute mean pixelwise distance
double meanPixelwiseDist = 0;

for (int i = 0; i < (int)pixelwiseDist.size(); i++)
for (int j = 0;
j < (int)pixelwiseDist[i].size(); j++)
meanPixelwiseDist +=
pixelwiseDist[i][j];

meanPixelwiseDist /= ((int)pixelwiseDist.size() *
(int)pixelwiseDist[0].size());

// detect edge
for (int subPosX = 0; subPosX < subwindow.cols;
subPosX++)
{
for (int subPosY = 0; subPosY < subwindow.rows;
subPosY++)
{
if ((meanPixelwiseDist <= threshold &&
pixelwiseDist[subPosX][subPosY] <=
threshold) ||
(meanPixelwiseDist <= threshold &&
pixelwiseDist[subPosX][subPosY] >
threshold))
{
// global Position
int globalPosX = posX + subPosX;
int globalPosY = posY + subPosY;

// compute global weight
cv::Vec3b intensity =
subwindow.at<cv::Vec3b>(
subPosY, subPosX);
weight[globalPosX][globalPosY]
[0] +=
intensity.val[0] *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
(threshold -
pixelwiseDist[subPosX]
[subPosY]);
weight[globalPosX][globalPosY]
[1] +=
intensity.val[1] *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
(threshold -
pixelwiseDist[subPosX]
[subPosY]);
weight[globalPosX][globalPosY]
[2] +=
intensity.val[2] *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
(threshold -
pixelwiseDist[subPosX]
[subPosY]);

// compute final image
imageResult[globalPosX]
[globalPosY][0] +=
intensity.val[0] *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
ArithmeticMean[0];
imageResult[globalPosX]
[globalPosY][1] +=
intensity.val[1] *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
ArithmeticMean[1];
imageResult[globalPosX]
[globalPosY][2] +=
intensity.val[2] *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
(threshold -
pixelwiseDist[subPosX]
[subPosY]) *
ArithmeticMean[2];
};
};
};
};
};

// compute final image
for (int globalPosX = 0; globalPosX < (int)imageResult.size();
globalPosX++)
{
for (int globalPosY = 0;
globalPosY < (int)imageResult[globalPosX].size();
globalPosY++)
{
// cout << "globalPosX: " << globalPosX << "/"
// << dst.cols << "," << imageResult.size () <<
// "\tglobalPosY: " << globalPosY << "/" <<
// dst.rows << "," <<imageResult.at
// (globalPosX).size () << endl;

// add image to result
cv::Vec3b intensity =
src.at<cv::Vec3b>(globalPosY, globalPosX);
imageResult[globalPosX][globalPosY][0] +=
(double)intensity.val[0];
imageResult[globalPosX][globalPosY][1] +=
(double)intensity.val[1];
imageResult[globalPosX][globalPosY][2] +=
(double)intensity.val[2];

// normalize using weight
imageResult[globalPosX][globalPosY][0] /=
(weight[globalPosX][globalPosY][0] + 1);
imageResult[globalPosX][globalPosY][1] /=
(weight[globalPosX][globalPosY][1] + 1);
imageResult[globalPosX][globalPosY][2] /=
(weight[globalPosX][globalPosY][2] + 1);

// copy to output image frame
dst.at<cv::Vec3b>(globalPosY, globalPosX)[0] =
(uchar)imageResult[globalPosX][globalPosY][0];
dst.at<cv::Vec3b>(globalPosY, globalPosX)[1] =
(uchar)imageResult[globalPosX][globalPosY][1];
dst.at<cv::Vec3b>(globalPosY, globalPosX)[2] =
(uchar)imageResult[globalPosX][globalPosY][2];
};
};
}
} // namespace ximgproc
} // namespace cv
Loading