Skip to content

Commit 496d6de

Browse files
ximgproc: add hole filling for run-length encoded images
1 parent 5ed0a37 commit 496d6de

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed

modules/ximgproc/include/opencv2/ximgproc/run_length_morphology.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ CV_EXPORTS void createRLEImage(const std::vector<cv::Point3i>& runs, OutputArray
113113
CV_EXPORTS void morphologyEx(InputArray rlSrc, OutputArray rlDest, int op, InputArray rlKernel,
114114
bool bBoundaryOnForErosion = true, Point anchor = Point(0,0));
115115

116+
/**
117+
* @brief Fills holes in a run-length encoded binary image
118+
*
119+
* The function fills holes in the input binary image. A hole is defined as a background region surrounded
120+
* by connected foreground pixels.
121+
*
122+
* @param rlSrc input run-length encoded binary image
123+
* @param rlDest output run-length encoded image with holes filled
124+
* @param connectivity connectivity for hole filling, either 4 or 8 (default: 8)
125+
*
126+
*/
127+
CV_EXPORTS void fillHoles(InputArray rlSrc, OutputArray rlDest, int connectivity = 8);
128+
116129
//! @}
117130

118131
}

modules/ximgproc/src/run_length_morphology.cpp

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "precomp.hpp"
3737
#include <math.h>
3838
#include <vector>
39+
#include <stack>
3940
#include <iostream>
4041

4142

@@ -807,6 +808,150 @@ CV_EXPORTS void morphologyEx(InputArray rlSrc, OutputArray rlDest, int op, Input
807808
}
808809
}
809810

