Skip to content

ximgproc: add hole filling for run-length encoded images #3972

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: 4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ CV_EXPORTS void createRLEImage(const std::vector<cv::Point3i>& runs, OutputArray
CV_EXPORTS void morphologyEx(InputArray rlSrc, OutputArray rlDest, int op, InputArray rlKernel,
bool bBoundaryOnForErosion = true, Point anchor = Point(0,0));

/**
* @brief Fills holes in a run-length encoded binary image
*
* The function fills holes in the input binary image. A hole is defined as a background region surrounded
* by connected foreground pixels.
*
* @param rlSrc input run-length encoded binary image
* @param rlDest output run-length encoded image with holes filled
* @param connectivity connectivity for hole filling, either 4 or 8 (default: 8)
*
*/
CV_EXPORTS void fillHoles(InputArray rlSrc, OutputArray rlDest, int connectivity = 8);

//! @}

}
Expand Down
145 changes: 145 additions & 0 deletions modules/ximgproc/src/run_length_morphology.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "precomp.hpp"
#include <math.h>
#include <vector>
#include <stack>
#include <iostream>


Expand Down Expand Up @@ -807,6 +808,150 @@ CV_EXPORTS void morphologyEx(InputArray rlSrc, OutputArray rlDest, int op, Input
}
}

static void getNeighborLookup(std::vector<int>& lutNeighbor, int connectivity, const rlVec& runs, const std::vector<int>& pIdxChord1, const std::vector<int>& pIdxNextRow, int emptyValue)
{
CV_Assert(connectivity == 8 || connectivity == 4);
CV_Assert(lutNeighbor.size() != 0 && lutNeighbor.size() == 4u * runs.size());
CV_Assert(runs.back().r - runs[0].r + 1 == (int)pIdxChord1.size() && pIdxChord1.size() == pIdxNextRow.size());
const int connectionOffset = (connectivity == 4) ? 1 : 0;
int nRows = (int)pIdxChord1.size();
for (int i = 0; i < nRows - 1; ++i)
{
int firstRowStart = pIdxChord1[i];
int secondRowStart = pIdxChord1[i + 1];
int firstRowEnd = pIdxNextRow[i] - 1;
int secondRowEnd = pIdxNextRow[i + 1] - 1;
if (firstRowStart != emptyValue && secondRowStart != emptyValue)
{
while (firstRowStart <= firstRowEnd && secondRowStart <= secondRowEnd)
{
const rlType& first = runs[firstRowStart];
const rlType& second = runs[secondRowStart];
// Compare runs between two consecutive rows sequentially
if ((first.ce + connectionOffset < second.cb))
{
firstRowStart++;
}
else if ((first.cb > second.ce + connectionOffset))
{
secondRowStart++;
}
else
{
// Each run stores indices of its 4-connected neighbors in lutNeighbor:
// [0] = Top-leftmost neighbor
// [1] = Top-rightmost neighbor
// [2] = Bottom-leftmost neighbor
// [3] = Bottom-rightmost neighbor
lutNeighbor[firstRowStart * 4 + 3] = secondRowStart;
lutNeighbor[secondRowStart * 4 + 1] = firstRowStart;
if (lutNeighbor[firstRowStart * 4 + 2] == emptyValue)
{
lutNeighbor[firstRowStart * 4 + 2] = secondRowStart;
}
if (lutNeighbor[secondRowStart * 4] == emptyValue)
{
lutNeighbor[secondRowStart * 4] = firstRowStart;
}
if ((first.ce == second.ce))
{
firstRowStart++;
secondRowStart++;
}
else if ((first.ce > second.ce))
{
secondRowStart++;
}
else
{
firstRowStart++;
}
}
}
}
}
}

