/* 对于m列n行的图像,我们从左向右,从上向下遍历每一个像素
* ①标签序号label从-1开始。
* ②如果当前像素为1
* i)左边和上边像素均为0,则直接label加1,设置当前像素对应的label值为当前label值
* ii)左边或上边有一个像素为1时, 当前像素对应的label值设置为左边或上边对应的label值
* iii)左边和上边都为有效像素时,取二者对应的label值中较小的那个值赋值给当前像素对应的label值
同时合并label较大的值。
* ③最后再从左到右,从上到下,找到当前像素对应label的根节点
*/
ushort get_root(ushort index, const ushort* map)
{
ushort i = index;
while (map[i] != (ushort)(-1))
{
i = map[i];
}
return i;
}
void union_region(ushort min, ushort max, ushort* map)
{
if (min == max)
return;
ushort root1 = get_root(min, map);
ushort root2 = get_root(max, map);
if (root1 < root2)
{
map[max] = root1;
}
else
{
map[max] = root2;
}
}
//只支持二值图,即src为二值图像
cv::Mat MarkConnDomainWithTwoPass(cv::Mat& src)
{
int type = src.type();
CV_Assert(src.type() == CV_8UC1);
//每个像素存储对应的label索引,索引从0开始,-1表示没有对应的label
cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_16UC1);
//存储父子label的关系,
//子label存储的值对应父label的index,根label值为0
ushort *pMap = new ushort[src.rows * src.cols];
memset(pMap, -1, src.rows * src.cols * sizeof(ushort));
int label = -1;
//第一遍标记
for (int y = 0; y < src.rows; y++)
{
uchar* pCurCol = src.ptr<uchar>(y);
uchar* pPreCol = nullptr;
ushort * pCurDest = dst.ptr<ushort>(y);
ushort * pPreDest = nullptr;
if (y > 0)
{
pPreDest = dst.ptr<ushort>(y - 1);
pPreCol = src.ptr<uchar>(y - 1);
}
for (int x = 0; x < src.cols; x++)
{
if (pCurCol[x] != 0)
{
if (x == 0)
{
if (pPreCol == nullptr || pPreCol[x] == 0)
{
label++;
pCurDest[x] = label;
}
else
{
pCurDest[x] = pPreDest[x];
}
}
else if (y == 0)
{
if (x == 0 || pCurCol[x - 1] == 0)
{
label++;
pCurDest[x] = label;
}
else
{
pCurDest[x] = pCurDest[x - 1];
}
}
else if (pCurCol[x - 1] == 0 && pPreCol[x] == 0)
{
label++;
pCurDest[x] = label;
}
else if (pCurCol[x - 1] == 0 && pPreCol[x] == 1)
{
pCurDest[x] = pPreDest[x];
}
else if (pCurCol[x - 1] == 1 && pPreCol[x] == 0)
{
pCurDest[x] = pCurDest[x - 1];
}
else if (pCurCol[x - 1] == 1 && pPreCol[x] == 1)
{
if (pCurDest[x - 1] < pPreDest[x])
{
pCurDest[x] = pCurDest[x - 1];
union_region(pCurDest[x], pPreDest[x], pMap);
}
else
{
pCurDest[x] = pPreDest[x];
union_region(pCurDest[x], pCurDest[x - 1], pMap);
}
}
}
else
{
pCurDest[x] = -1;
}
}
}
//合并相连接的区域
for (int y = 0; y < dst.rows; y++)
{
ushort *pdst = dst.ptr<ushort>(y);
for (int x = 0; x < dst.cols; x++)
{
if (pdst[x] != -1)
{
pdst[x] = get_root(pdst[x], pMap);
}
}
}
if (pMap != nullptr)
{
delete[] pMap;
pMap = nullptr;
}
return dst;
}
void test_MarkConnDomainWithTwoPass()
{
cv::Mat mat = cv::imread("E:\VisualWorkPlace\00OpenCVworkplace\images\testUse.bmp", cv::IMREAD_GRAYSCALE);
//二值化处理
cv::Mat halfVal(mat.rows, mat.cols, mat.type());
for (int i = 0; i < mat.rows; i++)
{
uchar * p = halfVal.ptr<uchar>(i);
uchar * pp = mat.ptr<uchar>(i);
for (int j = 0; j < mat.cols; j++)
{
if (pp[j] == 0)
{
p[j] = 1;
}
else
{
p[j] = 0;
}
}
}
//two pass 标记连通区域
cv::Mat dst = MarkConnDomainWithTwoPass(halfVal);
std::ofstream ofout("test.log");
ofout << dst;
cv::waitKey(0);
}