Commit 0f5d6ae1 authored by Dietrich Büsching's avatar Dietrich Büsching Committed by Alexander Alekhin

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 review
parent 729737dd
......@@ -15,3 +15,4 @@ Extended Image Processing
- Deriche Filter
- Pei&Lin Normalization
- Ridge Detection Filter
- Binary morphology on run-length encoded images
......@@ -288,3 +288,13 @@
year={1977},
publisher={IEEE Computer Society}
}
@inproceedings{Breuel2008,
title = {Binary Morphology and Related Operations on Run-Length Representations.},
author = {Breuel, Thomas},
year = {2008},
month = {01},
pages = {159-166},
volume = {1},
booktitle = {VISAPP 2008 - 3rd International Conference on Computer Vision Theory and Applications, Proceedings}
}
......@@ -56,6 +56,7 @@
#include "ximgproc/fourier_descriptors.hpp"
#include "ximgproc/ridgefilter.hpp"
#include "ximgproc/brightedges.hpp"
#include "ximgproc/run_length_morphology.hpp"
/** @defgroup ximgproc Extended Image Processing
......@@ -76,6 +77,26 @@ i.e. algorithms which somehow takes into account pixel affinities in natural ima
@defgroup ximgproc_fast_line_detector Fast line detector
@defgroup ximgproc_fourier Fourier descriptors
@defgroup ximgproc_run_length_morphology Binary morphology on run-length encoded image
These functions support morphological operations on binary images. In order to be fast and space efficient binary images are encoded with a run-length representation.
This representation groups continuous horizontal sequences of "on" pixels together in a "run". A run is charactarized by the column position of the first pixel in the run, the column
position of the last pixel in the run and the row position. This representation is very compact for binary images which contain large continuous areas of "on" and "off" pixels. A checkerboard
pattern would be a good example. The representation is not so suitable for binary images created from random noise images or other images where little correlation between neighboring pixels
exists.
The morphological operations supported here are very similar to the operations supported in the imgproc module. In general they are fast. However on several occasions they are slower than the functions
from imgproc. The structuring elements of cv::MORPH_RECT and cv::MORPH_CROSS have very good support from the imgproc module. Also small structuring elements are very fast in imgproc (presumably
due to opencl support). Therefore the functions from this module are recommended for larger structuring elements (cv::MORPH_ELLIPSE or self defined structuring elements). A sample application
(run_length_morphology_demo) is supplied which allows to compare the speed of some morphological operations for the functions using run-length encoding and the imgproc functions for a given image.
Run length encoded images are stored in standard opencv images. Images have a single column of cv::Point3i elements. The number of rows is the number of run + 1. The first row contains
the size of the original (not encoded) image. For the runs the following mapping is used (x: column begin, y: column end (last column), z: row).
The size of the original image is required for compatiblity with the imgproc functions when the boundary handling requires that pixel outside the image boundary are
"on".
@}
*/
......
// 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
// 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
\ No newline at end of file
#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;
}
\ No newline at end of file
/*
* 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
// 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));
}
}
\ No newline at end of file
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