二值图像骨架线提取

二值图像骨架线提取算法:HilditchThin算法、Rosenfeld算法、OpenCV_Contrib中的算法

HilditchThin算法

1、使用的8邻域标记为:
在这里插入图片描述2、下面看下它的算法描述:
复制目地图像到临时图像,对临时图像进行一次扫描,对于不为0的点,如果满足以下四个条件,则在目地图像中删除该点(就是设置该像素为0)

条件一:2<=p2+p3+p4+p5+p6+p7+p8+p9<=6
大于等于2会保证p1点不是端点或孤立点,因为删除端点和孤立点是不合理的,小于等于6保证p1点是一个边界点,而不是一个内部点。等于0时候,周围没有等于1的像素,所以p1为孤立点,等于1的时候,周围只有1个灰度等于1的像素,所以是端点(注:端点是周围有且只能有1个值为1的像素)。
在这里插入图片描述
条件二:p2->p9的排列顺序中,01模式的数量为1
比如下面的图中,有p2p3 => 01, p6p7=>01,所以该像素01模式的数量为2。
在这里插入图片描述
之所以要01模式数量为1,是要保证删除当前像素点后的连通性。比如下面的图中,01模式数量大于1,如果删除当前点p1,则连通性不能保证。
在这里插入图片描述
条件三:p2.p4.p8 = 0 or A(p2)!=1
A(p2)表示p2周围8邻域的01模式和。这个条件保证2个像素宽的垂直条不完全被腐蚀掉。
在这里插入图片描述
条件四:p2.p4.p6 = 0 or A(p4)!=1
A(p4)表示p4周围8邻域的01模式和。这个条件保证2个像素宽的水平条不完全被腐蚀掉。
在这里插入图片描述
如果图像中没有可以删除的点时,结束循环。
算法代码如下(输入归一化二值图像):

