Merge pull request #1672 from dbuesching:rl_morphology
* run length morphology * remove unused code, avoid warnings for undefined functions * handle empty input in getBoundingRectangle correctly, remove unused operations * changes according to code reviewpull/1686/head
parent
729737ddc9
commit
0f5d6ae194
8 changed files with 1495 additions and 0 deletions
@ -0,0 +1,119 @@ |
||||
// 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_RUN_LENGTH_MORPHOLOGY_HPP__ |
||||
#define __OPENCV_RUN_LENGTH_MORPHOLOGY_HPP__ |
||||
|
||||
#include <opencv2/core.hpp> |
||||
|
||||
namespace cv { |
||||
namespace ximgproc { |
||||
namespace rl { |
||||
|
||||
|
||||
//! @addtogroup ximgproc_run_length_morphology
|
||||
//! @{
|
||||
|
||||
/**
|
||||
* @brief Applies a fixed-level threshold to each array element. |
||||
* |
||||
* |
||||
* @param src input array (single-channel). |
||||
* @param rlDest resulting run length encoded image. |
||||
* @param thresh threshold value. |
||||
* @param type thresholding type (only cv::THRESH_BINARY and cv::THRESH_BINARY_INV are supported) |
||||
* |
||||
*/ |
||||
CV_EXPORTS void threshold(InputArray src, OutputArray rlDest, double thresh, int type); |
||||
|
||||
|
||||
/**
|
||||
* @brief Dilates an run-length encoded binary image by using a specific structuring element. |
||||
* |
||||
* |
||||
* @param rlSrc input image |
||||
* @param rlDest result |
||||
* @param rlKernel kernel |
||||
* @param anchor position of the anchor within the element; default value (0, 0) |
||||
* is usually the element center. |
||||
* |
||||
*/ |
||||
CV_EXPORTS void dilate(InputArray rlSrc, OutputArray rlDest, InputArray rlKernel, Point anchor = Point(0, 0)); |
||||
|
||||
/**
|
||||
* @brief Erodes an run-length encoded binary image by using a specific structuring element. |
||||
* |
||||
* |
||||
* @param rlSrc input image |
||||
* @param rlDest result |
||||
* @param rlKernel kernel |
||||
* @param bBoundaryOn indicates whether pixel outside the image boundary are assumed to be on |
||||
(True: works in the same way as the default of cv::erode, False: is a little faster) |
||||
* @param anchor position of the anchor within the element; default value (0, 0) |
||||
* is usually the element center. |
||||
* |
||||
*/ |
||||
CV_EXPORTS void erode(InputArray rlSrc, OutputArray rlDest, InputArray rlKernel, |
||||
bool bBoundaryOn = true, Point anchor = Point(0, 0)); |
||||
|
||||
/**
|
||||
* @brief Returns a run length encoded structuring element of the specified size and shape. |
||||
* |
||||
* |
||||
* @param shape Element shape that can be one of cv::MorphShapes |
||||
* @param ksize Size of the structuring element. |
||||
* |
||||
*/ |
||||
CV_EXPORTS cv::Mat getStructuringElement(int shape, Size ksize); |
||||
|
||||
/**
|
||||
* @brief Paint run length encoded binary image into an image. |
||||
* |
||||
* |
||||
* @param image image to paint into (currently only single channel images). |
||||
* @param rlSrc run length encoded image |
||||
* @param value all foreground pixel of the binary image are set to this value |
||||
* |
||||
*/ |
||||
CV_EXPORTS void paint(InputOutputArray image, InputArray rlSrc, const cv::Scalar& value); |
||||
|
||||
/**
|
||||
* @brief Check whether a custom made structuring element can be used with run length morphological operations. |
||||
* (It must consist of a continuous array of single runs per row) |
||||
* |
||||
* @param rlStructuringElement mask to be tested |
||||
*/ |
||||
CV_EXPORTS bool isRLMorphologyPossible(InputArray rlStructuringElement); |
||||
|
||||
/**
|
||||
* @brief Creates a run-length encoded image from a vector of runs (column begin, column end, row) |
||||
* |
||||
* @param runs vector of runs |
||||
* @param res result |
||||
* @param size image size (to be used if an "on" boundary should be used in erosion, using the default |
||||
* means that the size is computed from the extension of the input) |
||||
*/ |
||||
CV_EXPORTS void createRLEImage(std::vector<cv::Point3i>& runs, OutputArray res, Size size = Size(0, 0)); |
||||
|
||||
/**
|
||||
* @brief Applies a morphological operation to a run-length encoded binary image. |
||||
* |
||||
* |
||||
* @param rlSrc input image |
||||
* @param rlDest result |
||||
* @param op all operations supported by cv::morphologyEx (except cv::MORPH_HITMISS) |
||||
* @param rlKernel kernel |
||||
* @param bBoundaryOnForErosion indicates whether pixel outside the image boundary are assumed |
||||
* to be on for erosion operations (True: works in the same way as the default of cv::erode, |
||||
* False: is a little faster) |
||||
* @param anchor position of the anchor within the element; default value (0, 0) is usually the element center. |
||||
* |
||||
*/ |
||||
CV_EXPORTS void morphologyEx(InputArray rlSrc, OutputArray rlDest, int op, InputArray rlKernel, |
||||
bool bBoundaryOnForErosion = true, Point anchor = Point(0,0)); |
||||
|
||||
} |
||||
} |
||||
} |
||||
#endif |
@ -0,0 +1,37 @@ |
||||
// 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.
|
||||
#include "perf_precomp.hpp" |
||||
|
||||
namespace opencv_test { |
||||
namespace { |
||||
|
||||
typedef tuple<int, Size, int> RLParams; |
||||
|
||||
typedef TestBaseWithParam<RLParams> RLMorphologyPerfTest; |
||||
|
||||
PERF_TEST_P(RLMorphologyPerfTest, perf, Combine(Values(1,7, 21), Values(sz720p, sz2160p), |
||||
Values(MORPH_ERODE, MORPH_DILATE, MORPH_OPEN, MORPH_CLOSE, MORPH_GRADIENT,MORPH_TOPHAT, MORPH_BLACKHAT))) |
||||
{ |
||||
RLParams params = GetParam(); |
||||
int seSize = get<0>(params); |
||||
Size sz = get<1>(params); |
||||
int op = get<2>(params); |
||||
|
||||
Mat src(sz, CV_8U); |
||||
Mat thresholded, dstRLE; |
||||
Mat se = rl::getStructuringElement(MORPH_ELLIPSE, cv::Size(2 * seSize + 1, 2 * seSize + 1)); |
||||
|
||||
declare.in(src, WARMUP_RNG); |
||||
|
||||
TEST_CYCLE_N(4) |
||||
{ |
||||
rl::threshold(src, thresholded, 100.0, THRESH_BINARY); |
||||
rl::morphologyEx(thresholded, dstRLE, op, se); |
||||
} |
||||
|
||||
SANITY_CHECK_NOTHING(); |
||||
} |
||||
|
||||
} |
||||
} // namespace
|
@ -0,0 +1,246 @@ |
||||
#include <iostream> |
||||
|
||||
#include "opencv2/imgproc.hpp" |
||||
#include "opencv2/ximgproc.hpp" |
||||
#include "opencv2/imgcodecs.hpp" |
||||
#include "opencv2/highgui.hpp" |
||||
|
||||
using namespace std; |
||||
using namespace cv; |
||||
using namespace cv::ximgproc; |
||||
|
||||
// Adapted from cv_timer in cv_utilities
|
||||
class Timer |
||||
{ |
||||
public: |
||||
Timer() : start_(0), time_(0) {} |
||||
|
||||
void start() |
||||
{ |
||||
start_ = cv::getTickCount(); |
||||
} |
||||
|
||||
void stop() |
||||
{ |
||||
CV_Assert(start_ != 0); |
||||
int64 end = cv::getTickCount(); |
||||
time_ += end - start_; |
||||
start_ = 0; |
||||
} |
||||
|
||||
double time() |
||||
{ |
||||
double ret = time_ / cv::getTickFrequency(); |
||||
time_ = 0; |
||||
return ret; |
||||
} |
||||
|
||||
private: |
||||
int64 start_, time_; |
||||
}; |
||||
|
||||
static void help() |
||||
{ |
||||
|
||||
printf("\nAllows to estimate the efficiency of the morphology operations implemented\n" |
||||
"in ximgproc/run_length_morphology.cpp\n" |
||||
"Call:\n example_ximgproc_run_length_morphology_demo [image] -u=factor_upscaling image\n" |
||||
"Similar to the morphology2 sample of the main opencv library it shows the use\n" |
||||
"of rect, ellipse and cross kernels\n\n" |
||||
"As rectangular and cross-shaped structuring elements are highly optimized in opencv_imgproc module,\n" |
||||
"only with elliptical structuring elements a speedup is possible (e.g. for larger circles).\n" |
||||
"Run-length morphology has advantages for larger images.\n" |
||||
"You can verify this by upscaling your input with e.g. -u=2\n"); |
||||
printf( "Hot keys: \n" |
||||
"\tESC - quit the program\n" |
||||
"\tr - use rectangle structuring element\n" |
||||
"\te - use elliptic structuring element\n" |
||||
"\tc - use cross-shaped structuring element\n" |
||||
"\tSPACE - loop through all the options\n" ); |
||||
} |
||||
|
||||
static void print_introduction() |
||||
{ |
||||
printf("\nFirst select a threshold for binarization.\n" |
||||
"Then move the sliders for erosion/dilation or open/close operation\n\n" |
||||
"The ratio between the time of the execution from opencv_imgproc\n" |
||||
"and the code using run-length encoding will be displayed in the console\n\n"); |
||||
} |
||||
|
||||
Mat src, dst; |
||||
|
||||
int element_shape = MORPH_ELLIPSE; |
||||
|
||||
//the address of variable which receives trackbar position update
|
||||
int max_size = 40; |
||||
int open_close_pos = 0; |
||||
int erode_dilate_pos = 0; |
||||
int nThreshold = 100; |
||||
cv::Mat binaryImage; |
||||
cv::Mat binaryRLE, dstRLE; |
||||
cv::Mat rlePainted; |
||||
|
||||
static void PaintRLEToImage(cv::Mat& rleImage, cv::Mat& res, unsigned char uValue) |
||||
{ |
||||
res = cv::Scalar(0); |
||||
rl::paint(res, rleImage, Scalar((double) uValue)); |
||||
} |
||||
|
||||
|
||||
static bool AreImagesIdentical(cv::Mat& image1, cv::Mat& image2) |
||||
{ |
||||
cv::Mat diff; |
||||
cv::absdiff(image1, image2, diff); |
||||
int nDiff = cv::countNonZero(diff); |
||||
return (nDiff == 0); |
||||
} |
||||
|
||||
// callback function for open/close trackbar
|
||||
static void OpenClose(int, void*) |
||||
{ |
||||
int n = open_close_pos - max_size; |
||||
int an = n > 0 ? n : -n; |
||||
Mat element = getStructuringElement(element_shape, Size(an*2+1, an*2+1), Point(an, an) ); |
||||
Timer timer; |
||||
timer.start(); |
||||
if( n < 0 ) |
||||
morphologyEx(binaryImage, dst, MORPH_OPEN, element); |
||||
else |
||||
morphologyEx(binaryImage, dst, MORPH_CLOSE, element); |
||||
timer.stop(); |
||||
double imgproc_duration = timer.time(); |
||||
|
||||
element = rl::getStructuringElement(element_shape, Size(an * 2 + 1, an * 2 + 1)); |
||||
|
||||
Timer timer2; |
||||
timer2.start(); |
||||
if (n < 0) |
||||
rl::morphologyEx(binaryRLE, dstRLE, MORPH_OPEN, element, true); |
||||
else |
||||
rl::morphologyEx(binaryRLE, dstRLE, MORPH_CLOSE, element, true); |
||||
|
||||
timer2.stop(); |
||||
double rl_duration = timer2.time(); |
||||
cout << "ratio open/close duration: " << rl_duration / imgproc_duration << " (run-length: " |
||||
<< rl_duration << ", pixelwise: " << imgproc_duration << " )" << std::endl; |
||||
|
||||
PaintRLEToImage(dstRLE, rlePainted, (unsigned char)255); |
||||
if (!AreImagesIdentical(dst, rlePainted)) |
||||
{ |
||||
cout << "error result image are not identical" << endl; |
||||
} |
||||
|
||||
imshow("Open/Close", rlePainted); |
||||
} |
||||
|
||||
// callback function for erode/dilate trackbar
|
||||
static void ErodeDilate(int, void*) |
||||
{ |
||||
int n = erode_dilate_pos - max_size; |
||||
int an = n > 0 ? n : -n; |
||||
Mat element = getStructuringElement(element_shape, Size(an*2+1, an*2+1), Point(an, an) ); |
||||
Timer timer; |
||||
timer.start(); |
||||
if( n < 0 ) |
||||
erode(binaryImage, dst, element); |
||||
else |
||||
dilate(binaryImage, dst, element); |
||||
timer.stop(); |
||||
double imgproc_duration = timer.time(); |
||||
|
||||
element = rl::getStructuringElement(element_shape, Size(an*2+1, an*2+1)); |
||||
|
||||
Timer timer2; |
||||
timer2.start(); |
||||
if( n < 0 ) |
||||
rl::erode(binaryRLE, dstRLE, element, true); |
||||
else |
||||
rl::dilate(binaryRLE, dstRLE, element); |
||||
timer2.stop(); |
||||
double rl_duration = timer2.time(); |
||||
|
||||
PaintRLEToImage(dstRLE, rlePainted, (unsigned char)255); |
||||
cout << "ratio erode/dilate duration: " << rl_duration / imgproc_duration << |
||||
" (run-length: " << rl_duration << ", pixelwise: " << imgproc_duration << " )" << std::endl; |
||||
|
||||
if (!AreImagesIdentical(dst, rlePainted)) |
||||
{ |
||||
cout << "error result image are not identical" << endl; |
||||
} |
||||
|
||||
imshow("Erode/Dilate", rlePainted); |
||||
} |
||||
|
||||
static void OnChangeThreshold(int, void*) |
||||
{ |
||||
threshold(src, binaryImage, (double) nThreshold, 255.0, THRESH_BINARY ); |
||||
rl::threshold(src, binaryRLE, (double) nThreshold, THRESH_BINARY); |
||||
imshow("Threshold", binaryImage); |
||||
} |
||||
|
||||
|
||||
int main( int argc, char** argv ) |
||||
{ |
||||
cv::CommandLineParser parser(argc, argv, "{help h||}{ @image | ../data/aloeL.jpg | }{u| |}"); |
||||
if (parser.has("help")) |
||||
{ |
||||
help(); |
||||
return 0; |
||||
} |
||||
std::string filename = parser.get<std::string>("@image"); |
||||
|
||||
cv::Mat srcIn; |
||||
if( (srcIn = imread(filename,IMREAD_GRAYSCALE)).empty() ) |
||||
{ |
||||
help(); |
||||
return -1; |
||||
} |
||||
int nScale = 1; |
||||
if (parser.has("u")) |
||||
{ |
||||
int theScale = parser.get<int>("u"); |
||||
if (theScale > 1) |
||||
nScale = theScale; |
||||
} |
||||
|
||||
if (nScale == 1) |
||||
src = srcIn; |
||||
else |
||||
cv::resize(srcIn, src, cv::Size(srcIn.rows * nScale, srcIn.cols * nScale)); |
||||
|
||||
cout << "scale factor read " << nScale << endl; |
||||
|
||||
print_introduction(); |
||||
|
||||
//create windows for output images
|
||||
namedWindow("Open/Close",1); |
||||
namedWindow("Erode/Dilate",1); |
||||
namedWindow("Threshold",1); |
||||
|
||||
open_close_pos = erode_dilate_pos = max_size - 10; |
||||
createTrackbar("size s.e.", "Open/Close",&open_close_pos,max_size*2+1,OpenClose); |
||||
createTrackbar("size s.e.", "Erode/Dilate",&erode_dilate_pos,max_size*2+1,ErodeDilate); |
||||
createTrackbar("threshold", "Threshold",&nThreshold,255, OnChangeThreshold); |
||||
OnChangeThreshold(0, 0); |
||||
rlePainted.create(cv::Size(src.cols, src.rows), CV_8UC1); |
||||
|
||||
for(;;) |
||||
{ |
||||
OpenClose(open_close_pos, 0); |
||||
ErodeDilate(erode_dilate_pos, 0); |
||||
char c = (char)waitKey(0); |
||||
|
||||
if( c == 27 ) |
||||
break; |
||||
if( c == 'e' ) |
||||
element_shape = MORPH_ELLIPSE; |
||||
else if( c == 'r' ) |
||||
element_shape = MORPH_RECT; |
||||
else if( c == 'c' ) |
||||
element_shape = MORPH_CROSS; |
||||
else if( c == ' ' ) |
||||
element_shape = (element_shape + 1) % 3; |
||||
} |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,812 @@ |
||||
/*
|
||||
* By downloading, copying, installing or using the software you agree to this license. |
||||
* If you do not agree to this license, do not download, install, |
||||
* copy or use the software. |
||||
* |
||||
* |
||||
* License Agreement |
||||
* For Open Source Computer Vision Library |
||||
* (3 - clause BSD License) |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without modification, |
||||
* are permitted provided that the following conditions are met : |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* |
||||
* * Redistributions in binary form must reproduce the above copyright notice, |
||||
* this list of conditions and the following disclaimer in the documentation |
||||
* and / or other materials provided with the distribution. |
||||
* |
||||
* * Neither the names of the copyright holders nor the names of the contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* This software is provided by the copyright holders and contributors "as is" and |
||||
* any express or implied warranties, including, but not limited to, the implied |
||||
* warranties of merchantability and fitness for a particular purpose are disclaimed. |
||||
* In no event shall copyright holders or contributors be liable for any direct, |
||||
* indirect, incidental, special, exemplary, or consequential damages |
||||
* (including, but not limited to, procurement of substitute goods or services; |
||||
* loss of use, data, or profits; or business interruption) however caused |
||||
* and on any theory of liability, whether in contract, strict liability, |
||||
* or tort(including negligence or otherwise) arising in any way out of |
||||
* the use of this software, even if advised of the possibility of such damage. |
||||
*/ |
||||
#include "precomp.hpp" |
||||
#include <math.h> |
||||
#include <vector> |
||||
#include <iostream> |
||||
|
||||
|
||||
namespace cv { |
||||
namespace ximgproc { |
||||
namespace rl { |
||||
|
||||
struct rlType |
||||
{ |
||||
int cb, ce, r; |
||||
rlType(int cbIn, int ceIn, int rIn): cb(cbIn), ce(ceIn), r(rIn) {} |
||||
rlType(): cb(0), ce(0), r(0) {} |
||||
bool operator < (const rlType& other) const { if (r < other.r || (r == other.r && cb < other.cb) |
||||
|| (r == other.r && cb == other.cb && ce < other.ce)) return true; else return false; } |
||||
}; |
||||
|
||||
typedef std::vector<rlType> rlVec; |
||||
|
||||
template <class T> |
||||
void _thresholdLine(T* pData, int nWidth, int nRow, T threshold, int type, rlVec& res) |
||||
{ |
||||
bool bOn = false; |
||||
int nStartSegment = 0; |
||||
for (int j = 0; j < nWidth; j++) |
||||
{ |
||||
bool bAboveThreshold = (pData[j] > threshold); |
||||
bool bCurOn = (bAboveThreshold == (THRESH_BINARY == type)); |
||||
if (!bOn && bCurOn) |
||||
{ |
||||
nStartSegment = j; |
||||
bOn = true; |
||||
} |
||||
else if (bOn && !bCurOn) |
||||
{ |
||||
rlType chord(nStartSegment, j - 1, nRow); |
||||
res.push_back(chord); |
||||
bOn = false; |
||||
} |
||||
|
||||
} |
||||
if (bOn) |
||||
{ |
||||
rlType chord(nStartSegment, nWidth - 1, nRow); |
||||
res.push_back(chord); |
||||
} |
||||
} |
||||
|
||||
static void _threshold(cv::Mat& img, rlVec& res, double threshold, int type) |
||||
{ |
||||
res.clear(); |
||||
switch (img.depth()) |
||||
{ |
||||
case CV_8U: |
||||
for (int i = 0; i < img.rows; ++i) |
||||
_thresholdLine<uchar>(img.ptr(i), img.cols, i, (uchar) threshold, type, res); |
||||
break; |
||||
case CV_8S: |
||||
for (int i = 0; i < img.rows; ++i) |
||||
_thresholdLine<schar>((schar*) img.ptr(i), img.cols, i, (schar) threshold, type, res); |
||||
break; |
||||
case CV_16U: |
||||
for (int i = 0; i < img.rows; ++i) |
||||
{ |
||||
_thresholdLine<unsigned short>((unsigned short*)img.ptr(i), img.cols, i, |
||||
(unsigned short)threshold, type, res); |
||||
} |
||||
break; |
||||
case CV_16S: |
||||
for (int i = 0; i < img.rows; ++i) |
||||
_thresholdLine<short>((short*) img.ptr(i), img.cols, i, (short) threshold, type, res); |
||||
break; |
||||
case CV_32S: |
||||
for (int i = 0; i < img.rows; ++i) |
||||
_thresholdLine<int>((int*) img.ptr(i), img.cols, i, (int) threshold, type, res); |
||||
break; |
||||
case CV_32F: |
||||
for (int i = 0; i < img.rows; ++i) |
||||
_thresholdLine<float>((float*) img.ptr(i), img.cols, i, (float) threshold, type, res); |
||||
break; |
||||
case CV_64F: |
||||
for (int i = 0; i < img.rows; ++i) |
||||
_thresholdLine<double>((double*) img.ptr(i), img.cols, i, threshold, type, res); |
||||
break; |
||||
default: |
||||
CV_Error( CV_StsUnsupportedFormat, "unsupported image type" ); |
||||
} |
||||
} |
||||
|
||||
|
||||
static void convertToOutputArray(rlVec& runs, Size size, OutputArray& res) |
||||
{ |
||||
size_t nRuns = runs.size(); |
||||
std::vector<cv::Point3i> segments(nRuns + 1); |
||||
segments[0] = cv::Point3i(size.width, size.height, 0); |
||||
for (size_t i = 0; i < nRuns; ++i) |
||||
{ |
||||
rlType& curRun = runs[i]; |
||||
segments[i + 1] = Point3i(curRun.cb, curRun.ce, curRun.r); |
||||
} |
||||
Mat(segments).copyTo(res); |
||||
} |
||||
|
||||
|
||||
CV_EXPORTS void threshold(InputArray src, OutputArray rlDest, double thresh, int type) |
||||
{ |
||||
CV_INSTRUMENT_REGION(); |
||||
|
||||
Mat image = src.getMat(); |
||||
CV_Assert(!image.empty() && image.channels() == 1); |
||||
CV_Assert(type == THRESH_BINARY || type == THRESH_BINARY_INV); |
||||
rlVec runs; |
||||
_threshold(image, runs, thresh, type); |
||||
Size size(image.cols, image.rows); |
||||
|
||||
convertToOutputArray(runs, size, rlDest); |
||||
} |
||||
|
||||
|
||||
template <class T> |
||||
void paint_impl(cv::Mat& img, rlType* pRuns, int nSize, T value) |
||||
{ |
||||
int i; |
||||
rlType* pCurRun; |
||||
for (pCurRun = pRuns, i = 0; i< nSize; ++pCurRun, ++i) |
||||
{ |
||||
rlType& curRun = *pCurRun; |
||||
if (curRun.r < 0 || curRun.r >= img.rows || curRun.cb >= img.cols || curRun.ce < 0) |
||||
continue; |
||||
|
||||
T* rowPtr = (T*)img.ptr(curRun.r); |
||||
std::fill(rowPtr + std::max(curRun.cb, 0), rowPtr + std::min(curRun.ce + 1, img.cols), value); |
||||
} |
||||
} |
||||
|
||||
CV_EXPORTS void paint(InputOutputArray image, InputArray rlSrc, const Scalar& value) |
||||
{ |
||||
Mat _runs; |
||||
_runs = rlSrc.getMat(); |
||||
int N = _runs.checkVector(3); |
||||
if (N <= 1) |
||||
return; |
||||
|
||||
double dValue = value[0]; |
||||
|
||||
cv::Mat _image = image.getMat(); |
||||
|
||||
rlType* pRuns = (rlType*) &(_runs.at<Point3i>(1)); |
||||
switch (_image.type()) |
||||
{ |
||||
case CV_8UC1: |
||||
paint_impl<uchar>(_image, pRuns, N - 1, (uchar)dValue); |
||||
break; |
||||
case CV_8SC1: |
||||
paint_impl<schar>(_image, pRuns, N - 1, (schar)dValue); |
||||
break; |
||||
case CV_16UC1: |
||||
paint_impl<unsigned short>(_image, pRuns, N - 1, (unsigned short)dValue); |
||||
break; |
||||
case CV_16SC1: |
||||
paint_impl<short>(_image, pRuns, N - 1, (short)dValue); |
||||
break; |
||||
case CV_32SC1: |
||||
paint_impl<int>(_image, pRuns, N - 1, (int)dValue); |
||||
break; |
||||
case CV_32FC1: |
||||
paint_impl<float>(_image, pRuns, N - 1, (float)dValue); |
||||
break; |
||||
case CV_64FC1: |
||||
paint_impl<double>(_image, pRuns, N - 1, dValue); |
||||
break; |
||||
default: |
||||
CV_Error(CV_StsUnsupportedFormat, "unsupported image type"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
static void translateRegion(rlVec& reg, Point ptTrans) |
||||
{ |
||||
for (rlVec::iterator it=reg.begin();it!=reg.end();++it) |
||||
{ |
||||
it->r += ptTrans.y; |
||||
it->cb += ptTrans.x; |
||||
it->ce += ptTrans.x; |
||||
} |
||||
} |
||||
|
||||
CV_EXPORTS Mat getStructuringElement(int shape, Size ksize) |
||||
{ |
||||
Mat mask = cv::getStructuringElement(shape, ksize); |
||||
|
||||
rlVec reg; |
||||
_threshold(mask, reg, 0.0, THRESH_BINARY); |
||||
|
||||
Point ptTrans = - Point(mask.cols / 2, mask.rows / 2); |
||||
translateRegion(reg, ptTrans); |
||||
Mat rlDest; |
||||
convertToOutputArray(reg, Size(mask.cols, mask.rows), rlDest); |
||||
|
||||
return rlDest; |
||||
} |
||||
|
||||
static void erode_rle (rlVec& regIn, rlVec& regOut, rlVec& se) |
||||
{ |
||||
using namespace std; |
||||
|
||||
regOut.clear(); |
||||
|
||||
if (regIn.size() == 0) |
||||
return; |
||||
|
||||
int nMinRow = regIn[0].r; |
||||
int nMaxRow = regIn.back().r; |
||||
|
||||
int nRows = nMaxRow - nMinRow + 1; |
||||
|
||||
|
||||
const int EMPTY = -1; |
||||
|
||||
// setup a table which holds the index of the first chord for each row
|
||||
vector<int> pIdxChord1(nRows); |
||||
vector<int> pIdxNextRow(nRows); |
||||
|
||||
int i,j; |
||||
|
||||
for (i=1;i<nRows;i++) |
||||
{ |
||||
pIdxChord1[i] = EMPTY; |
||||
pIdxNextRow[i] = EMPTY; |
||||
} |
||||
|
||||
pIdxChord1[0] = 0; |
||||
pIdxNextRow[nRows-1] = (int) regIn.size(); |
||||
|
||||
for (i=1; i < (int) regIn.size();i++) |
||||
if (regIn[i].r != regIn[i-1].r) |
||||
{ |
||||
pIdxChord1[regIn[i].r - nMinRow] = i; |
||||
pIdxNextRow[regIn[i-1].r - nMinRow] = i; |
||||
} |
||||
|
||||
int nMinRowSE = se[0].r; |
||||
int nMaxRowSE = se.back().r; |
||||
|
||||
int nRowsSE = nMaxRowSE - nMinRowSE + 1; |
||||
|
||||
assert(nRowsSE == (int) se.size()); |
||||
|
||||
vector<int> pCurIdxRow(nRowsSE); |
||||
|
||||
// loop through all possible rows
|
||||
for (i=nMinRow - nMinRowSE; i<= nMaxRow - nMaxRowSE; i++) |
||||
{ |
||||
// check whether all relevant rows are available
|
||||
bool bNextRow = false; |
||||
|
||||
for (j=0; j < nRowsSE; j++) |
||||
{ |
||||
// get idx of first chord in regIn for this row of the se
|
||||
pCurIdxRow[j] = pIdxChord1[ j + nMinRowSE + i - nMinRow]; |
||||
if (pCurIdxRow[j] == -1) |
||||
{ |
||||
bNextRow = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (bNextRow) |
||||
continue; |
||||
|
||||
while (!bNextRow) |
||||
{ |
||||
int nPossibleStart = std::numeric_limits<int>::min(); |
||||
|
||||
// search for row with max( cb - se.cb) (the leftmost possible position of a result chord
|
||||
for (j=0;j<nRowsSE;j++) |
||||
nPossibleStart = max(nPossibleStart, regIn[pCurIdxRow[j]].cb - se[j].cb); |
||||
|
||||
// for all rows skip chords whose end is left from the point
|
||||
// where it can contribute to a result
|
||||
bool bHaveResult = true; |
||||
int nLimitingRow = 0; |
||||
int nChordEnd = std::numeric_limits<int>::max(); //INT_MAX;
|
||||
|
||||
for (j=0;j<nRowsSE;j++) |
||||
{ |
||||
while (regIn[pCurIdxRow[j]].ce < nPossibleStart + se[j].ce && |
||||
pCurIdxRow[j] != pIdxNextRow[j + nMinRowSE + i - nMinRow]) |
||||
{ |
||||
pCurIdxRow[j]++; |
||||
} |
||||
|
||||
// if all chords in this row skipped -> next row
|
||||
if (pCurIdxRow[j] == pIdxNextRow[ j + nMinRowSE + i - nMinRow]) |
||||
{ |
||||
bNextRow = true; |
||||
bHaveResult = false; |
||||
break; |
||||
} |
||||
else if ( bHaveResult ) |
||||
{ |
||||
// can the found chord contribute to a result ?
|
||||
if (regIn[ pCurIdxRow[j] ].cb - se[j].cb <= nPossibleStart) |
||||
{ |
||||
int nCurPossibleEnd = regIn[ pCurIdxRow[j] ].ce - se[j].ce; |
||||
if (nCurPossibleEnd < nChordEnd) |
||||
{ |
||||
nChordEnd = nCurPossibleEnd; |
||||
nLimitingRow = j; |
||||
} |
||||
} |
||||
else |
||||
bHaveResult = false; |
||||
} |
||||
} |
||||
|
||||
if (bHaveResult) |
||||
{ |
||||
regOut.push_back(rlType(nPossibleStart, nChordEnd, i)); |
||||
pCurIdxRow[nLimitingRow]++; |
||||
|
||||
if (pCurIdxRow[nLimitingRow] == pIdxNextRow[ nLimitingRow + nMinRowSE + i - nMinRow]) |
||||
bNextRow = true; |
||||
} |
||||
} // end while (!bNextRow
|
||||
} // end for
|
||||
|
||||
} |
||||
|
||||
static void convertInputArrayToRuns(InputArray& theArray, rlVec& runs, Size& theSize) |
||||
{ |
||||
Mat _runs; |
||||
_runs = theArray.getMat(); |
||||
int N = _runs.checkVector(3); |
||||
if (N == 0) |
||||
{ |
||||
runs.clear(); |
||||
return; |
||||
} |
||||
runs.resize(N - 1); |
||||
Point3i pt = _runs.at<Point3i>(0); |
||||
theSize.width = pt.x; |
||||
theSize.height = pt.y; |
||||
|
||||
for (int i = 1; i < N; ++i) |
||||
{ |
||||
pt = _runs.at<Point3i>(i); |
||||
runs[i-1] = rlType(pt.x, pt.y, pt.z); |
||||
} |
||||
} |
||||
|
||||
static void sortChords(rlVec& lChords) |
||||
{ |
||||
sort(lChords.begin(), lChords.end()); |
||||
} |
||||
|
||||
static void mergeNeighbouringChords(rlVec& rlIn, rlVec& rlOut) |
||||
{ |
||||
rlOut.clear(); |
||||
if (rlIn.size() == 0) |
||||
return; |
||||
|
||||
rlOut.push_back(rlIn[0]); |
||||
|
||||
for (int i = 1; i< (int)rlIn.size(); i++) |
||||
{ |
||||
rlType& curIn = rlIn[i]; |
||||
rlType& lastAddedOut = rlOut.back(); |
||||
if (curIn.r == lastAddedOut.r && curIn.cb <= lastAddedOut.ce + 1) |
||||
lastAddedOut.ce = max(curIn.ce, lastAddedOut.ce); |
||||
else |
||||
rlOut.push_back(curIn); |
||||
} |
||||
} |
||||
|
||||
static void union_regions(rlVec& reg1, rlVec& reg2, rlVec& regUnion) |
||||
{ |
||||
rlVec lAllChords(reg1); |
||||
|
||||
lAllChords.insert(lAllChords.end(), reg2.begin(), reg2.end()); |
||||
|
||||
sortChords(lAllChords); |
||||
mergeNeighbouringChords(lAllChords, regUnion); |
||||
} |
||||
|
||||
static void intersect(rlVec& reg1, rlVec& reg2, rlVec& regRes) |
||||
{ |
||||
rlVec::iterator end1 = reg1.end(); |
||||
rlVec::iterator end2 = reg2.end(); |
||||
|
||||
rlVec::iterator cur1 = reg1.begin(); |
||||
rlVec::iterator cur2 = reg2.begin(); |
||||
regRes.clear(); |
||||
|
||||
while (cur1 != end1 && cur2 != end2) |
||||
{ |
||||
if (cur1->r < cur2->r || (cur1->r == cur2->r && cur1->ce < cur2->cb)) |
||||
++cur1; |
||||
else if (cur2->r < cur1->r || (cur1->r == cur2->r && cur2->ce < cur1->cb)) |
||||
++cur2; |
||||
else |
||||
{ |
||||
assert(cur1->r == cur2->r); |
||||
int nStart = max(cur1->cb, cur2->cb); |
||||
int nEnd = min(cur1->ce, cur2->ce); |
||||
if (nStart > nEnd) |
||||
{ |
||||
assert(nStart <= nEnd); |
||||
} |
||||
regRes.push_back(rlType(nStart, nEnd, cur1->r)); |
||||
if (cur1->ce < cur2->ce) |
||||
++cur1; |
||||
else |
||||
++cur2; |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
static void addBoundary(rlVec& runsIn, int nWidth, int nHeight, int nBoundaryLeft, int nBoundaryTop, |
||||
int nBoundaryRight, int nBoundaryBottom, rlVec& res) |
||||
{ |
||||
rlVec boundary; |
||||
for (int i = -nBoundaryTop; i < 0; ++i) |
||||
boundary.push_back(rlType(-nBoundaryLeft, nWidth - 1 + nBoundaryRight, i)); |
||||
for (int i = 0; i < nHeight; ++i) |
||||
{ |
||||
boundary.push_back(rlType(-nBoundaryLeft, -1, i)); |
||||
boundary.push_back(rlType(nWidth, nWidth - 1 + nBoundaryRight, i)); |
||||
} |
||||
|
||||
for (int i = nHeight; i < nHeight + nBoundaryBottom; ++i) |
||||
boundary.push_back(rlType(-nBoundaryLeft, nWidth - 1 + nBoundaryRight, i)); |
||||
|
||||
union_regions(runsIn, boundary, res); |
||||
} |
||||
|
||||
static cv::Rect getBoundingRectangle(rlVec& reg) |
||||
{ |
||||
using namespace std; |
||||
cv::Rect rect; |
||||
if (reg.empty()) |
||||
{ |
||||
rect.x = rect.y = rect.width = rect.height = 0; |
||||
return rect; |
||||
} |
||||
|
||||
int minX = std::numeric_limits<int>::max(); |
||||
int minY = std::numeric_limits<int>::max(); |
||||
int maxX = std::numeric_limits<int>::min(); |
||||
int maxY = std::numeric_limits<int>::min(); |
||||
|
||||
int i; |
||||
for (i = 0; i<(int)reg.size(); i++) |
||||
{ |
||||
minX = min(minX, reg[i].cb); |
||||
maxX = max(maxX, reg[i].ce); |
||||
minY = min(minY, reg[i].r); |
||||
maxY = max(maxY, reg[i].r); |
||||
} |
||||
|
||||
rect.x = minX; |
||||
rect.y = minY; |
||||
rect.width = maxX - minX + 1; |
||||
rect.height = maxY - minY + 1; |
||||
return rect; |
||||
} |
||||
|
||||
static void createUprightRectangle(cv::Rect rect, rlVec &rl) |
||||
{ |
||||
rl.clear(); |
||||
rlType curRL; |
||||
int j; |
||||
int cb = rect.x; |
||||
int ce = rect.x + rect.width - 1; |
||||
for (j = 0; j < rect.height; j++) |
||||
{ |
||||
curRL.cb = cb; |
||||
curRL.ce = ce; |
||||
curRL.r = j + rect.y; |
||||
rl.push_back(curRL); |
||||
} |
||||
} |
||||
|
||||
static void erode_with_boundary_rle(rlVec& runsSource, int nWidth, int nHeight, rlVec& runsDestination, |
||||
rlVec& runsKernel) |
||||
{ |
||||
cv::Rect rect = getBoundingRectangle(runsKernel); |
||||
rlVec regExtended, regFrame, regResultRaw; |
||||
addBoundary(runsSource, nWidth, nHeight, max(0, -rect.x), max(0, -rect.y), |
||||
max(0, rect.x + rect.width), max(0, rect.y + rect.height), regExtended); |
||||
|
||||
erode_rle(regExtended, regResultRaw, runsKernel); |
||||
createUprightRectangle(cv::Rect(0, 0, nWidth, nHeight), regFrame); |
||||
intersect(regResultRaw, regFrame, runsDestination); |
||||
} |
||||
|
||||
|
||||
CV_EXPORTS void erode(InputArray rlSrc, OutputArray rlDest, InputArray rlKernel, bool bBoundaryOn, |
||||
Point anchor) |
||||
{ |
||||
rlVec runsSource, runsDestination, runsKernel; |
||||
Size sizeSource, sizeKernel; |
||||
convertInputArrayToRuns(rlSrc, runsSource, sizeSource); |
||||
convertInputArrayToRuns(rlKernel, runsKernel, sizeKernel); |
||||
|
||||
if (anchor != Point(0,0)) |
||||
translateRegion(runsKernel, -anchor); |
||||
|
||||
if (bBoundaryOn) |
||||
erode_with_boundary_rle(runsSource, sizeSource.width, sizeSource.height, runsDestination, runsKernel); |
||||
else |
||||
erode_rle(runsSource, runsDestination, runsKernel); |
||||
convertToOutputArray(runsDestination, sizeSource, rlDest); |
||||
} |
||||
|
||||
|
||||
static void subtract_rle( rlVec& regFrom, |
||||
rlVec& regSubtract, |
||||
rlVec& regRes) |
||||
{ |
||||
rlVec::iterator end1 = regFrom.end(); |
||||
rlVec::iterator end2 = regSubtract.end(); |
||||
|
||||
rlVec::iterator cur1 = regFrom.begin(); |
||||
rlVec::iterator cur2 = regSubtract.begin(); |
||||
regRes.clear(); |
||||
|
||||
while( cur1 != end1) |
||||
{ |
||||
if (cur2 == end2) |
||||
{ |
||||
regRes.insert( regRes.end(), cur1, end1); |
||||
return; |
||||
} |
||||
else if ( cur1->r < cur2->r || (cur1->r == cur2->r && cur1->ce < cur2->cb)) |
||||
{ |
||||
regRes.push_back(*cur1); |
||||
++cur1; |
||||
} |
||||
else if ( cur2->r < cur1->r || (cur1->r == cur2->r && cur2->ce < cur1->cb)) |
||||
++cur2; |
||||
else |
||||
{ |
||||
int curR = cur1->r; |
||||
assert(curR == cur2->r); |
||||
rlVec::iterator lastIncluded; |
||||
|
||||
bool bIncremented = false; |
||||
for (lastIncluded = cur2; |
||||
lastIncluded != end2 && lastIncluded->r == curR && lastIncluded->cb <= cur1->ce; |
||||
++lastIncluded) |
||||
{ |
||||
bIncremented = true; |
||||
} |
||||
|
||||
if (bIncremented) |
||||
--lastIncluded; |
||||
|
||||
// now all chords from cur2 to lastIncluded have an intersection with cur1
|
||||
if (cur1->cb < cur2->cb) |
||||
regRes.push_back(rlType(cur1->cb, cur2->cb - 1, curR)); |
||||
|
||||
// we add the gaps between the chords of reg2 to the result
|
||||
while (cur2 < lastIncluded) |
||||
{ |
||||
regRes.push_back(rlType(cur2->ce + 1, (cur2 + 1)->cb - 1, curR)); |
||||
if (regRes.back().cb > regRes.back().ce) |
||||
{ |
||||
assert(false); |
||||
} |
||||
++cur2; |
||||
} |
||||
|
||||
if (cur1->ce > lastIncluded->ce) |
||||
{ |
||||
regRes.push_back(rlType(lastIncluded->ce + 1, cur1->ce, curR)); |
||||
assert(regRes.back().cb <= regRes.back().ce); |
||||
} |
||||
++cur1; |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
static void invertRegion(rlVec& runsIn, rlVec& runsOut) |
||||
{ |
||||
// if there is only one chord in row -> do not insert anything for this row
|
||||
// otherwise insert chords for the spaces between chords
|
||||
runsOut.clear(); |
||||
int nCurRow = std::numeric_limits<int>::min(); |
||||
int nLastRight = nCurRow; |
||||
for (rlVec::iterator it = runsIn.begin(); it != runsIn.end(); ++it) |
||||
{ |
||||
rlType run = *it; |
||||
if (run.r != nCurRow) |
||||
{ |
||||
nCurRow = run.r; |
||||
nLastRight = run.ce; |
||||
} |
||||
else |
||||
{ |
||||
assert(run.cb > nLastRight + 1); |
||||
runsOut.push_back(rlType(nLastRight + 1, run.cb - 1, nCurRow)); |
||||
nLastRight = run.ce; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
static void dilate_rle(rlVec& runsSource, |
||||
rlVec& runsDestination, |
||||
rlVec& runsKernel) |
||||
{ |
||||
cv::Rect rectSource = getBoundingRectangle(runsSource); |
||||
cv::Rect rectKernel = getBoundingRectangle(runsKernel); |
||||
|
||||
cv::Rect background; |
||||
background.x = rectSource.x - 2 * rectKernel.width; |
||||
background.y = rectSource.y - 2 * rectKernel.height; |
||||
background.width = rectSource.width + 4 * rectKernel.width; |
||||
background.height = rectSource.height + 4 * rectKernel.height; |
||||
|
||||
rlVec rlBackground, rlSourceInverse, rlResultInverse; |
||||
createUprightRectangle(background, rlBackground); |
||||
subtract_rle(rlBackground, runsSource, rlSourceInverse); |
||||
|
||||
erode_rle(rlSourceInverse, rlResultInverse, runsKernel); |
||||
|
||||
invertRegion(rlResultInverse, runsDestination); |
||||
} |
||||
|
||||
|
||||
|
||||
CV_EXPORTS void dilate(InputArray rlSrc, OutputArray rlDest, InputArray rlKernel, Point anchor) |
||||
{ |
||||
rlVec runsSource, runsDestination, runsKernel; |
||||
Size sizeSource, sizeKernel; |
||||
convertInputArrayToRuns(rlSrc, runsSource, sizeSource); |
||||
convertInputArrayToRuns(rlKernel, runsKernel, sizeKernel); |
||||
|
||||
if (anchor != Point(0, 0)) |
||||
translateRegion(runsKernel, -anchor); |
||||
|
||||
dilate_rle(runsSource, runsDestination, runsKernel); |
||||
|
||||
convertToOutputArray(runsDestination, sizeSource, rlDest); |
||||
} |
||||
|
||||
CV_EXPORTS bool isRLMorphologyPossible(InputArray rlStructuringElement) |
||||
{ |
||||
rlVec runsKernel; |
||||
Size sizeKernel; |
||||
convertInputArrayToRuns(rlStructuringElement, runsKernel, sizeKernel); |
||||
|
||||
for (int i = 1; i < (int) runsKernel.size(); ++i) |
||||
if (runsKernel[i].r != runsKernel[i-1].r + 1) |
||||
return false; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
CV_EXPORTS void createRLEImage(std::vector<cv::Point3i>& runs, OutputArray res, Size size) |
||||
{ |
||||
size_t nRuns = runs.size(); |
||||
rlVec runsConverted(nRuns); |
||||
for (size_t i = 0u; i < nRuns; ++i) |
||||
{ |
||||
Point3i &curIn = runs[i]; |
||||
runsConverted[i] = rlType(curIn.x, curIn.y, curIn.z); |
||||
} |
||||
sortChords(runsConverted); |
||||
|
||||
if (size.width == 0 || size.height == 0) |
||||
{ |
||||
Rect boundingRect = getBoundingRectangle(runsConverted); |
||||
size.width = std::max(0, boundingRect.x + boundingRect.width); |
||||
size.height = std::max(0, boundingRect.y + boundingRect.height); |
||||
} |
||||
convertToOutputArray(runsConverted, size, res); |
||||
} |
||||
|
||||
|
||||
CV_EXPORTS void morphologyEx(InputArray rlSrc, OutputArray rlDest, int op, InputArray rlKernel, |
||||
bool bBoundaryOnForErosion, Point anchor) |
||||
{ |
||||
if (op == MORPH_ERODE) |
||||
rl::erode(rlSrc, rlDest, rlKernel, bBoundaryOnForErosion, anchor); |
||||
else if (op == MORPH_DILATE) |
||||
rl::dilate(rlSrc, rlDest, rlKernel, anchor); |
||||
else |
||||
{ |
||||
rlVec runsSource, runsKernel, runsDestination; |
||||
Size sizeSource, sizeKernel; |
||||
convertInputArrayToRuns(rlKernel, runsKernel, sizeKernel); |
||||
convertInputArrayToRuns(rlSrc, runsSource, sizeSource); |
||||
|
||||
if (anchor != Point(0, 0)) |
||||
translateRegion(runsKernel, -anchor); |
||||
|
||||
switch (op) |
||||
{ |
||||
case MORPH_OPEN: |
||||
{ |
||||
rlVec runsEroded; |
||||
|
||||
if (bBoundaryOnForErosion) |
||||
erode_with_boundary_rle(runsSource, sizeSource.width, sizeSource.height, runsEroded, runsKernel); |
||||
else |
||||
erode_rle(runsSource, runsEroded, runsKernel); |
||||
dilate_rle(runsEroded, runsDestination, runsKernel); |
||||
} |
||||
break; |
||||
case MORPH_CLOSE: |
||||
{ |
||||
rlVec runsDilated; |
||||
|
||||
dilate_rle(runsSource, runsDilated, runsKernel); |
||||
if (bBoundaryOnForErosion) |
||||
erode_with_boundary_rle(runsDilated, sizeSource.width, sizeSource.height, runsDestination, runsKernel); |
||||
else |
||||
erode_rle(runsDilated, runsDestination, runsKernel); |
||||
} |
||||
break; |
||||
case MORPH_GRADIENT: |
||||
{ |
||||
rlVec runsEroded, runsDilated; |
||||
if (bBoundaryOnForErosion) |
||||
erode_with_boundary_rle(runsSource, sizeSource.width, sizeSource.height, runsEroded, runsKernel); |
||||
else |
||||
erode_rle(runsSource, runsEroded, runsKernel); |
||||
dilate_rle(runsSource, runsDilated, runsKernel); |
||||
subtract_rle(runsDilated, runsEroded, runsDestination); |
||||
} |
||||
break; |
||||
case MORPH_TOPHAT: |
||||
{ |
||||
rlVec runsEroded, runsOpened; |
||||
|
||||
if (bBoundaryOnForErosion) |
||||
erode_with_boundary_rle(runsSource, sizeSource.width, sizeSource.height, runsEroded, runsKernel); |
||||
else |
||||
erode_rle(runsSource, runsEroded, runsKernel); |
||||
dilate_rle(runsEroded, runsOpened, runsKernel); |
||||
subtract_rle(runsSource, runsOpened, runsDestination); |
||||
} |
||||
break; |
||||
|
||||
case MORPH_BLACKHAT: |
||||
{ |
||||
rlVec runsClosed, runsDilated; |
||||
|
||||
dilate_rle(runsSource, runsDilated, runsKernel); |
||||
if (bBoundaryOnForErosion) |
||||
erode_with_boundary_rle(runsDilated, sizeSource.width, sizeSource.height, runsClosed, runsKernel); |
||||
else |
||||
erode_rle(runsDilated, runsClosed, runsKernel); |
||||
|
||||
subtract_rle(runsClosed, runsSource, runsDestination); |
||||
} |
||||
break; |
||||
default: |
||||
case MORPH_HITMISS: |
||||
CV_Error(CV_StsBadArg, "unknown or unsupported morphological operation"); |
||||
} |
||||
convertToOutputArray(runsDestination, sizeSource, rlDest); |
||||
} |
||||
} |
||||
|
||||
} |
||||
} //end of cv::ximgproc
|
||||
} //end of cv
|
@ -0,0 +1,249 @@ |
||||
// 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.
|
||||
#include "test_precomp.hpp" |
||||
#include "opencv2/ximgproc/run_length_morphology.hpp" |
||||
#include "opencv2/imgproc.hpp" |
||||
|
||||
namespace opencv_test { |
||||
namespace { |
||||
|
||||
const Size img_size(640, 480); |
||||
const int tile_size(20); |
||||
typedef tuple<cv::MorphTypes, int, int> RLMParams; |
||||
typedef tuple<cv::MorphTypes, int, int, int> RLMSParams; |
||||
|
||||
class RLTestBase |
||||
{ |
||||
public: |
||||
RLTestBase() { } |
||||
|
||||
protected: |
||||
std::vector<Mat> test_image; |
||||
std::vector<Mat> test_image_rle; |
||||
|
||||
void generateCheckerBoard(Mat& image); |
||||
void generateRandomImage(Mat& image); |
||||
|
||||
bool areImagesIdentical(Mat& pixelImage, Mat& rleImage); |
||||
bool arePixelImagesIdentical(Mat& image1, Mat& image2); |
||||
|
||||
void setUp_impl(); |
||||
}; |
||||
|
||||
void RLTestBase::generateCheckerBoard(Mat& image) |
||||
{ |
||||
image.create(img_size, CV_8UC1); |
||||
for (int iy = 0; iy < img_size.height; iy += tile_size) |
||||
{ |
||||
Range rowRange(iy, std::min(iy + tile_size, img_size.height)); |
||||
for (int ix = 0; ix < img_size.width; ix += tile_size) |
||||
{ |
||||
Range colRange(ix, std::min(ix + tile_size, img_size.width)); |
||||
Mat tile(image, rowRange, colRange); |
||||
bool bBright = ((iy + ix) % (2 * tile_size) == 0); |
||||
tile = (bBright ? Scalar(255) : Scalar(0)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void RLTestBase::generateRandomImage(Mat& image) |
||||
{ |
||||
image.create(img_size, CV_8UC1); |
||||
randu(image, Scalar::all(0), Scalar::all(255)); |
||||
} |
||||
|
||||
|
||||
void RLTestBase::setUp_impl() |
||||
{ |
||||
test_image.resize(2); |
||||
test_image_rle.resize(2); |
||||
generateCheckerBoard(test_image[0]); |
||||
rl::threshold(test_image[0], test_image_rle[0], 100.0, THRESH_BINARY); |
||||
|
||||
cv::Mat theRandom; |
||||
generateRandomImage(theRandom); |
||||
double dThreshold = 254.0; |
||||
cv::threshold(theRandom, test_image[1], dThreshold, 255.0, THRESH_BINARY); |
||||
|
||||
rl::threshold(theRandom, test_image_rle[1], dThreshold, THRESH_BINARY); |
||||
|
||||
} |
||||
|
||||
bool RLTestBase::areImagesIdentical(Mat& pixelImage, Mat& rleImage) |
||||
{ |
||||
cv::Mat rleConverted; |
||||
rleConverted = cv::Mat::zeros(pixelImage.rows, pixelImage.cols, CV_8UC1); |
||||
rl::paint(rleConverted, rleImage, Scalar(255.0)); |
||||
return arePixelImagesIdentical(pixelImage, rleConverted); |
||||
} |
||||
|
||||
bool RLTestBase::arePixelImagesIdentical(Mat& image1, Mat& image2) |
||||
{ |
||||
cv::Mat diff; |
||||
cv::absdiff(image1, image2, diff); |
||||
int nDiff = cv::countNonZero(diff); |
||||
return (nDiff == 0); |
||||
} |
||||
|
||||
|
||||
|
||||
class RL_Identical_Result_Simple : public RLTestBase, public ::testing::TestWithParam<RLMSParams> |
||||
{ |
||||
public: |
||||
RL_Identical_Result_Simple() { } |
||||
protected: |
||||
virtual void SetUp() { setUp_impl(); } |
||||
}; |
||||
|
||||
TEST_P(RL_Identical_Result_Simple, simple) |
||||
{ |
||||
Mat resPix, resRLE; |
||||
RLMSParams param = GetParam(); |
||||
cv::MorphTypes elementType = get<0>(param); |
||||
int nSize = get<1>(param); |
||||
int image = get<2>(param); |
||||
int op = get<3>(param); |
||||
Mat element = getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1), |
||||
Point(nSize, nSize)); |
||||
|
||||
|
||||
morphologyEx(test_image[image], resPix, op, element); |
||||
|
||||
Mat elementRLE = rl::getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1)); |
||||
rl::morphologyEx(test_image_rle[image], resRLE, op, elementRLE); |
||||
|
||||
ASSERT_TRUE(areImagesIdentical(resPix, resRLE)); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(TypicalSET, RL_Identical_Result_Simple, Combine(Values(MORPH_RECT, MORPH_CROSS, MORPH_ELLIPSE), |
||||
Values(1, 5, 11), Values(0, 1), Values(MORPH_ERODE, MORPH_DILATE, MORPH_OPEN, MORPH_CLOSE, MORPH_GRADIENT, MORPH_TOPHAT, MORPH_BLACKHAT))); |
||||
|
||||
|
||||
class RL_Identical_Result : public RLTestBase, public ::testing::TestWithParam<RLMParams> |
||||
{ |
||||
public: |
||||
RL_Identical_Result() { } |
||||
protected: |
||||
virtual void SetUp() { setUp_impl(); } |
||||
}; |
||||
|
||||
|
||||
TEST_P(RL_Identical_Result, erosion_no_boundary) |
||||
{ |
||||
Mat resPix, resRLE; |
||||
RLMParams param = GetParam(); |
||||
cv::MorphTypes elementType = get<0>(param); |
||||
int nSize = get<1>(param); |
||||
int image = get<2>(param); |
||||
Mat element = getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1), |
||||
Point(nSize, nSize)); |
||||
|
||||
erode(test_image[image], resPix, element, cv::Point(-1,-1), 1, BORDER_CONSTANT, cv::Scalar(0)); |
||||
|
||||
Mat elementRLE = rl::getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1)); |
||||
rl::erode(test_image_rle[image], resRLE, elementRLE, false); |
||||
|
||||
ASSERT_TRUE(areImagesIdentical(resPix, resRLE)); |
||||
} |
||||
|
||||
TEST_P(RL_Identical_Result, erosion_with_offset) |
||||
{ |
||||
Mat resPix, resRLE; |
||||
RLMParams param = GetParam(); |
||||
cv::MorphTypes elementType = get<0>(param); |
||||
int nSize = get<1>(param); |
||||
int image = get<2>(param); |
||||
int nOffset = nSize - 1; |
||||
Mat element = getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1), |
||||
Point(nSize, nSize)); |
||||
|
||||
erode(test_image[image], resPix, element, cv::Point(nSize + nOffset, nSize + nOffset)); |
||||
|
||||
Mat elementRLE = rl::getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1)); |
||||
rl::erode(test_image_rle[image], resRLE, elementRLE, true, Point(nOffset, nOffset)); |
||||
|
||||
ASSERT_TRUE(areImagesIdentical(resPix, resRLE)); |
||||
} |
||||
|
||||
TEST_P(RL_Identical_Result, dilation_with_offset) |
||||
{ |
||||
Mat resPix, resRLE; |
||||
RLMParams param = GetParam(); |
||||
cv::MorphTypes elementType = get<0>(param); |
||||
int nSize = get<1>(param); |
||||
int image = get<2>(param); |
||||
int nOffset = nSize - 1; |
||||
Mat element = getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1), |
||||
Point(nSize, nSize)); |
||||
|
||||
dilate(test_image[image], resPix, element, cv::Point(nSize + nOffset, nSize + nOffset)); |
||||
|
||||
Mat elementRLE = rl::getStructuringElement(elementType, Size(nSize * 2 + 1, nSize * 2 + 1)); |
||||
rl::dilate(test_image_rle[image], resRLE, elementRLE, Point(nOffset, nOffset)); |
||||
|
||||
ASSERT_TRUE(areImagesIdentical(resPix, resRLE)); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(TypicalSET, RL_Identical_Result, Combine(Values(MORPH_RECT, MORPH_CROSS, MORPH_ELLIPSE), Values(1,5,11), Values(0,1))); |
||||
|
||||
class RL_CreateCustomKernel : public RLTestBase, public testing::Test |
||||
{ |
||||
public: |
||||
RL_CreateCustomKernel() { } |
||||
protected: |
||||
virtual void SetUp() { setUp_impl(); } |
||||
}; |
||||
|
||||
|
||||
TEST_F(RL_CreateCustomKernel, check_valid) |
||||
{ |
||||
// create a diamond
|
||||
int nSize = 21; |
||||
std::vector<Point3i> runs; |
||||
for (int i = 0; i < nSize; ++i) |
||||
{ |
||||
runs.emplace_back(Point3i(-i, i, -nSize + i)); |
||||
runs.emplace_back(Point3i(-i, i, nSize - i)); |
||||
} |
||||
runs.emplace_back(Point3i(-nSize, nSize, 0)); |
||||
Mat kernel, dest; |
||||
rl::createRLEImage(runs, kernel); |
||||
ASSERT_TRUE(rl::isRLMorphologyPossible(kernel)); |
||||
|
||||
rl::erode(test_image_rle[0], dest, kernel); |
||||
//only one row means: no runs, all pixels off
|
||||
ASSERT_TRUE(dest.rows == 1); |
||||
} |
||||
|
||||
typedef tuple<int> RLPParams; |
||||
|
||||
class RL_Paint : public RLTestBase, public ::testing::TestWithParam<RLPParams> |
||||
{ |
||||
public: |
||||
RL_Paint() { } |
||||
protected: |
||||
virtual void SetUp() { setUp_impl(); } |
||||
}; |
||||
|
||||
TEST_P(RL_Paint, same_result) |
||||
{ |
||||
Mat converted, pixBinary, painted; |
||||
RLPParams param = GetParam(); |
||||
int rType = get<0>(param); |
||||
|
||||
double dThreshold = 100.0; |
||||
double dMaxValue = 105.0; |
||||
test_image[1].convertTo(converted, rType); |
||||
cv::threshold(converted, pixBinary, dThreshold, dMaxValue, THRESH_BINARY); |
||||
|
||||
painted.create(test_image[1].rows, test_image[1].cols, rType); |
||||
painted = cv::Scalar(0.0); |
||||
rl::paint(painted, test_image_rle[1], Scalar(dMaxValue)); |
||||
ASSERT_TRUE(arePixelImagesIdentical(pixBinary, painted)); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(TypicalSET, RL_Paint, Values(CV_8U, CV_16U, CV_16S, CV_32F, CV_64F)); |
||||
|
||||
} |
||||
} |
Loading…
Reference in new issue