2019-09-01 19:51:41 webzhuce 阅读数 441
• ###### 美术细化专业课程

通过19可是5个章节，在5个方面跟大家详细的介绍介绍美术设计在游戏中的灵活运用。

10555 人正在学习 去看看 张聪

# 基本概念

“骨架”是指一幅图像的骨骼部分，它描述物体的几何形状和拓扑结构，是重要的图像描绘子之一。计算骨架的过程一般称为“细化”或“骨架化”，在包括文字识别、工业零件形状识别以及印刷电路板自动检测在内的很多应用中，细化过程都发挥这关键作用。通常，对我们感兴趣的目标物体进行细化有助于突出目标的形状特点和拓扑结构并且减少冗余的信息。

# 示例演示

实现基于形态学的细化算法。与上面算法介绍不同，将白色作为前景，黑色作为背景。完整工程代码。

``````/*
*only process binary image
* white is foreground
*/
void Thining(const cv::Mat &src, cv::Mat &dst)
{

//four conditions
bool condition1 = false, condition2 = false, condition3 = false, condition4 = false;

//n*n neighbours
constexpr int N = 5;
uchar neighbour[N][N];
const int kOffset = N / 2;
src.copyTo(dst);

bool modified = true;
while(modified)
{
modified = false;
Mat img;
dst.copyTo(img);

for(int i = kOffset; i < (dst.rows - kOffset); i++)
{
for(int j = kOffset; j < (dst.cols - kOffset); j++)
{
if(dst.at<uchar>(i, j) == 0)
continue;

condition1 = false;
condition2 = false;
condition3 = false;
condition4 = false;

//get N*N neighbours of current point
//white : 0, black : 1
for(int m = 0; m < N; m++)
{
for(int n = 0; n < N; n++)
{
neighbour[m][n] = dst.at<uchar>(i + n - kOffset, j + m - kOffset) == 255 ? 1 : 0;
}
}

//2 <= NZ(P1) <=6
int count = neighbour[1][1] + neighbour[1][2] + neighbour[1][3] +
neighbour[2][1] + neighbour[2][3] +
neighbour[3][1] + neighbour[3][2] + neighbour[3][3];
if(count >= 2 && count <=6)
condition1 = true;

//Z0(P1) == 1
count = 0;
if(neighbour[1][2] == 0 && neighbour[1][1] == 1)
count++;
if(neighbour[1][1] == 0 && neighbour[2][1] == 1)
count++;
if(neighbour[2][1] == 0 && neighbour[3][1] == 1)
count++;
if(neighbour[3][1] == 0 && neighbour[3][2] == 1)
count++;
if(neighbour[3][2] == 0 && neighbour[3][3] == 1)
count++;
if(neighbour[3][3] == 0 && neighbour[2][3] == 1)
count++;
if(neighbour[2][3] == 0 && neighbour[1][3] == 1)
count++;
if(neighbour[1][3] == 0 && neighbour[1][2] == 1)
count++;
if(count == 1)
condition2 = true;

//P2*P4*P8 = 0 or Z0(P2) != 1
if(neighbour[1][2] * neighbour[2][1] * neighbour[2][3] == 0)
condition3 = true;
else
{
count = 0;
if(neighbour[0][2] == 0 && neighbour[0][1] == 1)
count++;
if(neighbour[0][1] == 0 && neighbour[1][1] == 1)
count++;
if(neighbour[1][1] == 0 && neighbour[2][1] == 1)
count++;
if(neighbour[2][1] == 0 && neighbour[2][2] == 1)
count++;
if(neighbour[2][2] == 0 && neighbour[2][3] == 1)
count++;
if(neighbour[2][3] == 0 && neighbour[1][3] == 1)
count++;
if(neighbour[1][3] == 0 && neighbour[0][3] == 1)
count++;
if(neighbour[0][3] == 0 && neighbour[0][2] == 1)
count++;
if(count != 1)
condition3 = true;
}

//P2*P4*P6 = 0 or Z0(P4) != 1
if(neighbour[1][2] * neighbour[2][1] * neighbour[3][2] == 0)
condition4 = true;
else
{
count = 0;
if(neighbour[1][1] == 0 && neighbour[1][0] == 1)
count++;
if(neighbour[1][0] == 0 && neighbour[2][0] == 1)
count++;
if(neighbour[2][0] == 0 && neighbour[3][0] == 1)
count++;
if(neighbour[3][0] == 0 && neighbour[3][1] == 1)
count++;
if(neighbour[3][1] == 0 && neighbour[3][2] == 1)
count++;
if(neighbour[3][2] == 0 && neighbour[2][2] == 1)
count++;
if(neighbour[2][2] == 0 && neighbour[1][2] == 1)
count++;
if(neighbour[1][2] == 0 && neighbour[1][1] == 1)
count++;
if(count != 1)
condition4 = true;
}

if(condition1 && condition2 && condition3 && condition4)
{
img.at<uchar>(i, j) = 0;
modified = true;
}
else
img.at<uchar>(i, j) = 255;

} // for columns
} // for rows
img.copyTo(dst);
} //  for while
}
``````

