二值图像骨架线提取

news2024/11/15 11:40:00

二值图像骨架线提取

  • HilditchThin算法
  • Rosenfeld算法
  • OpenCV_Contrib中的算法
  • 示例
  • 其他细化算法
    • 查表法
    • HilditchThin的另一种算法
  • 参考

二值图像骨架线提取算法: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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/391310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java+ElasticSearch+Pytorch实现以图搜图

以图搜图&#xff0c;涉及两大功能&#xff1a;1、提取图像特征向量。2、相似向量检索。第一个功能我通过编写pytorch模型并在java端借助djl调用实现&#xff0c;第二个功能通过elasticsearch7.6.2的dense_vector、cosineSimilarity实现。一、准备模型创建demo.py&#xff0c;输…

cuda2D FDTD——share

https://www.coder.work/article/30133 shared memory只能在block内共享&#xff0c;之间无法互相通信 对于2D TM波动方程计算&#xff0c;我们可以使用以下策略来处理共享内存的边界&#xff1a; 将全局内存中的数据复制到共享内存中时&#xff0c;除了将每个线程需要的数据…

Python爬虫实践:优志愿 院校列表

https://www.youzy.cn/tzy/search/colleges/collegeList获取目标网址等信息打开开发人员工具&#xff08;F12&#xff09;&#xff0c;拿到调用接口的地址&#xff0c;以及接口请求参数等信息&#xff0c;如下curl https://uwf7de983aad7a717eb.youzy.cn/youzy.dms.basiclib.ap…

假如你知道这样的MySQL性能优化

1. 为查询缓存优化你的查询 大多数的 MySQL 服务器都开启了查询缓存。这是提高性最有效的方法之 一&#xff0c;而且这是被 MySQL 的数据库引擎处理的。当有很多相同的查询被执行了多次的时候&#xff0c;这些查询结果会被放到一个缓存中&#xff0c;这样&#xff0c;后续的相同…

Kogito -- 入门详解

Kogito -- 入门详解1. Introduction1.1 Version1.2 Introduction2.Environment Install2.1 JDK Install2.2 Maven Install&#xff08;3.8.6&#xff09;2.3 Idea2.4 VSCode3. Run Code3.1 Dependency3.2 Run3.3 Swagger4.Awakening4.1 Big Data -- Postgres5.Awakening5.1 Big…

如何做一个高级的文本编辑器 textarea,拥有快捷键操作

如何做一个高级的文本编辑器 textarea&#xff0c;拥有快捷键操作 最近想做一个高级点的 textarea &#xff0c;支持 JetBrains 系列软件的快捷键&#xff0c;比如&#xff1a; CTRL D 复制当前行。Tab 在前面插入 4 个空格。Shift Tab 删除行前的空格&#xff0c;多于4个&a…

google独立站和与企业官网的区别是什么?

google独立站和与企业官网的区别是什么&#xff1f; 答案是&#xff1a;独立站通过谷歌SEO优化可以更好的获取自然排名的流量。 随着互联网的不断发展&#xff0c;企业越来越重视自身网站的建设和优化&#xff0c;而在企业网站建设中&#xff0c;很多人会犯一个常见的错误&am…

模块、包和异常

目录1.模块import 导入from...import 导入2. 模块的搜索顺序3. __name__属性的使用4. 包包的使用步骤5. 发布模块6. 安装模块7. 卸载模块8. pip 安装第三方模块9. 异常处理异常捕获异常的传递抛出 raise 异常1.模块 模块是 Python 程序架构的一个核心概念 每一个以扩展名 py …

LPDDR4x 的 学习总结(4) - SDRAM chip的组织结构

上节总结cell的结构和基本操作 本节基于cell组合起来的DRAM组织结构 DDR Device 的组织结构 Cells 以特定的方式组成 Column/Row/Bank/Chip/Rank/DIMM/Channel等多层级组织结构如下图&#xff1a; 图1 - DRAM的组织结构 图2 - DRAM容量的组织结构图 Channel: 同1个DDR控制器 …

GIT基础常用命令-1 GIT基础篇