void HilditchThin(cv::Mat& src, cv::Mat& dst) //传入归一化图像src
{
    if(src.type()!=CV_8UC1)
    {
        printf("只能处理二值或灰度图像\n");
        return;
    }
    //非原地操作时候,copy src到dst
    if(dst.data!=src.data)
        src.copyTo(dst);
        
    int i, j;
    int width, height;
    //之所以减2,是方便处理8邻域,防止越界
    width = src.cols -2;
    height = src.rows -2;
    int step = src.step;
    int  p2,p3,p4,p5,p6,p7,p8,p9;
    uchar* img;
    bool ifEnd;
    int A1;
    cv::Mat tmpimg;
    while(1)
    {
        dst.copyTo(tmpimg);
        ifEnd = false;
        img = tmpimg.data+step;
        for(i = 2; i < height; i++)
        {
            img += step;
            for(j =2; j<width; j++)
            {
                uchar* p = img + j;
                A1 = 0;
                if( p[0] > 0)
                {
                    if(p[-step]==0&&p[-step+1]>0) //p2,p3 01模式
                    {
                        A1++;
                    }
                    if(p[-step+1]==0&&p[1]>0) //p3,p4 01模式
                    {
                        A1++;
                    }
                    if(p[1]==0&&p[step+1]>0) //p4,p5 01模式
                    {
                        A1++;
                    }
                    if(p[step+1]==0&&p[step]>0) //p5,p6 01模式
                    {
                        A1++;
                    }
                    if(p[step]==0&&p[step-1]>0) //p6,p7 01模式
                    {
                        A1++;
                    }
                    if(p[step-1]==0&&p[-1]>0) //p7,p8 01模式
                    {
                        A1++;
                    }
                    if(p[-1]==0&&p[-step-1]>0) //p8,p9 01模式
                    {
                        A1++;
                    }
                    if(p[-step-1]==0&&p[-step]>0) //p9,p2 01模式
                    {
                        A1++;
                    }
                    p2 = p[-step]>0?1:0;
                    p3 = p[-step+1]>0?1:0;
                    p4 = p[1]>0?1:0;
                    p5 = p[step+1]>0?1:0;
                    p6 = p[step]>0?1:0;
                    p7 = p[step-1]>0?1:0;
                    p8 = p[-1]>0?1:0;
                    p9 = p[-step-1]>0?1:0;
                    //计算AP2,AP4
                    int A2, A4;
                    A2 = 0;
                    //if(p[-step]>0)
                    {
                        if(p[-2*step]==0&&p[-2*step+1]>0) A2++;
                        if(p[-2*step+1]==0&&p[-step+1]>0) A2++;
                        if(p[-step+1]==0&&p[1]>0) A2++;
                        if(p[1]==0&&p[0]>0) A2++;
                        if(p[0]==0&&p[-1]>0) A2++;
                        if(p[-1]==0&&p[-step-1]>0) A2++;
                        if(p[-step-1]==0&&p[-2*step-1]>0) A2++;
                        if(p[-2*step-1]==0&&p[-2*step]>0) A2++;
                    }
 
 
                    A4 = 0;
                    //if(p[1]>0)
                    {
                        if(p[-step+1]==0&&p[-step+2]>0) A4++;
                        if(p[-step+2]==0&&p[2]>0) A4++;
                        if(p[2]==0&&p[step+2]>0) A4++;
                        if(p[step+2]==0&&p[step+1]>0) A4++;
                        if(p[step+1]==0&&p[step]>0) A4++;
                        if(p[step]==0&&p[0]>0) A4++;
                        if(p[0]==0&&p[-step]>0) A4++;
                        if(p[-step]==0&&p[-step+1]>0) A4++;
                    }
 
 
                    //printf("p2=%d p3=%d p4=%d p5=%d p6=%d p7=%d p8=%d p9=%d\n", p2, p3, p4, p5, p6,p7, p8, p9);
                    //printf("A1=%d A2=%d A4=%d\n", A1, A2, A4);
                    if((p2+p3+p4+p5+p6+p7+p8+p9)>1 && (p2+p3+p4+p5+p6+p7+p8+p9)<7  &&  A1==1)
                    {
                        if(((p2==0||p4==0||p8==0)||A2!=1)&&((p2==0||p4==0||p6==0)||A4!=1)) 
                        {
                            dst.at<uchar>(i,j) = 0; //满足删除条件,设置当前像素为0
                            ifEnd = true;
                            //printf("\n");
 
                            //PrintMat(dst);
                        }
                    }
                }
            }
        }
        //printf("\n");
        //PrintMat(dst);
        //PrintMat(dst);
        //已经没有可以细化的像素了,则退出迭代
        if(!ifEnd) break;
    }
}

Rosenfeld算法

1、使用下面的八邻域表示法:
在这里插入图片描述
2、对于前景点像素p1, 如果p2=0,则p1 称作北部边界点。如果p6=0,p1称作南部边界点,p4=0,p1称作东部边界点,p8=0,p1称作西部边界点。
在这里插入图片描述
p1周围8个像素的值都为0,则p1为孤立点,如果周围8个像素有且只有1个像素值为1,则此时p1称作端点。
3、另外还要了解的一个概念就是8 simple。就是我们把p1的值设置为0后,不会改变周围8个像素的8连通性。下面的三个图中,如果p1=0后,则会改变8连通性。
在这里插入图片描述
而下面的则不会改边8连通性,此时可以称像素p1是8 simple
4、Rosenfeld细化算法描述如下:

  • 扫描所有像素,如果像素是北部边界点,且是8simple,但不是孤立点和端点,删除该像素。
  • 扫描所有像素,如果像素是南部边界点,且是8simple,但不是孤立点和端点,删除该像素。
  • 扫描所有像素,如果像素是东部边界点,且是8simple,但不是孤立点和端点,删除该像素。
  • 扫描所有像素,如果像素是西部边界点,且是8simple,但不是孤立点和端点,删除该像素。
  • 执行完上面4个步骤后,就完成了一次迭代,我们重复执行上面的迭代过程,直到图像中再也没有可以删除的点后,退出迭代循环。

