Commit 96953843 authored by simonreich's avatar simonreich Committed by Alexander Alekhin

Merge pull request #1690 from simonreich:epf

Adds Edge-Preserving Filter (#1690)

* Module EPF - Edge-Preserving Filter added

* Changed name from template to epf

* Removed clang-format file

* Added header Files. Eliminated showWindow function. Used CommandLineParser.

* Moved filter from epf module to ximgproc

* Removed header files from sample

* Minor bug fix in demo. Pointers in demo removed.

* Pointers removed. InputArray/OutputArray added

* License header added

* License header from sample file removed

* Unit test for performance added

* Replaced manual mean computation with cv::mean

* Beautified code via clang-format and https://raw.githubusercontent.com/opencv/opencv_contrib/master/modules/cvv/.clang-format

* Merged historic if... else if statement into one if statement

* Trailing whitespace removed and .. changed into .

* Tabs replaced with 4 spaces.

* Removed subwindow = src(roi);

* Moved type test to beginning of code

* Removed indentation from namespace and added //! @}

* Added name to header

* git cleanup introduced some errors fixed here

* Changed path testdata/perf/320x260.png to perf/320x260.png

* Fixed warning declaration of 'subwindow1' hides previous local declaration

* Fixed warning 'const' qualifier on reference type 'cv::InputArray' (aka 'const cv::_InputArray &') has no effect

* Accuracy test added/

* Renamed void edgepreservingFilter to void edgePreservingFilter
parent 483d7d98
......@@ -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
......
// 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
// 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)))
{
/* 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
#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;
}
// 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
// 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 "test_precomp.hpp"
namespace opencv_test { namespace {
TEST(ximgproc_EdgepreservingFilter, regression)
{
// Load original image
std::string filename = string(cvtest::TS::ptr()->get_data_path()) + "perf/320x260.png";
cv::Mat src, dst, noise, original = imread(filename, 1);
ASSERT_FALSE(original.empty()) << "Could not load input image " << filename;
ASSERT_EQ(3, original.channels()) << "Load color input image " << filename;
// add noise
noise = Mat(original.size(), original.type());
cv::randn(noise, 0, 5);
src = original + noise;
// Filter
int kernel = 9;
double threshold = 20;
ximgproc::edgePreservingFilter(src, dst, kernel, threshold);
double psnr = cvtest::PSNR(original, dst);
//printf("psnr=%.2f\n", psnr);
ASSERT_LT(psnr, 25.0);
}
}} // namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment