/* 对于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); }