5、算法代码如下(输入归一化二值图像):

void Rosenfeld(Mat& src, Mat& dst)//输入的目标像素为1,背景像素为0
{
	if (src.type() != CV_8UC1)
	{
		printf("只能处理二值或灰度图像\n");
		return;
	}
	//非原地操作时候,copy src到dst
	if (dst.data != src.data)
	{
		src.copyTo(dst);
	}

	int i, j, n;
	int width, height;
	//之所以减1,是方便处理8邻域,防止越界
	width = src.cols - 1;
	height = src.rows - 1;
	int step = src.step;
	int  p2, p3, p4, p5, p6, p7, p8, p9;
	uchar* img;
	bool ifEnd;
	Mat tmpimg;
	int dir[4] = { -step, step, 1, -1 };
	while (1)
	{
		//分四个子迭代过程,分别对应北,南,东,西四个边界点的情况
		ifEnd = false;
		for (n = 0; n < 4; n++)
		{
			dst.copyTo(tmpimg);
			img = tmpimg.data;
			for (i = 1; i < height; i++)
			{
				img += step;
				for (j = 1; j < width; j++)
				{
					uchar* p = img + j;
					//如果p点是背景点或者且为方向边界点,依次为北南东西,继续循环
					if (p[0] == 0 || p[dir[n]] > 0) continue;
					p2 = p[-step] > 0 ? 1 : 0;
					p3 = p[-step + 1] > 0 ? 1 : 0;
					p4 = p[1] > 0 ? 1 : 0;
					p5 = p[step + 1] > 0 ? 1 : 0;
					p6 = p[step] > 0 ? 1 : 0;
					p7 = p[step - 1] > 0 ? 1 : 0;
					p8 = p[-1] > 0 ? 1 : 0;
					p9 = p[-step - 1] > 0 ? 1 : 0;
					//8 simple判定
					int is8simple = 1;
					if (p2 == 0 && p6 == 0)
					{
						if ((p9 == 1 || p8 == 1 || p7 == 1) && (p3 == 1 || p4 == 1 || p5 == 1))
							is8simple = 0;
					}
					if (p4 == 0 && p8 == 0)
					{
						if ((p9 == 1 || p2 == 1 || p3 == 1) && (p5 == 1 || p6 == 1 || p7 == 1))
							is8simple = 0;
					}
					if (p8 == 0 && p2 == 0)
					{
						if (p9 == 1 && (p3 == 1 || p4 == 1 || p5 == 1 || p6 == 1 || p7 == 1))
							is8simple = 0;
					}
					if (p4 == 0 && p2 == 0)
					{
						if (p3 == 1 && (p5 == 1 || p6 == 1 || p7 == 1 || p8 == 1 || p9 == 1))
							is8simple = 0;
					}
					if (p8 == 0 && p6 == 0)
					{
						if (p7 == 1 && (p9 == 1 || p2 == 1 || p3 == 1 || p4 == 1 || p5 == 1))
							is8simple = 0;
					}
					if (p4 == 0 && p6 == 0)
					{
						if (p5 == 1 && (p7 == 1 || p8 == 1 || p9 == 1 || p2 == 1 || p3 == 1))
							is8simple = 0;
					}
					int adjsum;
					adjsum = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
					//判断是否是邻接点或孤立点,0,1分别对于那个孤立点和端点
					if (adjsum != 1 && adjsum != 0 && is8simple == 1)
					{
						dst.at<uchar>(i, j) = 0; //满足删除条件,设置当前像素为0
						ifEnd = true;
					}

				}
			}
		}
		if (!ifEnd) break;
	}
}

OpenCV_Contrib中的算法

