二值图像骨架线提取

这篇具有很好参考价值的文章主要介绍了二值图像骨架线提取。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

二值图像骨架线提取算法: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文章来源地址https://www.toymoban.com/news/detail-443826.html

到了这里,关于二值图像骨架线提取的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • opencv-图像处理基础-二值图像

    1.二值图像 二值图像是指仅仅包含黑色和白色两种颜色的图像。 在计算机中,通过一个栅格状排列的数据集(矩阵)来表示和处理图像。例如,图 2-1 是 一个字母 A 的图像,计算机在处理该图像时,会首先将其划分为一个个的小方块,每一个小方 块就是一个独立的处理单位

    2024年02月15日
    浏览(57)
  • 图像处理(二值图、灰度图、彩色图像)

    图像处理之二值图像、灰度图像、RGB图像 1、二值图像 定义:二值图像是值仅仅包含黑色和白色的图像 计算机在处理时,会把黑色像素点处理为0,白色像素点处理1。由于只用一个比特位就能表示,所以称之为二值图像。 2、灰度图 为了表达更丰富的颜色细节,灰度图采用更

    2024年02月06日
    浏览(61)
  • OpenCV图像处理----图像的二值化

    图像二值化( Image Binarization) 就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。 二值化的原理 OpenCV提供的图像二值化API threshold()方法参数: 图片矩阵 阈值 图片中的最大值 二值化的方式 二值化的方式: THRESH_BINARY 高于阈值改为

    2024年02月12日
    浏览(42)
  • 图像二值化

    图像二值化就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。图像的二值化使图像中数据量大为减少,从而能凸显出目标的轮廓。 要得到二值化图像,首先要把图像灰度化,然后将256个亮度等级的灰度图像通过适当的阈值选取而

    2024年02月05日
    浏览(65)
  • 图像二值化算法

    图像二值化( Image Binarization)就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。二值图像每个像素只有两种取值:要么纯黑,要么纯白。进行二值化有多种方式,其中最常用的就是采用阈值法(Thresholding)进行二值化。 根据阈值

    2024年02月06日
    浏览(50)
  • 图像二值化(一)

      继《图像灰度处理》后,我们继续讨论图像处理的其他基本操作——图像二值化处理。   图像二值化是将像素点的灰度值设为0或255,使图像呈现明显的黑白效果。二值化之前需要把图像进行灰度处理。图像选用的是最近很火的ChatGPT标志。   Threshold为全局阈值,但是

    2024年02月07日
    浏览(36)
  • matlab数字图像处理——图像的读写,灰度、二值图像

    一、实验目的 1.结合数字图像处理的知识,直观感受图像处理的基本实现过程 2.熟悉MATLAB工具的使用 3.了解图像的读写和显示 二、实验内容 实验内容一:图像读取 (1)利用编程实现读取图像 利用imread读取文件夹images中的图像;查看读取到的图像数据矩阵,对比灰度图像、

    2024年02月07日
    浏览(58)
  • MATLAB图像处理(二):图像转二值图

    二值图即黑白图,图中只有黑白色之分。图像的二维矩阵只由0和1构成,0表黑色,1表白色。 二值化利于图像进一步处理,且数据量减小,易于凸显出目标轮廓。 具体定义可以参考如下链接: http://zhidao.baidu.com/question/89536354.html 首先对有256个亮度等级的灰度图像进行阈值选取

    2024年02月04日
    浏览(53)
  • OpenCV(八):图像二值化

    目录 1.固定值二值化 2.自适应阈值二值化 3.Android JNI完整代码 1.固定值二值化 固定阈值二值化是OpenCV中一种简单而常用的图像处理技术,用于将图像转换为二值图像。在固定阈值二值化中,像素值根据一个预定义的阈值进行分类,大于阈值的像素被设置为白色,而小于或等于

    2024年02月10日
    浏览(53)
  • opencv(20) 图像阈值(二值化)

    二值化核心思想,设阈值,大于阈值的为0(黑色)或 255(白色),使图像称为黑白图。 阈值可固定,也可以自适应阈值。 自适应阈值一般为一点像素与这点为中序的区域像素平均值或者高斯分布加权和的比较,其中可以设置一个差值也可以不设置。 图像的阈值化旨在提取

    2024年02月02日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包