// @author Yu Changming, [email protected], https://github.com/yuchangminghit
CV_EXPORTS void fillHoles(InputArray rlSrc, OutputArray rlDest, int connectivity)
{
CV_Assert(connectivity == 8 || connectivity == 4);
rlVec runsSource, runsDestination;
Size sizeSource;
convertInputArrayToRuns(rlSrc, runsSource, sizeSource);
if ((int)runsSource.size() <= 3)
{
convertToOutputArray(runsSource, sizeSource, rlDest);
return;
}
rlVec borderRect, backgroundRuns;
cv::Rect boundingRect = getBoundingRectangle(runsSource);
if (boundingRect.width < 3 || boundingRect.height < 3)
{
convertToOutputArray(runsSource, sizeSource, rlDest);
return;
}
createUprightRectangle(cv::Rect(boundingRect.x - 1, boundingRect.y - 1, boundingRect.width + 2, boundingRect.height + 2), borderRect);
subtract_rle(borderRect, runsSource, backgroundRuns);
int nMinRow = backgroundRuns[0].r;
int nMaxRow = backgroundRuns.back().r;
int nRows = nMaxRow - nMinRow + 1;
const int EMPTY = -1;
std::vector<int> pIdxChord1(nRows, EMPTY);
std::vector<int> pIdxNextRow(nRows, EMPTY);
pIdxChord1[0] = 0;
pIdxNextRow[nRows - 1] = (int)backgroundRuns.size();
for (int i = 1; i < (int)backgroundRuns.size(); i++) {
if (backgroundRuns[i].r != backgroundRuns[i - 1].r) {
pIdxChord1[backgroundRuns[i].r - nMinRow] = i;
pIdxNextRow[backgroundRuns[i - 1].r - nMinRow] = i;
}
}
std::vector<int> neighborLUT(4u * backgroundRuns.size(), EMPTY);
getNeighborLookup(neighborLUT, connectivity, backgroundRuns, pIdxChord1, pIdxNextRow, EMPTY);
// Stack stores run indices to be processed
std::stack<int> indexStack;
indexStack.push(0);
// Flags for each run:
// 0: Run is adjacent to the outer background (not part of a hole)
// 255: Run is isolated from the outer background (part of a hole region)
std::vector<uchar> selectedFlags(backgroundRuns.size(), (uchar)255);
const int SELECTED = 0;
selectedFlags[0] = SELECTED;
const int neighborOffsets[] = { 0, 2 };
while (!indexStack.empty())
{
int currentIdx = indexStack.top();
indexStack.pop();
for (int i = 0; i < 2; i++)
{
if (neighborLUT[4 * currentIdx + neighborOffsets[i]] != EMPTY)
{
int startIdx = neighborLUT[4 * currentIdx + neighborOffsets[i]];
int endIdx = neighborLUT[4 * currentIdx + neighborOffsets[i] + 1];
for (int nbrIdx = startIdx; nbrIdx <= endIdx; ++nbrIdx)
{
if (selectedFlags[nbrIdx] != SELECTED)
{
selectedFlags[nbrIdx] = SELECTED;
indexStack.push(nbrIdx);
}
}
}
}
}
rlVec holesBackground;
for (int i = 0; i < (int)selectedFlags.size(); i++)
{
if (selectedFlags[i] == SELECTED)
{
holesBackground.push_back(backgroundRuns[i]);
}
}
invertRegion(holesBackground, runsDestination);
convertToOutputArray(runsDestination, sizeSource, rlDest);
}

}
} //end of cv::ximgproc
} //end of cv
26 changes: 26 additions & 0 deletions modules/ximgproc/test/test_run_length_morphology.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,5 +245,31 @@ TEST_P(RL_Paint, same_result)

INSTANTIATE_TEST_CASE_P(TypicalSET, RL_Paint, Values(CV_8U, CV_16U, CV_16S, CV_32F, CV_64F));

static void getRuns(std::vector<cv::Point3i>& runs, const cv::Mat& rleImage)
{
runs.clear();
cv::Point3i* ptr = (cv::Point3i*)rleImage.ptr(0);
for (int i = 1; i < rleImage.rows; i++)
{
runs.push_back(ptr[i]);
}
}

TEST(RL_FILL_HOLES, accuracyFillHoles)
{
std::vector<Point3i> runsSrc = { Point3i(1,4,0),Point3i(0,0,1),Point3i(2,2,1), Point3i(4,4,1), Point3i(1,4,2), Point3i(2,2,3), Point3i(4,4,3), Point3i(3,3,4) };
std::vector<Point3i> runsDest8 = { Point3i(1,4,0),Point3i(0,4,1),Point3i(1,4,2), Point3i(2,4,3), Point3i(3,3,4) };
std::vector<Point3i> runsDest4 = { Point3i(1,4,0),Point3i(0,0,1),Point3i(2,4,1), Point3i(1,4,2), Point3i(2,2,3), Point3i(4,4,3), Point3i(3,3,4) };
Mat rleSrc, rleDest8, rleDest4;
rl::createRLEImage(runsSrc, rleSrc);
rl::fillHoles(rleSrc, rleDest8, 8);
rl::fillHoles(rleSrc, rleDest4, 4);
std::vector<cv::Point3i> runsDest8Get, runsDest4Get;
getRuns(runsDest8Get, rleDest8);
getRuns(runsDest4Get, rleDest4);
EXPECT_EQ(runsDest8Get, runsDest8);
EXPECT_EQ(runsDest4Get, runsDest4);
}

}
}
Loading