OpenCV细化算法大致描述如下:
1、复制源图,进行一次全图扫描,对于不为0的点,如果满足以下四个条件

  • (1)保证当前点不是孤立点或端点
  • (2)保证删除当前像素点后的连通性
  • (3)上 * 右 * 下 = 0
  • (4)左 * 右 * 下 = 0
  • 在第一次子迭代中,只是移去东南的边界点,而不考虑西北的边界点。

2、把目地图像再次复制到临时图像,接着对临时图像进行一次扫描,如果不为0的点它的八邻域满足以下4个条件,则在目地图像中删除该点(就是设置该像素为0)

  • (1) 周围8个点,2<= p2+p3+p4+p5+p6+p7+p8+p9<=6
  • (2) p2->p9的排列顺序中,01模式的数量(这里假设二值图非零值为1)为1。
  • (3) 左 * 右 * 上 = 0
  • (4) 左 * 下 * 上 = 0

执行完上面两个步骤后,就完成了一次细化算法,我们可以多次迭代执行上述过程,得到最终的骨架图。
算法源代码(输入二值图,无需归一化,一般的:thinningType=ZHANGSUEN):

// Apply the thinning procedure to a given image
void thinning(InputArray input, OutputArray output, int thinningType) {
	Mat processed = input.getMat().clone();
	CV_CheckTypeEQ(processed.type(), CV_8UC1, "");
	// Enforce the range of the input image to be in between 0 - 255
	processed /= 255;

	Mat prev = Mat::zeros(processed.size(), CV_8UC1);
	Mat diff;

	do {
		thinningIteration(processed, 0, thinningType);
		thinningIteration(processed, 1, thinningType);
		absdiff(processed, prev, diff);
		processed.copyTo(prev);
	} while (countNonZero(diff) > 0);

	processed *= 255;

	output.assign(processed);
}

// Applies a thinning iteration to a binary image
void thinningIteration(Mat img, int iter, int thinningType) {
	Mat marker = Mat::zeros(img.size(), CV_8UC1);

	if (thinningType == THINNING_ZHANGSUEN) {
		for (int i = 1; i < img.rows - 1; i++)
		{
			for (int j = 1; j < img.cols - 1; j++)
			{
				uchar p2 = img.at<uchar>(i - 1, j);
				uchar p3 = img.at<uchar>(i - 1, j + 1);
				uchar p4 = img.at<uchar>(i, j + 1);
				uchar p5 = img.at<uchar>(i + 1, j + 1);
				uchar p6 = img.at<uchar>(i + 1, j);
				uchar p7 = img.at<uchar>(i + 1, j - 1);
				uchar p8 = img.at<uchar>(i, j - 1);
				uchar p9 = img.at<uchar>(i - 1, j - 1);

				int A = (p2 == 0 && p3 == 1) + (p3 == 0 && p4 == 1) +
					(p4 == 0 && p5 == 1) + (p5 == 0 && p6 == 1) +
					(p6 == 0 && p7 == 1) + (p7 == 0 && p8 == 1) +
					(p8 == 0 && p9 == 1) + (p9 == 0 && p2 == 1);
				int B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
				int m1 = iter == 0 ? (p2 * p4 * p6) : (p2 * p4 * p8);
				int m2 = iter == 0 ? (p4 * p6 * p8) : (p2 * p6 * p8);

				if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0)
					marker.at<uchar>(i, j) = 1;
			}
		}
	}
	if (thinningType == THINNING_GUOHALL) {
		for (int i = 1; i < img.rows - 1; i++)
		{
			for (int j = 1; j < img.cols - 1; j++)
			{
				uchar p2 = img.at<uchar>(i - 1, j);
				uchar p3 = img.at<uchar>(i - 1, j + 1);
				uchar p4 = img.at<uchar>(i, j + 1);
				uchar p5 = img.at<uchar>(i + 1, j + 1);
				uchar p6 = img.at<uchar>(i + 1, j);
				uchar p7 = img.at<uchar>(i + 1, j - 1);
				uchar p8 = img.at<uchar>(i, j - 1);
				uchar p9 = img.at<uchar>(i - 1, j - 1);

				int C = ((!p2) & (p3 | p4)) + ((!p4) & (p5 | p6)) +
					((!p6) & (p7 | p8)) + ((!p8) & (p9 | p2));
				int N1 = (p9 | p2) + (p3 | p4) + (p5 | p6) + (p7 | p8);
				int N2 = (p2 | p3) + (p4 | p5) + (p6 | p7) + (p8 | p9);
				int N = N1 < N2 ? N1 : N2;
				int m = iter == 0 ? ((p6 | p7 | (!p9)) & p8) : ((p2 | p3 | (!p5)) & p4);

				if ((C == 1) && ((N >= 2) && ((N <= 3)) & (m == 0)))
					marker.at<uchar>(i, j) = 1;
			}
		}
	}

	img &= ~marker;
}

