中国大数据公司排名10强/深圳网站seo外包公司哪家好
二值图像骨架线提取
- 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到dstif(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,AP4int 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; //满足删除条件,设置当前像素为0ifEnd = 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到dstif (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; //满足删除条件,设置当前像素为0ifEnd = 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 - 255processed /= 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 转 16int 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到dstif (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++){//前面标记为删除的像素,我们置其相应邻域值为-1for (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
相关文章:

二值图像骨架线提取
二值图像骨架线提取HilditchThin算法Rosenfeld算法OpenCV_Contrib中的算法示例其他细化算法查表法HilditchThin的另一种算法参考二值图像骨架线提取算法:HilditchThin算法、Rosenfeld算法、OpenCV_Contrib中的算法 HilditchThin算法 1、使用的8邻域标记为ÿ…...

规划数据指标体系方法(上)——OSM 模型
之前我已经有写过文章讲了数据指标体系的搭建思路,但有同学还是不太清楚要从何入手,今天我就来跟大家讲一讲搭建数据指标体系之前第一步要先做的事情——规划数据指标体系。 规划数据指标体系,在业内有三种比较常见的方法,分别是&…...

做程序界中的死神,继续提升灵力上限
标题解读:标题中的死神,是源自《死神》动漫里面的角色,斩魂刀是死神的武器,始解是斩魂刀的初始解放形态,卐解是斩魂刀的觉醒解放形态,也是死神的大招。意旨做程序界中程序员的佼佼者,一步一步最…...

[数据结构]:11-冒泡排序(顺序表指针实现形式)(C语言实现)
目录 前言 已完成内容 冒泡排序实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-PSeqListFunction.cpp 04-SortCommon.cpp 05-SortFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容,除其中使用到C引用外,全为C语言代…...

Java实验报告经验总结
每一段是每一次实验报告写的经验总结,一共是一学期的内容 文章目录一二三四五六一 ~~~~~分析:这次做程序中也出了不少问题,究其根本还是没有理解清楚各语句功能和其应用。 ~~~~~比如说:当我们在定义浮点数时,数字的后面…...

ESP32使用TCP HTTP访问API接口JSON解析获取数据
ESP32使用TCP HTTP访问API接口JSON解析获取数据API接口代码解析获取时间代码烧录效果总结API接口 单片机常用的API接口基本都是返回的一串JSON格式的数据,这里以ESP32联网获取时间信息作为获取API数据的示例,以便后续移植使用。 很多功能性的API接…...

spring security 实现自定义认证和登录(4):使用token进行验证
前面我们实现了给客户端下发token,虽然客户端拿到了token,但我们还没处理客户端下一次携带token请求时如何验证,我们想要实现拿得到token之后,只需要验证token,不需要用户再携带用户名和密码了。 1. 禁用 UsernamePass…...

戴眼镜检测和识别2:Pytorch实现戴眼镜检测和识别(含戴眼镜数据集和训练代码)
Pytorch实现戴眼镜检测和识别(含戴眼镜数据集和训练代码) 目录 Pytorch实现戴眼镜检测和识别(含戴眼镜数据集和训练代码) 1.戴眼镜检测和识别方法 2.戴眼镜数据集 3.人脸检测模型 4.戴眼镜分类模型训练 (1)项目安装 (2)准…...

信息收集之Google Hacking
Google HackingGoogleHacking作为常用且方便的信息收集搜索引擎工具,它是利用谷歌搜索强大,可以搜出不想被看到的后台、泄露的信息、未授权访问,甚至还有一些网站配置密码和网站漏洞等。掌握了Google Hacking基本使用方法,或许下一…...

【面试题】如何避免使用过多的 if else?
大厂面试题分享 面试题库前后端面试题库 (面试必备) 推荐:★★★★★地址:前端面试题库一、引言相信大家听说过回调地狱——回调函数层层嵌套,极大降低代码可读性。其实,if-else层层嵌套,如下图…...

oneblog_justauth_三方登录配置【Gitee】
文章目录oneblog添加第三方平台gitee中创建三方应用完善信息oneblog添加第三方平台 1.oneblog管理端,点击左侧菜单 网站管理——>社会化登录配置管理 ,添加一个社会化登录 2.编辑信息如下,选择gitee平台后复制redirectUri,然后去gitee获取clientId和…...

33- PyTorch实现分类和线性回归 (PyTorch系列) (深度学习)
知识要点 pytorch最常见的创建模型的方式, 子类 读取数据: data pd.read_csv(./dataset/credit-a.csv, headerNone) 数据转换为tensor: X torch.from_numpy(X.values).type(torch.FloatTensor) 创建简单模型: from torch import nn model nn.Sequential(nn.Linear(15, 1…...

C++基础——Ubuntu下编写C++环境配置总结(C++基本简介、Ubuntu环境配置、编写简单C++例程)
【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…...

项目管理中,导致进度失控的五种错误
项目管理中对工期的控制主要是进度控制,在项目进行中中,由于项目时间跨度长,人员繁杂,如果管理不规范,就容易导致项目进度滞后,如何管理好施工进度是管理者需要解决的问题之一。 1、项目计划缺乏执行力 安…...

C# 中的abstract和virtual
重新理解了下关键字abstract,做出以下总结: 1.标记为abstract的类不能实例化,但是依然可以有构造函数,也可以重载构造函数,在子类中调用 2.abstract类中可以有abstract标记的方法和属性,也可以没有,被标记…...

第六十周总结——React数据管理
React数据管理 代码仓库 React批量更新 React中的批量更新就是将多次更新合并处理,最终只渲染一次,来获得更好的性能。 React18版本之前的批量更新 // react 17 react-dom 17 react-scripts 4.0.3 import * as ReactDOM from "react-dom"…...

Springboot之@Async异步指定自定义线程池使用
开发中会碰到一些耗时较长或者不需要立即得到执行结果的逻辑,比如消息推送、商品同步等都可以使用异步方法,这时我们可以用到Async。但是直接使用 Async 会有风险,当我们没有指定线程池时,他会默认使用其Spring自带的 SimpleAsync…...

视频知识点(23)- TS格式详解指南
*《音视频开发》系列-总览*(点我) 一、格式简介 TS视频封装格式,是一种被广泛应用的多媒体文件格式。它的全称是MPEG2-TS,其中TS是“Transport Stream”的缩写。TS(Transport Stream)流是一种传输流,它由固定长度(188 字节)的 TS 包组成,TS 包是对PES包的一种封装方式…...

linux篇【16】:传输层协议<后序>
目录 六.滑动窗口 (1)发送缓冲区结构 (2)滑动窗口介绍 (3)滑动窗口不一定只会向右移动。滑动窗口可以变大也可以变小。 (4)那么如果出现了丢包, 如何进行重传? 这里分两种情况…...

【C语言】动态内存管理
一.为什么存在动态内存分配? 我们已经掌握的内存开辟方式有:int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的方式有两个特点: 1. 空间开辟大小是固定的。 2. 数组在申明的…...

【Pytorch】AutoGrad个人理解
前提知识:[Pytorch] 前向传播和反向传播示例_友人小A的博客-CSDN博客 目录 简介 叶子节点 Tensor AutoGrad Functions 简介 torch.autograd是PyTorch的自动微分引擎(自动求导),为神经网络训练提供动力。torch.autograd需要对…...

华硕z790让独显和集显同时工作
系统用了一段时间,现在想让显卡主要做深度学习训练,集显用来连接显示器。却发现显示器接到集显接口无信号。 打售后客服也没有解决,现在把解决方案记录一下。 这是客服给的方案: 请开机后进BIOS---Advanced---System Agent (SA)…...

提高编程思维的python代码
1.通过函数取差。举例:返回有差别的列表元素 from math import floordef difference_by(a,b,fn):b set(map(fn, b))return [i for i in a if fn(i) not in b] print(difference_by([2.1, 1.2], [2.3, 3.4], floor))2.一行代码调用多个函数 def add(a, b):return …...

CSS背景background属性整理
1.background-color background-color属性:设置元素的背景颜色 2.background-position background-position属性:设置背景图像的起始位置,需要把 background-attachment 属性设置为 "fixed",才能保证该属性在 Firefo…...

AQS底层源码深度剖析-Lock锁
目录 AQS底层源码深度剖析-Lock锁 ReentrantLock底层原理 为什么把获取锁失败的线程加入到阻塞队列中,而不是采取其它方法? 总结:三大核心原理 CAS是啥? 代码模拟一个CAS: 公平锁与非公平锁 可重入锁的应用场景&…...

网络编程(二)
6. TCP 三次握手四次挥手 HTTP 协议是 Hype Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web)服务器(sever)传输超文本到客户端(本地浏览器…...

访问学者进入美国哪些东西不能带?
随着疫情的稳定,各国签证的逐步放开,成功申请到国外访问学者、博士后如何顺利的进入国外,哪些东西不能带,下面就随知识人网小编一起看一看。一、畜禽肉类(Meats, Livestock and Poultry)不论是新鲜的、干燥的、罐头的、真空包装的…...

灵巧手抓持<分类><仿真>
获取灵巧手抓取物体时的抓持类型,需要考虑:手本身的结构、被抓取物体的形状尺寸、抓持操作任务的条件。 研究方法:基于模型的方法、基于数据驱动的方法 基于模型的方法:建立灵巧手抓持相关的运动学和动力学模型建立目标函数求解…...

CENTO OS上的网络安全工具(十九)ClickHouse集群部署
一、VMware上集群部署ClickHouse (一)网络设置 1. 通过修改文件设置网络参数 (1)CentOS 在CENTOS上的网络安全工具(十六)容器特色的Linux操作_lhyzws的博客-CSDN博客中我们提到过可以使用更改配置文件的方式…...

tesseract -图像识别
下载链接:https://digi.bib.uni-mannheim.de/tesseract/如下选择最新的版本,这里我选择tesseract-ocr-w64-setup-5.3.0.20221222.exe有如下python模块操作tesseractpyocr 国内源:pip install -i https://pypi.mirrors.ustc.edu.cn/simple/ py…...