## 运行结果

2013-03-21 16:41:26 KTs_3 阅读数 806
• ###### 美术细化专业课程

通过19可是5个章节，在5个方面跟大家详细的介绍介绍美术设计在游戏中的灵活运用。

10555 人正在学习 去看看 张聪

### 细化算法：

P3    P2   P9

P4    P1   P8

P5    P6   P7

(1) 7>NZ(P1)>1;

(2) Z0(P1)=1;

(3) P2*P4*P6=0 OR Z0(P2)!=1;

(4) P2*P4*P8=0 OR Z0(P4)!=1;

Set  ncount=1;

if  P2=0 and P3=1, then ncount++;

if  P3=0 and P4=1, then ncount++;

if  P4=0 and P5=1, then ncount++;

if  P5=0 and P6=1, then ncount++;

if  P6=0 and P7=1, then ncount++;

if  P7=0 and P8=1,then  ncount++;

if  P8=0 and P9=1,then  ncount++;

if  P9=0 and P2=1,then  ncount++;

Z0(P)=ncount;

```#include <iostream>
#include "cv.h"
#include "highgui.h"

using namespace std;
using namespace cv;

Mat ThinImage(Mat img)
{
int i,j,count,ncount;

for (i=2;i<img.rows-2;i++)
{
for (j=2;j<img.cols-2;j++)
{
if(img.at<uchar>(i,j)==255)
{
bool condition1=false,
condition2=false,
condition3=false,
condition4=false;
//count P(i,j) around #1
count=0;
if(img.at<uchar>(i-1,j-1)==255)
count++;
if(img.at<uchar>(i-1,j)==255)
count++;
if(img.at<uchar>(i-1,j+1)==255)
count++;
if(img.at<uchar>(i,j-1)==255)
count++;
if(img.at<uchar>(i,j+1)==255)
count++;
if(img.at<uchar>(i+1,j-1)==255)
count++;
if(img.at<uchar>(i+1,j)==255)
count++;
if(img.at<uchar>(i+1,j+1)==255)
count++;
if(count<7&&count>1)
condition1=true;

//compute ncount
ncount=0;
if(img.at<uchar>(i-1,j)==0&&img.at<uchar>(i-1,j-1)==255)
ncount++;
if(img.at<uchar>(i-1,j-1)==0&&img.at<uchar>(i,j-1)==255)
ncount++;
if(img.at<uchar>(i,j-1)==0&&img.at<uchar>(i+1,j-1)==255)
ncount++;
if(img.at<uchar>(i+1,j-1)==0&&img.at<uchar>(i+1,j)==255)
ncount++;
if(img.at<uchar>(i+1,j)==0&&img.at<uchar>(i+1,j+1)==255)
ncount++;
if(img.at<uchar>(i+1,j+1)==0&&img.at<uchar>(i,j+1)==255)
ncount++;
if(img.at<uchar>(i,j+1)==0&&img.at<uchar>(i-1,j+1)==255)
ncount++;
if(img.at<uchar>(i-1,j+1)==0&&img.at<uchar>(i-1,j)==255)
ncount++;
if(ncount==1)
condition2=true;

if (img.at<uchar>(i-1,j)*img.at<uchar>(i,j-1)*img.at<uchar>(i,j+1)==0)
condition3=true;
else
{
ncount=0;
i-=1;
if(img.at<uchar>(i-1,j)==0&&img.at<uchar>(i-1,j-1)==255)
ncount++;
if(img.at<uchar>(i-1,j-1)==0&&img.at<uchar>(i,j-1)==255)
ncount++;
if(img.at<uchar>(i,j-1)==0&&img.at<uchar>(i+1,j-1)==255)
ncount++;
if(img.at<uchar>(i+1,j-1)==0&&img.at<uchar>(i+1,j)==255)
ncount++;
if(img.at<uchar>(i+1,j)==0&&img.at<uchar>(i+1,j+1)==255)
ncount++;
if(img.at<uchar>(i+1,j+1)==0&&img.at<uchar>(i,j+1)==255)
ncount++;
if(img.at<uchar>(i,j+1)==0&&img.at<uchar>(i-1,j+1)==255)
ncount++;
if(img.at<uchar>(i-1,j+1)==0&&img.at<uchar>(i-1,j)==255)
ncount++;
if(ncount!=1)
condition3=true;
i+=1;
}

if (img.at<uchar>(i-1,j)*img.at<uchar>(i,j-1)*img.at<uchar>(i+1,j)==0)
condition4=true;
else
{
ncount=0;
j-=1;
if(img.at<uchar>(i-1,j)==0&&img.at<uchar>(i-1,j-1)==255)
ncount++;
if(img.at<uchar>(i-1,j-1)==0&&img.at<uchar>(i,j-1)==255)
ncount++;
if(img.at<uchar>(i,j-1)==0&&img.at<uchar>(i+1,j-1)==255)
ncount++;
if(img.at<uchar>(i+1,j-1)==0&&img.at<uchar>(i+1,j)==255)
ncount++;
if(img.at<uchar>(i+1,j)==0&&img.at<uchar>(i+1,j+1)==255)
ncount++;
if(img.at<uchar>(i+1,j+1)==0&&img.at<uchar>(i,j+1)==255)
ncount++;
if(img.at<uchar>(i,j+1)==0&&img.at<uchar>(i-1,j+1)==255)
ncount++;
if(img.at<uchar>(i-1,j+1)==0&&img.at<uchar>(i-1,j)==255)
ncount++;
if(ncount!=1)
condition4=true;
j+=1;

}

if(condition1&&condition2&&condition3&&condition4==true)
img.at<uchar>(i,j)=0;
}
}
}

imshow("ThinImage",img);
waitKey();
return img;
}```