811+
static void getNeighborLookup(std::vector<int>& lutNeighbor, int connectivity, const rlVec& runs, const std::vector<int>& pIdxChord1, const std::vector<int>& pIdxNextRow, int emptyValue)
812+
{
813+
CV_Assert(connectivity == 8 || connectivity == 4);
814+
CV_Assert(lutNeighbor.size() != 0 && lutNeighbor.size() == 4u * runs.size());
815+
CV_Assert(runs.back().r - runs[0].r + 1 == (int)pIdxChord1.size() && pIdxChord1.size() == pIdxNextRow.size());
816+
const int connectionOffset = (connectivity == 4) ? 1 : 0;
817+
int nRows = (int)pIdxChord1.size();
818+
for (int i = 0; i < nRows - 1; ++i)
819+
{
820+
int firstRowStart = pIdxChord1[i];
821+
int secondRowStart = pIdxChord1[i + 1];
822+
int firstRowEnd = pIdxNextRow[i] - 1;
823+
int secondRowEnd = pIdxNextRow[i + 1] - 1;
824+
if (firstRowStart != emptyValue && secondRowStart != emptyValue)
825+
{
826+
while (firstRowStart <= firstRowEnd && secondRowStart <= secondRowEnd)
827+
{
828+
const rlType& first = runs[firstRowStart];
829+
const rlType& second = runs[secondRowStart];
830+
// Compare runs between two consecutive rows sequentially
831+
if ((first.ce + connectionOffset < second.cb))
832+
{
833+
firstRowStart++;
834+
}
835+
else if ((first.cb > second.ce + connectionOffset))
836+
{
837+
secondRowStart++;
838+
}
839+
else
840+
{
841+
// Each run stores indices of its 4-connected neighbors in lutNeighbor:
842+
// [0] = Top-leftmost neighbor
843+
// [1] = Top-rightmost neighbor
844+
// [2] = Bottom-leftmost neighbor
845+
// [3] = Bottom-rightmost neighbor
846+
lutNeighbor[firstRowStart * 4 + 3] = secondRowStart;
847+
lutNeighbor[secondRowStart * 4 + 1] = firstRowStart;
848+
if (lutNeighbor[firstRowStart * 4 + 2] == emptyValue)
849+
{
850+
lutNeighbor[firstRowStart * 4 + 2] = secondRowStart;
851+
}
852+
if (lutNeighbor[secondRowStart * 4] == emptyValue)
853+
{
854+
lutNeighbor[secondRowStart * 4] = firstRowStart;
855+
}
856+
if ((first.ce == second.ce))
857+
{
858+
firstRowStart++;
859+
secondRowStart++;
860+
}
861+
else if ((first.ce > second.ce))
862+
{
863+
secondRowStart++;
864+
}
865+
else
866+
{
867+
firstRowStart++;
868+
}
869+
}
870+
}
871+
}
872+
}
873+
}
874+
875+
// @author Yu Changming, [email protected], https://github.com/yuchangminghit
876+
CV_EXPORTS void fillHoles(InputArray rlSrc, OutputArray rlDest, int connectivity)
877+
{
878+
CV_Assert(connectivity == 8 || connectivity == 4);
879+
rlVec runsSource, runsDestination;
880+
Size sizeSource;
881+
convertInputArrayToRuns(rlSrc, runsSource, sizeSource);
882+
if ((int)runsSource.size() <= 3)
883+
{
884+
convertToOutputArray(runsSource, sizeSource, rlDest);
885+
return;
886+
}
887+
rlVec borderRect, backgroundRuns;
888+
cv::Rect boundingRect = getBoundingRectangle(runsSource);
889+
if (boundingRect.width < 3 || boundingRect.height < 3)
890+
{
891+
convertToOutputArray(runsSource, sizeSource, rlDest);
892+
return;
893+
}
894+
createUprightRectangle(cv::Rect(boundingRect.x - 1, boundingRect.y - 1, boundingRect.width + 2, boundingRect.height + 2), borderRect);
895+
subtract_rle(borderRect, runsSource, backgroundRuns);
896+
int nMinRow = backgroundRuns[0].r;
897+
int nMaxRow = backgroundRuns.back().r;
898+
int nRows = nMaxRow - nMinRow + 1;
899+
const int EMPTY = -1;
900+
std::vector<int> pIdxChord1(nRows, EMPTY);
901+
std::vector<int> pIdxNextRow(nRows, EMPTY);
902+
pIdxChord1[0] = 0;
903+
pIdxNextRow[nRows - 1] = (int)backgroundRuns.size();
904+
for (int i = 1; i < (int)backgroundRuns.size(); i++) {
905+
if (backgroundRuns[i].r != backgroundRuns[i - 1].r) {
906+
pIdxChord1[backgroundRuns[i].r - nMinRow] = i;
907+
pIdxNextRow[backgroundRuns[i - 1].r - nMinRow] = i;
908+
}
909+
}
910+
std::vector<int> neighborLUT(4u * backgroundRuns.size(), EMPTY);
911+
getNeighborLookup(neighborLUT, connectivity, backgroundRuns, pIdxChord1, pIdxNextRow, EMPTY);
912+
// Stack stores run indices to be processed
913+
std::stack<int> indexStack;
914+
indexStack.push(0);
915+
// Flags for each run:
916+
// 0: Run is adjacent to the outer background (not part of a hole)
917+
// 255: Run is isolated from the outer background (part of a hole region)
918+
std::vector<uchar> selectedFlags(backgroundRuns.size(), EMPTY);
919+
const int SELECTED = 0;
920+
selectedFlags[0] = SELECTED;
921+
const int neighborOffsets[] = { 0, 2 };
922+
while (!indexStack.empty())
923+
{
924+
int currentIdx = indexStack.top();
925+
indexStack.pop();
926+
for (int i = 0; i < 2; i++)
927+
{
928+
if (neighborLUT[4 * currentIdx + neighborOffsets[i]] != EMPTY)
929+
{
930+
int startIdx = neighborLUT[4 * currentIdx + neighborOffsets[i]];
931+
int endIdx = neighborLUT[4 * currentIdx + neighborOffsets[i] + 1];
932+
for (int nbrIdx = startIdx; nbrIdx <= endIdx; ++nbrIdx)
933+
{
934+
if (selectedFlags[nbrIdx] != SELECTED)
935+
{
936+
selectedFlags[nbrIdx] = SELECTED;
937+
indexStack.push(nbrIdx);
938+
}
939+
}
940+
}
941+
}
942+
}
943+
rlVec holesBackground;
944+
for (int i = 0; i < (int)selectedFlags.size(); i++)
945+
{
946+
if (selectedFlags[i] == SELECTED)
947+
{
948+
holesBackground.push_back(backgroundRuns[i]);
949+
}
950+
}
951+
invertRegion(holesBackground, runsDestination);
952+
convertToOutputArray(runsDestination, sizeSource, rlDest);
953+
}
954+
810955
}
811956
} //end of cv::ximgproc
812957
} //end of cv

modules/ximgproc/test/test_run_length_morphology.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,5 +245,31 @@ TEST_P(RL_Paint, same_result)
245245

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

248+
static void getRuns(std::vector<cv::Point3i>& runs, const cv::Mat& rleImage)
249+
{
250+
runs.clear();
251+
cv::Point3i* ptr = (cv::Point3i*)rleImage.ptr(0);
252+
for (int i = 1; i < rleImage.rows; i++)
253+
{
254+
runs.push_back(ptr[i]);
255+
}
256+
}
257+
258+
TEST(RL_FILL_HOLES, accuracyFillHoles)
259+
{
260+
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) };
261+
std::vector<Point3i> runsDest8 = { Point3i(1,4,0),Point3i(0,4,1),Point3i(1,4,2), Point3i(2,4,3), Point3i(3,3,4) };
262+
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) };
263+
Mat rleSrc, rleDest8, rleDest4;
264+
rl::createRLEImage(runsSrc, rleSrc);
265+
rl::fillHoles(rleSrc, rleDest8, 8);
266+
rl::fillHoles(rleSrc, rleDest4, 4);
267+
std::vector<cv::Point3i> runsDest8Get, runsDest4Get;
268+
getRuns(runsDest8Get, rleDest8);
269+
getRuns(runsDest4Get, rleDest4);
270+
EXPECT_EQ(runsDest8Get, runsDest8);
271+
EXPECT_EQ(runsDest4Get, runsDest4);
272+
}
273+
248274
}
249275
}

0 commit comments

Comments
 (0)