示例

总结:其实几种算法的效果都差不多,但第一种算法在细化时会忽略4行4列图像数据,如果目标在边缘位置,最好使用后两种算法。
原图:
在这里插入图片描述
细化图:
在这里插入图片描述
直接使用细化算法可能有很多毛刺,你可以先删除突出部分,然后再细化会好很多。

其他细化算法

查表法

//查表法//
Mat lookUpTable(Mat& mat, int lut[])
{
	Mat mat_in;
	mat.convertTo(mat_in, CV_16UC1);		 //8 转 16
	int MatX = mat_in.rows;
	int MatY = mat_in.cols;
	int num = 512;
	//表的维数和卷积核中的数据有关,小矩阵初始化按行赋值
	Mat kern = (Mat_<int>(3, 3) << 1, 8, 64, 2, 16, 128, 4, 32, 256);		//卷积核
	Mat mat_out = Mat::zeros(MatX, MatY, CV_16UC1);
	Mat mat_expend = Mat::zeros(MatX + 2, MatY + 2, CV_16UC1);

	Rect Roi(1, 1, MatY, MatX);				//(列,行,列,行)

	Mat mat_expend_Roi(mat_expend, Roi);	//确定扩展矩阵的Roi区域
	mat_in.copyTo(mat_expend_Roi);			//将传入矩阵赋给Roi区域

	Mat Mat_conv;

	//实用卷积核和和每一个八邻域进行点乘再相加,其结果为表的索引,对应值为0能去掉,为1则不能去掉
	filter2D(mat_expend, Mat_conv, mat_expend.depth(), kern);				//卷积
	Mat mat_index = Mat_conv(Rect(1, 1, MatY, MatX));
	for (int i = 0; i < MatX; i++)
	{
		for (int j = 0; j < MatY; j++)
		{
			int matindex = mat_index.at<short>(i, j);

			if ((matindex < num) && (matindex > 0))
			{
				mat_out.at<short>(i, j) = lut[matindex];
			}
			else if (matindex > num)
			{
				mat_out.at<short>(i, j) = lut[num - 1];
			}
		}
	}
	return mat_out;
}

//细化查表法//
Mat img_bone(Mat& mat)
{
	// mat 为细化后的图像
	Mat mat_in = mat;

	//在数字图像处理时,只有单通道、三通道 8bit 和 16bit 无符号(即CV_16U)的 mat 才能被保存为图像
	mat.convertTo(mat_in, CV_16UC1);

	int lut_1[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };

	int lut_2[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1,
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
					0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1 };

	Mat mat_bool;

	threshold(mat_in, mat_bool, 0, 1, THRESH_BINARY);	//二值图像归一化

	Mat mat_out;

	Mat image_iters;

	while (true)
	{
		mat_out = mat_bool;

		//查表:水平、垂直
		image_iters = lookUpTable(mat_bool, lut_1);
		mat_bool = lookUpTable(image_iters, lut_2);

		Mat diff = mat_out != mat_bool;

		//countNonZero函数返回灰度值不为0的像素数
		bool mat_equal = countNonZero(diff) == 0;		//判断图像是否全黑

		if (mat_equal)
		{
			break;
		}
	}
	Mat Matout;

	mat_bool.convertTo(Matout, CV_8UC1);

	return Matout;
}