《数字图像处理与机器视觉》（人民邮电出版社）

（新手上路，如有错误或不够优化的地方请大方指出，共同提高！）

2018-12-24 22:44:22 fengxianghui01 阅读数 2134
• ###### 美术细化专业课程

通过19可是5个章节，在5个方面跟大家详细的介绍介绍美术设计在游戏中的灵活运用。

10555 人正在学习 去看看 张聪

细化(thinning)算法有很多，我们在这里介绍的是一种简单而且效果很好的算法，用它就能够实现从文本抽取骨架的功能。我们的对象是白纸黑字的文本，但在程序中为了处理的方便，还是采用 256 级灰度图，不过只用到了调色板中 0 和 255 两项。

所谓细化，就是从原来的图中去掉一些点，但仍要保持原来的形状。实际上，是保持原图的骨架。所谓骨架，可以理解为图象的中轴，例如一个长方形的骨架是它的长方向上的中轴线；正方形的骨架是它的中心点；圆的骨架是它的圆心，直线的骨架是它自身，孤立点的骨架也是自身。那么怎样判断一个点是否能去掉呢？显然，要根据它的八个相邻点的情况来判断，我们给几个例子(如图 6.22 所示)。

图 6.22 中，(1)不能删，因为它是个内部点，我们要求的是骨架，如果连内部点也删了，骨架也会被掏空的；(2)不能删，和(1)是同样的道理；(3)可以删，这样的点不是骨架；(4)不能删，因为删掉后，原来相连的部分断开了；(5)可以删，这样的点不是骨架；(6)不能删，因为它是直线的端点，如果这样的点删了，那么最后整个直线也被删了，剩不下什么。总结一下，有如下的判据：(1)内部点不能删除；(2)孤立点不能删除；(3)直线端点不能删除；(4)如果 P 是边界点，去掉 P 后，如果连通分量不增加，则 P 可以删除。

我们可以根据上述的判据，事先做出一张表，从 0 到 255 共有 256 个元素，每个元素要么是0，要么是 1。我们根据某点(当然是要处理的黑色点了)的八个相邻点的情况查表，若表中的元素是 1，则表示该点可删，否则保留。

``````#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

