二值图像求取连通域算法
一幅图像二值化处理后往往包含多个区域,需要通过标记把它们分别提取出来。标记分割后图像中各区域的简单而有效的方法是检查各像素与其相邻像素的连通性。
在二值图像中,背景区像素的值为0,目标区域的像素值为1。假设对一幅图像从左向右,从上向下进行扫描,要标记当前正被扫描的像素需要检查它与在它之前被扫描到的若干个近邻像素的连通性。
考虑4连通的情形。对图像进行逐像素扫描。
假如当前像素值为0,就移动到下一个扫描的位置。
假如当前像素值为1,检查它左边和上边的两个邻接像素(这两个像素一定会在当前像素之前被扫描到)。这两个像素值和标记的组合有四种情况要考虑。
-
他们的像素值都为0。此时给该像素一个新的标记(表示一个新的连通域的开始)。
-
它们中间只有一个像素值为1。此时当前像素的标记=为1的像素值的标记。
-
它们的像素值都为1且标记相同。此时当前像素的标记=该标记。
-
它们的像素值为1且标记不同。将其中的较小的值赋给当前像素。之后从另一边回溯到区域的开始像素为止。每次回溯再分别执行上述四个判断步骤。
这样即可保证所有的连通域都被标记出来。之后再通过对不同的标记赋予不同的颜色或将其加上边框即可完成标记。
1 /// <summary> 2 /// 回溯法标记连通域 3 /// </summary> 4 /// <param name="x">该点的横坐标</param> 5 /// <param name="y">该点的纵坐标</param> 6 /// <param name="isMarked">是否已经被标记过,用于记录回溯路线。默认值为false,如果该点已经被标记过,则应指定该参数为true。</param> 7 private void Connect(int x, int y, bool isMarked = false) 8 { 9 if (x == 0 && y == 0) //mat[0, 0] 10 { 11 if (f(x, y) == 1) mat[x, y] = mark; // new area 12 } 13 14 else if (x != 0 && y == 0) // First Row 15 { 16 if (f(x, y) == 1) 17 { 18 if (mat[x - 1, y] != 0) 19 { 20 mat[x, y] = mat[x - 1, y]; // left one 21 Connect(x - 1, y, true); 22 } 23 else 24 { 25 if (isMarked == false) 26 mat[x, y] = ++mark; // new area 27 } 28 } 29 } 30 31 else if (x == 0 && y != 0) // First Column 32 { 33 if (f(x, y) == 1) 34 { 35 if (mat[x, y - 1] != 0) 36 { 37 mat[x, y] = mat[x, y - 1]; // up one 38 Connect(x, y - 1, true); 39 } 40 else 41 { 42 if (isMarked == false) 43 mat[x, y] = ++mark; 44 } 45 } 46 } 47 48 else if (x != 0 && y != 0) // other pixel 49 { 50 if (f(x, y) == 1) 51 { 52 if (mat[x, y - 1] == 0 && mat[x - 1, y] == 0) // new area 53 { 54 if (isMarked == false) 55 mat[x, y] = ++mark; 56 } 57 else if (mat[x, y - 1] == 0 && mat[x - 1, y] != 0) 58 { 59 if (isMarked == false) 60 mat[x, y] = mat[x - 1, y]; 61 else 62 { 63 if (mat[x - 1, y] > mat[x, y]) 64 mat[x - 1, y] = mat[x, y]; 65 Connect(x - 1, y, true); // 沿x方向继续回溯 66 } 67 } 68 else if (mat[x, y - 1] != 0 && mat[x - 1, y] == 0) 69 { 70 if (isMarked == false) 71 mat[x, y] = mat[x, y - 1]; 72 else 73 { 74 if (mat[x, y - 1] > mat[x, y]) 75 mat[x, y - 1] = mat[x, y]; 76 Connect(x, y - 1, true); // 沿y方向继续回溯 77 } 78 } 79 else if (mat[x, y - 1] != 0 && mat[x - 1, y] != 0 && mat[x, y - 1] == mat[x - 1, y]) 80 { 81 if (isMarked == false) 82 mat[x, y] = mat[x, y - 1]; 83 else 84 { 85 if (mat[x, y - 1] > mat[x, y]) 86 { 87 mat[x, y - 1] = mat[x - 1, y] = mat[x, y]; 88 Connect(x - 1, y, true); // 遇到上边和左边都有已标记像素的情况,两边同时回溯 89 Connect(x, y - 1, true); 90 } 91 } 92 93 } 94 else if (mat[x, y - 1] != 0 && mat[x - 1, y] != 0 && mat[x, y - 1] != mat[x - 1, y]) 95 { 96 mat[x, y] = Math.Min(mat[x - 1, y], mat[x, y - 1]); 97 mat[x - 1, y] = mat[x, y - 1] = mat[x, y]; // 直接消除等价类 98 Connect(x - 1, y, true); 99 Connect(x, y - 1, true); 100 } 101 } 102 }
执行效果如下:
二值化后的图像
标记了连通域后的图像
当然这个方法的一个问题是执行效率很低,对比较大的图片需要较长时间才能完成标记步骤。但准确率还是比较高的。
参考文献:
[1] 章毓晋. 图像分割. 科学出版社,2001年,pp.63
[2] R.Gonzalez. 数字图像处理. 电子工业出版社, 2014年, pp.38-40