HilditchThin的另一种算法

void cvHilditchThin(cv::Mat& src, cv::Mat& dst)
{
	if (src.type() != CV_8UC1)
	{
		printf("只能处理二值或灰度图像\n");
		return;
	}
	//非原地操作时候,copy src到dst
	if (dst.data != src.data)
	{
		src.copyTo(dst);
	}

	//8邻域的偏移量
	int offset[9][2] = { {0,0},{1,0},{1,-1},{0,-1},{-1,-1},
	{-1,0},{-1,1},{0,1},{1,1} };
	//四邻域的偏移量
	int n_odd[4] = { 1, 3, 5, 7 };
	int px, py;
	int b[9];                      //3*3格子的灰度信息
	int condition[6];              //1-6个条件是否满足
	int counter;                   //移去像素的数量
	int i, x, y, copy, sum;

	uchar* img;
	int width, height;
	width = dst.cols;
	height = dst.rows;
	img = dst.data;
	int step = dst.step;
	do
	{

		counter = 0;

		for (y = 0; y < height; y++)
		{

			for (x = 0; x < width; x++)
			{

				//前面标记为删除的像素,我们置其相应邻域值为-1
				for (i = 0; i < 9; i++)
				{
					b[i] = 0;
					px = x + offset[i][0];
					py = y + offset[i][1];
					if (px >= 0 && px < width &&    py >= 0 && py < height)
					{
						// printf("%d\n", img[py*step+px]);
						if (img[py*step + px] == WHITE)
						{
							b[i] = 1;
						}
						else if (img[py*step + px] == GRAY)
						{
							b[i] = -1;
						}
					}
				}
				for (i = 0; i < 6; i++)
				{
					condition[i] = 0;
				}

				//条件1,是前景点
				if (b[0] == 1) condition[0] = 1;

				//条件2,是边界点
				sum = 0;
				for (i = 0; i < 4; i++)
				{
					sum = sum + 1 - abs(b[n_odd[i]]);
				}
				if (sum >= 1) condition[1] = 1;

				//条件3, 端点不能删除
				sum = 0;
				for (i = 1; i <= 8; i++)
				{
					sum = sum + abs(b[i]);
				}
				if (sum >= 2) condition[2] = 1;

				//条件4, 孤立点不能删除
				sum = 0;
				for (i = 1; i <= 8; i++)
				{
					if (b[i] == 1) sum++;
				}
				if (sum >= 1) condition[3] = 1;

				//条件5, 连通性检测
				if (func_nc8(b) == 1) condition[4] = 1;

				//条件6,宽度为2的骨架只能删除1边
				sum = 0;
				for (i = 1; i <= 8; i++)
				{
					if (b[i] != -1)
					{
						sum++;
					}
					else
					{
						copy = b[i];
						b[i] = 0;
						if (func_nc8(b) == 1) sum++;
						b[i] = copy;
					}
				}
				if (sum == 8) condition[5] = 1;

				if (condition[0] && condition[1] && condition[2] && condition[3] && condition[4] && condition[5])
				{
					img[y*step + x] = GRAY; //可以删除,置位GRAY,GRAY是删除标记,但该信息对后面像素的判断有用
					counter++;
					//printf("----------------------------------------------\n");
					//PrintMat(dst);
				}
			}
		}

		if (counter != 0)
		{
			for (y = 0; y < height; y++)
			{
				for (x = 0; x < width; x++)
				{
					if (img[y*step + x] == GRAY)
						img[y*step + x] = BLACK;

				}
			}
		}

	} while (counter != 0);
}

参考

https://www.cnblogs.com/mikewolf2002/p/3321732.html

  • 0
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值