int main(int* argc, char** argv)
{
FILE* fp = fopen("./threshold.bmp", "rb");
if (fp == 0)
return 0;

int lineByte = (biCount*width / 8 + 3) / 4 * 4;

unsigned char* pBmpBuf,*pBmpBuf2;
pBmpBuf = new unsigned char[lineByte*height];
fclose(fp);

// 新图
FILE* fop = fopen("thinging.bmp", "wb");
if (fop == 0)
return 0;

pBmpBuf2 = new unsigned char[lineByte*height];
for (int i = 0; i < height; ++i){
for (int j = 0; j < width; ++j){
*(pBmpBuf2 + i*lineByte + j) = *(pBmpBuf + i*lineByte + j);
}
}

// 比较矩阵，0为黑，1为白；
// 1:不能；2：不能；3：能；4：不能；5：能；6：不能
char str[6][8] = { { 0, 0, 0, 0, 0, 0, 0, 0, }, { 255, 0, 255, 0, 0, 255, 0, 0 },
{ 255, 0, 255, 255, 0, 255, 0, 255 }, { 255, 255, 255, 0, 0, 255, 255, 255 },
{ 255, 0, 255, 255, 0, 255, 255, 255 }, { 0, 255, 255, 255, 255, 255, 255, 255 } };
int temp, k;
char strTemp[8];
for (int i = 1; i < height-1; ++i){
for (int j = 1; j < width - 1; ++j){
k = 0;
for (int m = -1; m <= 1; ++m){
for (int n = -1; n <= 1; ++n){
temp = *(pBmpBuf + (i - m)*lineByte + (j + n));
strTemp[k] = temp;
k++;
}
}
for (int k = 0; k < 6; ++k){
if (strcmp(strTemp, str[k])==0){
if (k == 3 || k == 5){
*(pBmpBuf2 + i*lineByte + j) = 255;
}
}
}
}
}

fwrite(pBmpBuf2, lineByte*height, 1, fop);
fclose(fop);

system("pause");
return 0;
}``````

原图                     效果图

2018-08-07 14:51:11 sm16111 阅读数 1244
• ###### 美术细化专业课程

通过19可是5个章节，在5个方面跟大家详细的介绍介绍美术设计在游戏中的灵活运用。

10555 人正在学习 去看看 张聪

## Opencv处理图像--细化

``````#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>

IplImage* ColorSrc ,*ColorSrcCopy;

//#define SHOW

//#define LONG int
//#define BYTE unsigned char
/////////////////////////////////////////////////////////////////////////
//基于索引表的细化细化算法
//功能：对图象进行细化
//参数：lpDIBBits：代表图象的一维数组
//      lWidth：图象高度
//      lHeight：图象宽度
//      无返回值
bool ThiningDIBSkeleton (unsigned char* lpDIBBits, int lWidth, int lHeight)
{
//循环变量
long i;
long j;
long lLength;

unsigned char deletemark[256] = {
0,0,0,0,0,0,0,1,	0,0,1,1,0,0,1,1,
0,0,0,0,0,0,0,0,	0,0,1,1,1,0,1,1,
0,0,0,0,0,0,0,0,	1,0,0,0,1,0,1,1,
0,0,0,0,0,0,0,0,	1,0,1,1,1,0,1,1,
0,0,0,0,0,0,0,0,	0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,	0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,	1,0,0,0,1,0,1,1,
1,0,0,0,0,0,0,0,	1,0,1,1,1,0,1,1,
0,0,1,1,0,0,1,1,	0,0,0,1,0,0,1,1,
0,0,0,0,0,0,0,0,	0,0,0,1,0,0,1,1,
1,1,0,1,0,0,0,1,	0,0,0,0,0,0,0,0,
1,1,0,1,0,0,0,1,	1,1,0,0,1,0,0,0,
0,1,1,1,0,0,1,1,	0,0,0,1,0,0,1,1,
0,0,0,0,0,0,0,0,	0,0,0,0,0,1,1,1,
1,1,1,1,0,0,1,1,	1,1,0,0,1,1,0,0,
1,1,1,1,0,0,1,1,	1,1,0,0,1,1,0,0
};//索引表

unsigned char p0, p1, p2, p3, p4, p5, p6, p7;
unsigned char *pmid, *pmidtemp;
unsigned char sum;
int changed;
bool bStart = true;
lLength = lWidth * lHeight;
unsigned char *pTemp = (unsigned char *)malloc(sizeof(unsigned char) * lWidth * lHeight);

//    P0 P1 P2
//    P7    P3
//    P6 P5 P4

while(bStart)
{
bStart = false;
changed = 0;

//首先求边缘点(并行)
pmid = (unsigned char *)lpDIBBits + lWidth + 1;
memset(pTemp,  0, lLength);
pmidtemp = (unsigned char *)pTemp + lWidth + 1;
for(i = 1; i < lHeight -1; i++)
{
for(j = 1; j < lWidth - 1; j++)
{
if( *pmid == 0)
{
pmid++;
pmidtemp++;
continue;
}

p3 = *(pmid + 1);
p2 = *(pmid + 1 - lWidth);
p1 = *(pmid - lWidth);
p0 = *(pmid - lWidth -1);
p7 = *(pmid - 1);
p6 = *(pmid + lWidth - 1);
p5 = *(pmid + lWidth);
p4 = *(pmid + lWidth + 1);

sum = p0 & p1 & p2 & p3 & p4 & p5 & p6 & p7;
if(sum == 0)
{
*pmidtemp = 1;

#ifdef SHOW
cvSet2D(ColorSrc,i,j,cvScalar(0,0,255));
#endif
}

pmid++;
pmidtemp++;
}
pmid++;
pmid++;
pmidtemp++;
pmidtemp++;
}
#ifdef SHOW
cvNamedWindow("color");
cvShowImage("color",ColorSrc);
cvWaitKey(0);
#endif

//现在开始串行删除
pmid = (unsigned char *)lpDIBBits + lWidth + 1;
pmidtemp = (unsigned char *)pTemp + lWidth + 1;

for(i = 1; i < lHeight -1; i++)
{
for(j = 1; j < lWidth - 1; j++)
{
if( *pmidtemp == 0)
{
pmid++;
pmidtemp++;
continue;
}

p3 = *(pmid + 1);
p2 = *(pmid + 1 - lWidth);
p1 = *(pmid - lWidth);
p0 = *(pmid - lWidth -1);
p7 = *(pmid - 1);
p6 = *(pmid + lWidth - 1);
p5 = *(pmid + lWidth);
p4 = *(pmid + lWidth + 1);

p1 *= 2;
p2 *= 4;
p3 *= 8;
p4 *= 16;
p5 *= 32;
p6 *= 64;
p7 *= 128;

sum = p0 | p1 | p2 | p3 | p4 | p5 | p6 | p7;
//	sum = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7;
if(deletemark[sum] == 1)
{
*pmid = 0;
bStart = true;

#ifdef SHOW
cvSet2D(ColorSrc,i,j,cvScalar(0,0,0));

cvNamedWindow("delcolor");
cvShowImage("delcolor",ColorSrc);
cvWaitKey(2);
#endif
}

pmid++;
pmidtemp++;
}

pmid++;
pmid++;
pmidtemp++;
pmidtemp++;
}

#ifdef SHOW
printf("过了一圈\n");
#endif
}

return true;
}

