对二值图进行细化(骨架提取),也就是把每根线条细化到一个像素的宽度。有两个比较成熟的算法实现此功能,分别是Zhang-Suen算法和Guo-Hall算法。
我们下面使用OpenCVSharp,使用C#实现上述两个算法:
private static Mat ThinningIteration(Mat img, int iter, int thinningType)
{
Mat marker = Mat.Zeros(img.Size(), MatType.CV_8UC1);
int rows = img.Rows;
int cols = img.Cols;
marker.Col(0).SetTo(1);
marker.Col(cols - 1).SetTo(1);
marker.Row(0).SetTo(1);
marker.Row(rows - 1).SetTo(1);
//THINNING_ZHANGSUEN
if (thinningType == 1)
{
marker.ForEachAsByte((byte* value, int* position) =>
{
int i = position[0];
int j = position[1];
if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1)
return;
var ptr = (byte*)img.Ptr(i, j).ToPointer();
// p9 p2 p3
// p8 p1 p4
// p7 p6 p5
byte p2 = ptr[-cols];
byte p3 = ptr[-cols + 1];
byte p4 = ptr[1];
byte p5 = ptr[cols + 1];
byte p6 = ptr[cols];
byte p7 = ptr[cols - 1];
byte p8 = ptr[-1];
byte p9 = ptr[-cols - 1];
int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7);
if (iter == 0)
*value = lut_zhang_iter0[neighbors];
else
*value = lut_zhang_iter1[neighbors];
});
}
//THINNING_GUOHALL
else if (thinningType == 2)
{
marker.ForEachAsByte((byte* value, int* position) =>
{
int i = position[0];
int j = position[1];
if (i == 0 || j == 0 || i == rows - 1 || j == cols - 1)
return;
var ptr = (byte*)img.Ptr(i, j).ToPointer();
// p9 p2 p3
// p8 p1 p4
// p7 p6 p5
byte p2 = ptr[-cols];
byte p3 = ptr[-cols + 1];
byte p4 = ptr[1];
byte p5 = ptr[cols + 1];
byte p6 = ptr[cols];
byte p7 = ptr[cols - 1];
byte p8 = ptr[-1];
byte p9 = ptr[-cols - 1];
int neighbors = p9 | (p2 << 1) | (p3 << 2) | (p4 << 3) | (p5 << 4) | (p6 << 5) | (p7 << 6) | (p8 << 7);
if (iter == 0)
*value = lut_guo_iter0[neighbors];
else
*value = lut_guo_iter1[neighbors];
});
}
img &= marker;
return img;
}
public static void Thinning(InputArray input, OutputArray output, int thinningType)
{
Mat processed = input.GetMat().Clone();
processed /= 255;
Mat prev = processed.Clone();
Mat diff = new Mat();
do
{
processed = ThinningIteration(processed, 0, thinningType);
processed = ThinningIteration(processed, 1, thinningType);
Cv2.Absdiff(processed, prev, diff);
if (diff.CountNonZero() == 0) break;
processed.CopyTo(prev);
}
while (true);
processed *= 255;
processed.CopyTo(output);
}
我们的测试框图如下图所示:
原二值图如下图所示:
细化(提取骨骼)之后的结果图如下:
Zhang-Suen算法
Guo-Hall算法