git基础常用命令-1 GIT基础篇1.git简介及配置1.1 git简介1.2 git配置config1.2.1 查看配置git config1.2.2 配置设置1.2.3 获取帮助git help2 GIT基础常用命令2.1 获取镜像仓库2.1.1 git init2.1.2 git clone2.2 本地仓库常用命令2.2.1 git status2.2.2 git add2.2.3 git diff2…

seata1.5.2使用从零快速上手(提供代码与安装包)

1.软件准备&#xff1a; 1.1 seata1.5.2 官网下载&#xff1a;地址:http://seata.io/zh-cn/ server源码:https://github.com/seata/seata 百度云下载&#xff08;建议&#xff09;: 百度下载 链接&#xff1a;https://pan.baidu.com/s/1eilbSI0YdmupHYI7FroTsw 提取码&…

【编程基础之Python】10、Python中的运算符

【编程基础之Python】10、Python中的运算符Python中的运算符算术运算符赋值运算符比较运算符逻辑运算符位运算符成员运算符身份运算符运算符优先级运算符总结Python中的运算符 Python是一门非常流行的编程语言&#xff0c;它支持各种运算符来执行各种操作。这篇文章将详细介绍…

构造有向无环图(拓扑排序)

蓝桥杯集训每日一题 acwing3696 给定一个由 n 个点和 m 条边构成的图。 不保证给定的图是连通的。 图中的一部分边的方向已经确定&#xff0c;你不能改变它们的方向。 剩下的边还未确定方向&#xff0c;你需要为每一条还未确定方向的边指定方向。 你需要保证在确定所有边的…

独家揭秘:站外引流的十大技巧!

在今天的互联网时代&#xff0c;如何有效地引流已成为网站运营者面临的一个重要问题。 站外引流是指通过在其他网站或平台上建立链接或发布内容&#xff0c;将流量引导到自己的网站&#xff0c;提高自己网站的访问量。 本文将为大家揭秘站外引流的十大技巧&#xff0c;帮助大…

python项目搭建(上)

提示&#xff1a;惊觉相思不露&#xff0c;原来只因已入骨 文章目录前言软件的安装HTTP协议1.安装Django启动Django路由子表定义数据库创建数据库表过滤条件生成HTML前言 这里是用python搭建的一个**销售管理系统&#xff0c;用于记录个人遇到的一些错误 提示&#xff1a;以下…

软测入门(七)python操作数据文件(Json、yaml、csv、excel、xml)

python操作文件 txt文件 read() : 读取所有readline() : 读取一行readlines() : 读取所有&#xff0c;且以行为单位&#xff0c;放入list列表中 file open(r"F:\abc.txt", "r", encoding"utf-8") # 以utf-8格式读取文件 # 读取所有 # print…

IM即时通讯开发之常用加解密算法与通讯安全讲解

平时开发工作中&#xff0c;我们会经常接触加密、解密的技术。尤其在今天移动互联网时代&#xff0c;越来越多的用户会将数据存储在云端&#xff0c;或使用在线的服务处理信息。这些数据有些涉及用户的隐私&#xff0c;有些涉及用户的财产&#xff0c;要是没有一套的方案来解决…

儿童写作业的台灯怎么选择?2023给孩子买台灯最新推荐一下

儿童年龄比较小&#xff0c;所以眼睛也比较脆弱&#xff0c;然而现在的社会普遍节奏较快&#xff0c;无论是上班族&#xff0c;还是大中小学生&#xff0c;压力都比较大&#xff0c;儿童经常晚上看书、写字学习&#xff0c;眼睛难免劳累&#xff0c;所以儿童台灯最重要的就是柔…

CDGA|给金融科技数据治理的四大建议,从根基上解决问题

随着我国金融科技行业的发展&#xff0c;当前的数据治理水平已无法满足金融反哺实体经济发展的效率需要。 目前主要存在数据基础设施不完善导致的治理根基薄弱核心问题&#xff0c;建议从以下四个方面进行完善数据基础设施&#xff0c;构筑金融科技数据治理体系。 加强顶层设计…

Springboot整合RabbitMQ并使用

1、Springboot整合RabbitMQ 1、引入场景启动器 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency>引入AMQP场景启动器之后&#xff0c;RabbitAutoConfiguratio…