int main()
{
//#define WRITERESULT
//#define WRITEIMAGE

cvThreshold(src,src,100,255,CV_THRESH_BINARY);  //二值化图像
unsigned char* imagedata ;

cvNamedWindow("s");
cvShowImage("s" , src);

FILE* fp ;

#ifdef WRITEIMAGE
fp = fopen("data255.txt","rt+");
#endif

imagedata = (unsigned char*)malloc(sizeof(char)*src->width*src->height); //动态分配内存

int x , y;
for(y=0;y<src->height;y++)
{
unsigned char* ptr  = (unsigned char*)(src->imageData + y*src->widthStep);
for(x=0;x<src->width;x++)
{
imagedata[y*src->width+x] = ptr[x] > 0 ? 1 : 0;

#ifdef WRITEIMAGE
if(ptr[x] > 0)
fprintf(fp,"1");
else
{
fprintf(fp,"0");
}
#endif
}

#ifdef WRITEIMAGE
fprintf(fp,"\n");
#endif
}

#ifdef WRITEIMAGE
fclose(fp);
#endif

#ifdef WRITERESULT
fp = fopen("result.txt","rt+");
#endif

ThiningDIBSkeleton(imagedata,src->width,src->height);//进行细化操作

for(y=0;y<src->height;y++)
{
unsigned char* ptr  = (unsigned char*)(src->imageData + y*src->widthStep);
for(x=0;x<src->width;x++)
{
ptr[x] = imagedata[y*src->width + x]>0? 255 : 0;

#ifdef WRITERESULT
if(ptr[x] > 0)
fprintf(fp,"1");
else
{
fprintf(fp,"0");
}
#endif
}

#ifdef WRITERESULT
fprintf(fp,"\n");
#endif
}

#ifdef WRITERESULT
fclose(fp);
#endif

cvNamedWindow("src");
cvShowImage("src" , src);
cvWaitKey(0);

cvReleaseImage(&src);
cvReleaseImage(&ColorSrc);
free(imagedata);

return 0;
}

``````

细化前后对比

2017-04-18 14:36:14 Isaac320 阅读数 603
• ###### 美术细化专业课程

通过19可是5个章节，在5个方面跟大家详细的介绍介绍美术设计在游戏中的灵活运用。

10555 人正在学习 去看看 张聪

是结构元B在A中的匹配。